Friday, January 24, 2020

Learning Rust - Day 7 - Writing Automated Tests

This is the 7th journal entry of my learning Rust journey. You can read other posts in this series by following the label learning rust.

In this session, I read through Chapter 11 of The Rust Book. It was about writing automated tests in Rust. This chapter was a breeze; for obvious reasons. In fact, the most interesting things I learnt was not even about testing in Rust, but about another feature in the language: Attributes.

I have always noticed things like #[derive(Debug)], #![allow(unused_variables)], etc being used in the language, but I never stopped to actually read up on what they are. I know they were a facility of providing some form of metadata in the language; sort of like @annotations in Java, I just never took the time to find out what they are official called in Rust.

It ended up being that the testing mechanism in Rust revolves around the use of this language feature: notably #[cfg(test)] and #[test], so I took the opportunity to find out what exactly these things were.

They are Attributes and they are:
...a general, free-form metadatum that is interpreted according to name, convention, language, and compiler version...
They can exist in two forms. Outer attributes: the ones that starts with #! and Inner attributes: the ones that starts with  only #. The outer attribute is placed outside something - i.e. before a struct definition, function definition,  module definition etc. The inner attribute is placed inside something. e.g when placed in the (root of a) crate in other to apply an attribute to the crate.

Attributes can also be classified into the following kinds: Built-in attributesMacro attributesDerive macro helper attributes, and Tool attributes. So far, so good, I find the Built-in attributes to be the most interesting ones, because, based on my knowledge of Rust, they are the ones I have mostly encountered. A list of these Built-in attributes can be found here.

Apart from learning more about Attributes, there were a couple of things I picked up about testing in Rust that are worth noting:

  • Use #[cfg(test)] attribute on module that contain test functions. Use #[test] attribute on test functions within the test module.
  • assert!assert_eq! and assert_ne! are macros that can be used for asserting test conditions.
  • favour assert_eq! and assert_ne! over assert! because they provide more useful messages in case of test failures and allow specifying of custom error messages on test failures. 
  • cargo test --help displays options that can be used with cargo test while
    cargo test -- --help displays the options you can use after the separator --. It looks like that latter can only be ran in the root of a rust project.
  • You can’t use the #[should_panic] annotation on tests that use Result<T, E>. An Err value would need to be returned to signify an expectation of error.
  • Run the tests with cargo test -- --test-threads=1 to prevent concurrent execution of tests 
  • Use #[ignore] attribute to ignoring some tests unless specifically requested.
  • To specifically run a single test, pass the name of the test function to cargo test. That is
    cargo test name_of_test_function 
  • Use cargo test -- --nocapture to also see the outputs (if any) from succeeding tests. By default outputs are shown only for failed tests
  • If there is setup code to be shared across different tests, then make sure to put them inside of tests/common/mod.rs instead of tests/common.rs. Not doing this would make the setup code appear in the test results.
  • Unit tests are placed in the same file as the module, while integration tests go into tests/integration_test.rs and they do not need #[cfg(test)] attribute; only the #[test] attribute is needed.
That was it for learning about writing automated tests in Rusts. I would be taking on Chapter 12 next. Which is: An I/O Project: Building a Command Line Program. It Looks like a chapter that would help solidify some of the concepts presented in the book thus far. Looking forward!

No comments: