Test early test often: writing tests in Rust is super easy and (kinda) fun!
It's extremely tempting to simply not write tests.
You could forever keep saying "I'll learn how to write tests later" or "Any errors will just show up in production and I'll just catch them there" ... aaaaand I've fallen into that trap many times.
This time let's tackle testing in Rust head-on, up front!
Tests are super simple. Tests make your code better, and they make you a better coder.
Simply put, tests in Rust are functions annotated with #[test]
which are run using cargo test
. If the function panics, the test fails. If it doesn't panic, the test passes. That's it.
We can use assert!
to check if something is true (it will cause the function to panic if the expression is false).
Here's a test that passes.
And here's a test that fails.
We can also use assert_eq!
to check if two things are equal.
And we can use assert_ne!
to check if two things are not equal.
We can put these tests anywhere and cargo test
will run them all. It's common however to put all your tests inside a tests module at the bottom of the file. Annotate this module with #[cfg(test)]
.
Why would we want to do this?
Well, putting tests in a module like this allows us to encapsulate code and any helper functions or libraries we might need for our tests and make sure they don't end up in our final binary.
For simple test functions that don't bring in other code, it's not strictly necessary. You could even put the tests inline right after the functions you're testing.
Here are some tests in action.
// src/main.rs
Well, there you go. Now you can write tests to make sure a function actually does what you want it to do. Super easy and ... kinda fun!
There's a little bit more to testing in Rust and you can read about it in the Rust Book. Here's a breakdown of some other stuff to think about.
#[should_panic]
- put this under#[test]
if you actually want the test to panic (useful for testing error handling).#[ignore]
- put this under#[test]
to ignore the test unless you runcargo test -- --ignored
(useful for tests that take a long time to run).- Integration tests - put tests as separate files in the
tests/
directory if you're making a library and want to test how it integrates with other code. - Custom failure messages - use
assert!(1 == 2, "1 does not equal 2")
to provide a custom failure message. ie. additional arguments toassert!
will be passed toformat!
and used as the failure message. Result<(), String>
- instead of panicking, you can useResult<(), String>
as the return type for your test function and the test will pass if the result isOk(())
and fail if the result isErr(String)
.- use
cargo test -- --nocapture
to see the output ofprintln!
if your tests pass. - choose which tests to run with
cargo test it_works
orcargo test it_works2
.
That's it for now. Good luck out there you beautiful evolving Rustaceans! Go write some amazing tests!
🦀🦀🦀🦀🦀🦀🦀