Custom assertions using Rust macros
Recently I wrote about how Rust macros make it easy to refactor repetitive code that might otherwise become annoying in a strongly-typed language. Continuing the theme from that post, I’ve noticed another use case where macros can be beneficial: writing custom assertions in tests.
In the unicode-bom crate, there’s a bunch of tests that assert the different Unicode byte-order marks are parsed correctly and, just as importantly, that a number of similar byte sequences are not incorrectly identified as byte-order marks.
The cruft in these assertions isn’t huge, but it’s enough to reduce readability for the key parts of each assertion. Here’s what one of them looks like without the assistance of a macro:
assert_eq!(Bom::from(vec![0u8, 0u8, 0xfeu8, 0xffu8].as_slice()), Bom::Utf32Be);
That may not seem too bad at first glance, but in context it swiftly becomes a wall of impenetrable boilerplate:
assert_eq!(Bom::from(vec![0u8, 0u8, 0xfeu8].as_slice()), Bom::Null);
assert_eq!(Bom::from(vec![0u8, 0u8, 0xfeu8, 0xfeu8].as_slice()), Bom::Null);
assert_eq!(Bom::from(vec![0u8, 0u8, 0xfeu8, 0xffu8].as_slice()), Bom::Utf32Be);
assert_eq!(Bom::from(vec![0x0eu8, 0xfeu8].as_slice()), Bom::Null);
assert_eq!(Bom::from(vec![0x0eu8, 0xffu8, 0xfeu8].as_slice()), Bom::Null);
assert_eq!(Bom::from(vec![0x0eu8, 0xfeu8, 0xffu8].as_slice()), Bom::Scsu);
However, a macro to tidy it up is pretty simple:
macro_rules! assert_bom {
([$($byte:expr),*], $bom:ident) => {
assert_eq!(Bom::from(vec![$($byte as u8),*].as_slice()), Bom::$bom)
}
}
With that in place, the assertions from earlier now look like this:
assert_bom!([0, 0, 0xfe], Null);
assert_bom!([0, 0, 0xfe, 0xfe], Null);
assert_bom!([0, 0, 0xfe, 0xff], Utf32Be);
assert_bom!([0x0e, 0xfe], Null);
assert_bom!([0x0e, 0xff, 0xfe], Null);
assert_bom!([0x0e, 0xfe, 0xff], Scsu);
The benefit here is that
a reader’s eye
is only confronted by
the key part of each assertion.
All you can see
are the byte sequences
and the expected result,
which means it’s easy to zero in
on any problems
if you’re debugging
a failing test.
And because you’re already used to
the standard assertion macros
like assert! and assert_eq!,
a name like assert_bom!
is immediately intuitive.