Assertion macros

In testing, when given a test case, we try to assert the expected behavior of our software component on a given range of inputs. Languages usually provide functions called assertion functions to perform these assertions. Rust provides us with assertion functions, implemented as macros, that help us achieve the same thing. Let's take a look at some of the commonly used ones:

      assert!(true);
assert!(a == b, "{} was not equal to {}", a, b);
  • assert!: This is the simplest assertion macro that takes a Boolean value to assert against. If the value is false, the test panics, showing the line where the failure happened. This can additionally take in a format string, followed by a corresponding number of variables, for providing custom error messages:
      let a = 23;
let b = 87;
assert_eq!(a, b, "{} and {} are not equal", a, b);
  • assert_eq!: This takes in two values and fails if they are not equal. This can also take in a format string for custom error messages.
  • assert_ne!: This is similar to assert_eq! since it takes two values, but only asserts when the values are not equal to each other.
  • debug_assert!: This is similar to assert!. Debug assertion macros can be also be used in code other than test code. This is mostly used in code to assert for any contract or invariant that should be held by the code during runtime. These assertions are only effective on debug builds and help catch assertion violations when run in debug mode. When the code is compiled in optimized mode, these macro invocations are completely ignored and optimized away to a no-op. There are similar variants to this such as debug_assert_eq! and debug_assert_ne!, which work just like the assert! class of macros.

To compare the values within these assertion macros, Rust relies on traits. For example, the == inside assert!(a == b) actually turns into a method call, a.eq(&b), which returns a bool value. The eq method comes from the PartialEq trait. Most built-in types in Rust implement the PartialEq and Eq traits so that they can be compared. The details of these traits and the difference between PartialEq and Eq are discussed in Chapter 4Types, Generics, and Traits.

For user-defined types, however, we need to implement these traits. Fortunately, Rust provides us with a convenient macro called derive, which takes one or more trait names to implement. It can be used by putting the #[derive(Eq, PartialEq)] annotation over any user-defined type. Notice the trait names within parentheses. Derive is a procedural macro that simply generates code for impl blocks for the type on which it appears and implements the trait's methods or any associated functions. We'll discuss these macros when we get to Chapter 9, Metaprogramming with Macros.

With that aside, let's start writing some tests!