#unit-testing #integration-tests #directory

srctrait-common-testing

Structured testing with setup, teardown, and standardized fixture and temp directories

2 releases (1 stable)

Uses new Rust 2024

new 4.0.0 May 19, 2025
0.0.0 May 14, 2025

#232 in Testing

Download history 127/week @ 2025-05-12

127 downloads per month
Used in srctrait-common

AGPL-3.0-or-later

63KB
1K SLoC

SrcTrait Common Testing

Crate Badge Docs Badge License Badge

Structured testing with setup, teardown, and standardized fixture and temp directories

Features

  • Structure tests into inheritable heirarchies: Module -> Test
  • Bundle common assets outside of heirarchies using Group
  • Setup and teardown callbacks for each Module, Test, and Group
  • Module and Group setup occurs before Test
  • Categorize based on use-case: Unit and Integration
  • Standardized paths for fixture and temp directories
  • Miscellaneous helper functions for common tasks

Restrictions

  • One Module per Rust test module
  • One Test per Rust test function
  • Module and Group are static to a Rust module
  • Test is instantiated non-static inside of the Rust test function
  • Unit tests live in src
  • Integration tests live in tests or example
  • Fixture asset files live in testing

Standard paths

use-case / module_path / function_name

Example

// Just something to test against
pub fn fibonacci(n: u8) -> Option<u128> {
    if n == 0 {
        return Some(0);
    } else if n == 1 {
        return Some(1);
    } else if n > FIBONACCI_MAX_N_U128 { // overflow u128
        return None;
    }

    let mut sum: u128 = 0;
    let mut last: u128 = 0;
    let mut curr: u128 = 1;
    for _i in 1..n {
        if let Some(r) = last.checked_add(curr) {
            sum = r;
        } else {
            return None;
        }

        last = curr;
        curr = sum;
    }

    Some(sum)
}

const FIBONACCI_MAX_N_U128: u8 = 185;

// Testing namepaths will strip '::tests' out.
#[cfg(test)]
mod tests {
    use std::{fs, sync::LazyLock};
    use super::*;
    use srctrait_common_testing::prelude::*;

    // Module setup runs before any tests that use the module.
    // Teardown runs on process exit.
    //
    // Fixture dirs are enforced at runtime and panic if they don't exist.
    //
    // This module's namepath().full_path() will be:
    //     srctrait-common-testing/integration/example-fibonacci
    //
    // This module's namepath().path() will be:
    //     integration/example-fibonacci
    //
    // fixture dir: $CRATE/testing/fixtures/integration/example-fibonacci
    // temp dir: $TMP/srctrait-common-testing-example-fibonacci.XXXXXXXX
    static TESTING: testing::Module = testing::module!(Integration, {
            .using_fixture_dir()
            .using_temp_dir()
            .teardown_static(teardown)
            .setup(|module| {
                let fibonacci_u128_csv = (0..FIBONACCI_MAX_N_U128)
                    .map(|n| fibonacci(n).unwrap().to_string())
                    .collect::<Vec<_>>()
                    .join(",");

                let fibonacci_csv_tmp_file = module.temp_dir()
                    .join(&*FIBONACCI_U128_CSV_FILENAME);

                fs::write(&fibonacci_csv_tmp_file, fibonacci_u128_csv).unwrap();
            })
    });

    static FIBONACCI_U128_CSV_FILENAME: LazyLock<String> = LazyLock::new(||
        "fibonacci-u128.csv".to_string());

    // Anything const or static can be used with static teardown functions,
    // including things like LazyLock.
    //
    // Module's internal teardown will delete its temp directory before
    // running its custom teardown.
    extern "C" fn teardown() {
        println!("Farewell, Fibonacci. Don't worry, {} has already been deleted.",
            *FIBONACCI_U128_CSV_FILENAME);
    }

    // Groups are standalone and work similar to Modules, but without children.
    // They have their own tmp and fixture directories, setup, and teardown.
    //
    // This group's namepath().full_path() will be:
    //     srctrait-common-testing/integration/example-fibonacci/100
    // This group's namepath().path() will be:
    //     integration/example-fibonacci/100
    static GROUP_100: testing::Group = testing::group!("example-fibonacci/100", Integration, {
        .using_fixture_dir()
    });

    // The #[tested] helper is merely the equivalent of #[test] and #[named]
    #[tested]
    fn test_u128() {
        // test!() assumes a module named `TESTING` is in scope and links to it.
        //
        // The "inherit_" methods use the same dir as their parent module.
        // The "using_" methods will append a subdir named for the test function.
        //
        // Tests block on their module's setup. The tmp file needed here will exist.
        //
        // fixture dir: $CRATE/testing/fixtures/integration/example-fibonacci/test-u128
        // tmp dir: $TMP/srctrait-common-testing-example-fibonacci.XXXXXXXX
        let test = testing::test!({
            .using_fixture_dir()
            .inherit_temp_dir()
        });

        let fibonacci_fixture_csv_file = test.fixture_dir().join(&*FIBONACCI_U128_CSV_FILENAME);
        let expected_fibonacci_u128 = fs::read_to_string(fibonacci_fixture_csv_file).unwrap()
            .split(",")
            .map(|d| d.trim().parse().unwrap())
            .collect::<Vec<u128>>();

        let fibonacci_tmp_csv_file = test.temp_dir().join(&*FIBONACCI_U128_CSV_FILENAME);
        let actual_fibonacci_u128 = fs::read_to_string(fibonacci_tmp_csv_file).unwrap()
            .split(",")
            .map(|i| i.parse().unwrap())
            .collect::<Vec<u128>>();

        assert_eq!(expected_fibonacci_u128, actual_fibonacci_u128);
    }

    // Demonstration using a Group
    #[tested]
    fn test_100() {
        // This empty Test will block on its Module's setup.
        let _test = testing::test!();

        // Groups will block on their setup.
        let fib100_file = GROUP_100.fixture_dir().join("fib-100.txt");

        let fib100: u128 = fs::read_to_string(fib100_file)
            .map(|d| d.trim().parse().unwrap())
            .unwrap();

        assert_eq!(fib100, fibonacci(100).unwrap());
    }
}

Documentation

Refer to docs.rs/srctrait-common-testing

Repository

Contributors, please review SRCTRAIT.md.

Found a bug? Search for an existing issue on GitHub.
If an issue exists, chime in to add weight to it.
If not, create one and let us know.

License (AGPL3)

SrcTrait Common Testing: Structured testing in sequence with fixture and temp directories
Developed by SourceTrait
Copyright (C) 2025 Asmov LLC

This program is free software: you can redistribute it and/or modify it under the terms of the GNU Affero General Public License as published by the Free Software Foundation, either version 3 of the License, or (at your option) any later version.

This program is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU Affero General Public License for more details.

You should have received a copy of the GNU Affero General Public License along with this program. If not, see https://www.gnu.org/licenses/.

Third-Party Licenses

crate: function_name

Our library publically exports the named macro from Daniel Henry-Mantilla's crate: function_name. It is available for use from our crate as srctrait_common_testing::named.

License (MIT):
Copyright (c) 2019 Daniel Henry-Mantilla

Dependencies

~3–14MB
~187K SLoC