🦉
Programming Notes
  • My Programming Notes
  • CKA Exam Preparation
    • Certified Kubernetes Administrator
    • Setup Minikube
    • Network Design Principles
    • Role-Based Access Control (RBAC)
    • Namespace
    • Resource Quota
    • Pod
    • Deployment
    • Deployment: Rollout
    • ConfigMap
    • Service
    • Service: kubectl expose
    • Pod: Resources Management
    • Pod & Container: Quality of Service Class
    • Pod & Container: Probes
    • Limit Range
    • Scaling: Manual
    • Scaling: Horizontal Pod Autoscaler
    • Persistent Volume & Claim
    • Secret
    • Ingress: Routing
    • Ingress: TLS
    • Ingress: Rate Limit
    • Ingress: Basic Auth
    • Ingress: CRD (Custom Resource Definition)
    • Job
    • CronJob
    • Mutli-Node Cluster
  • Golang
    • Generics
    • Context
    • Goroutines and Channels in Go
    • Goroutine: Concurrency vs Parallelism
    • Goroutine: Performance & Tradeoffs
    • JSON: omitzero
  • Rust
    • Arrays & Slices
    • Closures
    • Generics & Traits
    • Iterators
    • Run Code Simultaneously
    • String vs &str
    • Tests
    • Rustlings Exercises
      • Variables
      • Functions
      • If
      • Primitive Types
      • Vectors
      • Move Semantics
      • Structs
      • Enums and Matching Pattern
      • Strings
      • Modules
      • Hashmaps
      • Options
      • Error Handling
      • Generics
      • Traits
      • Lifetimes
      • Tests
      • Iterators
      • Smart Pointers
      • Threads
      • Macros
      • Quiz 1
      • Quiz 2
      • Quiz 3
  • Software Engineering
    • CAP Theorem
    • Circuit Breaker
    • Decoupling
    • GraphQL: Query Caching
    • HMAC Signature
    • Idempotency
    • Monolith VS Microservice
    • OWASP Top 10 2021
    • PCI DSS
    • PostgreSQL: Partitioning
    • PostgreSQL: Replication
    • Protobuf & gRPC
    • Redis: Streams
    • Resource Scaling
    • Signed URL
    • SOLID
    • Stack VS Heap
    • Stateful VS Stateless
  • Site Reliability Engineering
    • Chaos Engineering
    • Distributed Tracing
    • Kubernetes (k8s)
    • SLA, SLO, and SLI Metrics
    • Site Reliability Engineer
  • Others
    • FFMPEG Cheat sheet
Powered by GitBook
On this page
  • Unit Test
  • Integration Test
  • Assertion
  • assert!
  • assert_eq!
  • assert_ne!
  • Custom Panic Message
  • Expecting Panic
  • Commonly Paired Functions for Assertions
  • Best Practices
  • References
  1. Rust

Tests

Tests are Rust functions that verify that the non-test code is functioning in the expected manner. The bodies of test functions typically perform these three actions:

  • Set up any needed data or state.

  • Run the code you want to test.

  • Assert that the results are what you expect.

The Rust community thinks about tests in terms of two main categories: unit tests and integration tests.

Unit Test

Unit tests are small and more focused, testing one module in isolation at a time, and can test private interfaces. The purpose of unit tests is to test each unit of code in isolation from the rest of the code to quickly pinpoint where code is and isn’t working as expected.

The convention is to create a module named tests in each file to contain the test functions that annotated with #[test] and to annotate the module with cfg(test).

#[cfg(test)] annotation on the tests module tells Rust to compile and run the test code only when we run cargo test not when we run cargo build. This saves compile time and saves space in the compiled artifact when running cargo build.

Example:

pub fn add(left: usize, right: usize) -> usize {
    left + right
}

#[cfg(test)]
mod tests {
    use super::*;

    #[test]
    fn it_works() {
        let result = add(2, 2);
        assert_eq!(result, 4);
    }
}

Run the test using cargo test.

Integration Test

Integration tests are entirely external to your library and use your code in the same way any other external code would, using only the public interface and potentially exercising multiple modules per test.

To create integration tests, you first need a tests directory at the top level of our project directory, next to src. Cargo knows to look for integration test files in this directory. We can then make as many test files as we want, and Cargo will compile each of the files as an individual crate.

adder
├── Cargo.lock
├── Cargo.toml
├── src
│   └── lib.rs
└── tests
    └── integration_test.rs

Lets add this code into src/lib.rs:

