30 Days of Rust - Day 23
Tags: rust, devjournal, learning, beginners
Published: 2023-05-31 21:01:40

Good evening everyone,
Apologies for the flaky posts recently. It's difficult to pull myself away from the warm summer days. That said, today's topic is something that lies close to my heart: testing!

## Yesterday's questions answered
*No questions to answer*

## Today's open questions
*No open questions*

## What we already know
This is the first introduction to testing in this blog series. We already covered some basic stuff in [this article](https://dev.to/johnnylarner/30-days-of-rust-valley-of-despair-26kb):
> - Store your **unit tests** in the same file as your features
> - Dedicate a **tests** folder for integration tests
> - Unit tests should be declared as a `mod`, wrapped by a `#[cfg(test)]` decorator
> - Each test should have a `#[test]` decorator
> - Use any of the assertion macros for testing outputs
> - Use the `#[should_panic(msg)]` decorator to test error handling
> - Run `cargo test` to run your suite

## Choose your macro!
Rust has a few useful macros for helping developers easily validate the outputs of tests.
> `assert!` : Best used to evaluate boolean return values
> `assert_eq!`: Use this to compare two values
> `assert_neq!`: Most suited to when you know an answer can be non-deterministic but should explicitly not be a particular value.

### Debugging macros
One useful feature of Rust's equality macros are the optional debug arguments. You can provide a format string along with arguments and these will be printed when that assertion fails:
```rust
assert_eq!(1 + 1, 4, "{} + {} must equal {}", 1, 1, 4);
```

This is particularly useful if the test requires additional context due to complexity.

One thing to note is that Rust encourages users to use `left` and `right` as aliases for values being evaluated and are not considered equivalent to `expected` and `actual`. The semantic concept of `expected` and `actual` is just ignored in Rust.

## Catching the right panic
The `#[should_panic]` decorator can be provided an argument `expected`. This uses substring matching to evaluate whether a test panicked for the correct reason:
```rust
#[test]
#[should_panic(expected = "less than or equal to 100")] 
fn greater_than_100() { 
	Guess::new(200);
}
```

This test will only go green if the panic message contains the substring we specify.

### Leveraging the `?` operator for tricky tests
We've already learned about how the `?` can be used to reduce the burden of handling `Result` types in Rust. This also holds true for tests we right. The code we write will often return errors at multiple stages, so by declaring the return type of a test as a `Result`, we can use the `?` operator on the code we call.

## Separating your tests
Rust is the first language I've learned that asks developers to include unit test code in the source files in which the code being tested is located. Proximity to source code can make writing tests easier (especially if you only have a small screen 🐒).

The Rust compiler ignores this code when the project is built. It achieves this through the `#[cfg(test)]` decorator that you use to wrap your unit test `mod`.

### Writing integration tests
Integration tests follow a more typical style. Your project should house the tests in the `tests` folder. Any `.rs` files you put here will be treated as integration tests, designed to test your code's public interfaces in a realistic fashion.

Test setup should be also be stored in this folder, though in a subdirectory. One example would be `tests/common/mod.rs`. Here you could declare public functions that can be imported into your integration test files. By using the old school `mod.rs` path, you can indicate to the compiler to not treat the files as tests.

## Master your test runs
We use `cargo` to run tests in Rust: 
	`cargo run test`
	
That's the most basic way to run all your tests. If you provide an argument after `test`, this will be used to filter all test names. This is similar to the `-k` flag in pytest.

Here are some other useful commands:
```shell
cargo test -- --show-output # Display stdout for passing tests

cargo test -- --test-threads=1 # Single-threaded runs for better reliability

cargo test -- --ignored # Run only tests with #[ignore] decorator

cargo test -- --include-ignored # Run all tests
```