pub mod adder {
    pub fn add(left: usize, right: usize) -> usize {
        left + right
    }
}

And this integration test code in test/integration_test.rs:

use rplay::adder;

#[test]
fn it_adds_two() {
    let result = adder::add(2, 2);
    assert_eq!(result, 4);
}

Because of my project folder is named rplay we import it using use rplay::adder;. So please adjust it using your own project name.

Run the test using cargo test.

Assertion

In general there are 3 types of assertion provided by Rust which is: assert!, assert_eq!, and assert_ne!. But we can combine it with different macros and function to create better test cases.

assert!

  • Checks if the condition is true.

  • Fails if false.

#[test]
fn test_condition() {
    let value = 42;
    assert!(value > 40);
}

assert_eq!

  • Checks if the given two values are equal.

  • Fails if not equal.

#[test]
fn test_equality() {
    assert_eq!(2 + 2, 4);
}

assert_ne!

  • Checks if the two values are not equal.

  • Fails if equal.

#[test]
fn test_inequality() {
    assert_ne!(2 * 3, 7);
}

Custom Panic Message

We can also call panic! explicitly if needed to make the test fail.

#[test]
fn test_fail() {
    panic!("This test will always fail");
}

Additionally all the assertion macros has a second form, where a custom panic message can be provided with or without arguments for formatting.

#[test]
fn test_fail() {
    let x = false;
    assert!(x, "x wasn't true!");
}

And if failed will produce output like this:

---- tests::test_condition stdout ----
thread 'tests::test_condition' panicked at src/main.rs:9:9:
x wasn't true!
stack backtrace:
   0: _rust_begin_unwind
   1: core::panicking::panic_fmt
   2: rplay::tests::test_condition
             at ./src/main.rs:9:9
   3: rplay::tests::test_condition::{{closure}}
             at ./src/main.rs:7:24
   4: core::ops::function::FnOnce::call_once
             at /private/tmp/rust-20250109-8032-r89n27/rustc-1.84.0-src/library/core/src/ops/function.rs:250:5
note: Some details are omitted, run with `RUST_BACKTRACE=full` for a verbose backtrace.

Expecting Panic

We can use #[should_panic] annotation if expect the test will result panic. For example check if a function panics as expected:

#[test]
#[should_panic]
fn test_divide_by_zero() {
    let _ = 1 / 0;
}

Commonly Paired Functions for Assertions

Use other assert-like macros and functions that are frequently used in Rust to validate conditions during testing or debugging.

matches!: Validates if a value matches a specific pattern.

#[test]
fn test_matches() {
    enum Result {
        Ok(i32),
        Err(String),
    }

    let value = Result::Ok(10);
    assert!(matches!(value, Result::Ok(_))); // Check if value is Result::Ok
}

Validation Functions: .is_empty() / .is_none() / .is_some() / .is_ok() / .is_err()

#[test]
fn test_result() {
    let result: Result<i32, &str> = Ok(10);
    assert!(result.is_ok());
    assert_eq!(result.unwrap(), 10);
}

#[test]
fn test_option() {
    let value: Option<i32> = Some(10);
    assert!(value.is_some());
    assert_eq!(value.unwrap(), 10);
}

Iterator Functions: .any() / .all() / .count()

fn test_positive_numbers() {
    let numbers = vec![1, 2, 3];
    assert!(numbers.iter().all(|&x| x > 0)); // Assert all numbers are positive
}

Regular Expression: regex::Regex

Install it in your project using cargo add regex.

use regex::Regex;

#[test]
fn test_regex() {
    let re = Regex::new(r"^\d+$").unwrap();
    assert!(re.is_match("12345"));
}

Best Practices

  • Use assert_eq! or assert_ne!: They provide better error messages than plain assert! for equality checks.

  • Leverage matches! for Pattern Matching: Cleaner and more expressive than manual matching.

  • Use Descriptive Test Names: Explain what is being tested and under what conditions.

  • Use Custom Panic Messages: Add a helpful message to assertions.

  • Keep Assertions Focused: Ensure each test checks a single condition or small, related conditions.

References

PreviousString vs &strNextRustlings Exercises

Last updated 4 months ago

https://doc.rust-lang.org/book/ch11-01-writing-tests.html
https://doc.rust-lang.org/book/ch11-03-test-organization.html
https://doc.rust-lang.org/std/macro.assert.html
https://doc.rust-lang.org/std/macro.assert_eq.html
https://doc.rust-lang.org/std/macro.assert_ne.html