# Test4A Testing library written in Rust that provides some tools to apply "Advanced" [Arrange-Act-Assert](https://github.com/testdouble/contributing-tests/wiki/Arrange-Act-Assert) testing design. # Table of Contents - [Introduction](#introduction) - [Installation](#installation) - [Usage](#usage) - [First use case](#first-use-case) - [Multiple use cases](#multiple-use-cases) - [Ensure a code panics](#ensure-a-code-panics) - [Assertions](#assertions) - [License](#license) ## Introduction Arrange-Act-Assert is a widely used way to design unit tests. When applied, each use case can be written with the following form: ```rust #[test] fn test_use_case() { // Arrange // Act // Assert } ``` This technique ensures that our tests are clear and well delimited. However, if we want to ensure that all methods of a type work correctly, we have to defined a lot of use cases, and so the test code quickly grows.
The main idea behind `Test4A` is to define use cases in a compact and reusable way, in order to write unit tests more efficiently. Here are the main ideas of this library: - An alternative way to define tests in order to be more readable - More assertion types - A simple way to define custom assertions - Support of panic testing - A clear description of the error and detailed information about the use case when a test fails - A great integration with tools provided by Rust and Cargo - No restriction for the types that are tested (some testing frameworks require that the type implements `Clone`, but no trait is required with `Test4A`) - Not a single necessary macro ## Installation To use `Test4A` in your crate, simply include this in your `Cargo.toml`: ```toml [dev_dependencies] test4a = "0.1" ``` ## Usage To clearly understand how to use `Test4A`, let's write some unit tests for the `usize` type. ### First use case We can start by writing our first use case: ```rust #[cfg(test)] mod tests { use test4a::{Equal, Runner}; struct Expected { value: usize, } #[test] fn test_from_0() { Runner::arrange(|message| { message.set("Value of 0"); 0 }) .act( |message, value| { message.set("Add 1"); *value += 1; }, || Expected { value: 1 }, ) .assert(|message, value, expected| { message.set("Value is correct"); Equal::new(value, expected.value) }); } } ``` Here, we define a test that init the `usize` variable to 0, add 1 and test that the value is effectively 1. The code is organized with a Arrange-Act-Assert design, as we can do classically. `Expect` is a type you define to pass data between the Act step and the Assert one. You can use the `message` parameter to define a custom label for each step. These labels will be printed for the failing cases in order to quickly find which use case has failed. ### Multiple use cases In the previous section, we have defined a single use case. However, we can see that a lot of code is written to only define it. `Test4A` becomes interesting when we want to define multiple cases. First of all, let's define some initial states for our `usize` object: - x = 0 (zero) - x = 1 (non zero value) For each initial state, we want to test some actions: - x += 0 (add zero) - x += 1 (add a non zero value) - x -= 1 (subtract a non zero value) We can write a function for each action: ```rust fn add_0(message: &mut Message, value: &mut usize) { message.set("Add 0"); *value += 0; } fn add_1(message: &mut Message, value: &mut usize) { message.set("Add 1"); *value += 1; } fn subtract_1(message: &mut Message, value: &mut usize) { message.set("Subtract 1"); *value -= 1; } ``` Then, we can define all the assertions we want to check. Here, we only want to ensure the value is correct after the execution of an action. Here is the corresponding function: ```rust fn value_expected( message: &mut Message, value: usize, expected: Expected, ) -> Equal { message.set("Value is correct"); Equal::new(value, expected.value) } ``` The function should return a type that implement the `Assert` trait. `Equal` is one of those types that are included in the library. More information can be found in the [Assertions](#assertions) section. Now, we can define a runner for each initial state, and use the previous defined functions to know what actions and assertions to execute: ```rust #[test] fn test_from_0() { Runner::arrange(|message| { message.set("Initial value of 0"); 0 }) .act(add_0, || Expected { value: 0 }) .act(add_1, || Expected { value: 1 }) .act(subtract_1, || Expected { value: 0 }) // This test will fail, see next section .assert(value_expected); } #[test] fn test_from_1() { Runner::arrange(|message| { message.set("Initial value of 1"); 1 }) .act(add_0, || Expected { value: 1 }) .act(add_1, || Expected { value: 2 }) .act(subtract_1, || Expected { value: 0 }) .assert(value_expected); } ``` All possible combinations of action/assertion will be executed independently (the arrange step is executed for each case). You can define as actions and assertions as you want in each runner.
It has to be noted that in case of parallel execution with `cargo test`, all use cases of a runner are executed sequentially. ### Ensure a code panics If you execute the test defined above, one test will fail. This is when we try to subtract 1 to 0. In this case, we don't expect a value for the `usize` object, but instead that the code panics. To define an action that should panic for a given arrangement, you can use `act_panic`: ```rust Runner::arrange(|message| { message.set("Initial value of 0"); 0 }) .act(add_0, || Expected { value: 0 }) .act(add_1, || Expected { value: 1 }) .act_panic(PanicWhen::Debug, subtract_1) .assert(expect_value); ``` Here, we indicate to `Test4A` that the `substract_1` action should panic only when the code is built in debug mode.
The use case defined by this action is immediately stopped after that: the following assertions are not executed. In the same way, you can use `assert_panic` to assert that a code panics while testing an assertion. ### Assertions In the previous sections, we have seen one type of assertion defined by `Test4A`: `Equal`.
To provide more assertions and control them, the library defines an assertion with a type that implement `Assert`. More assertions are available: - compare two values (`Equal`, `NotEqual`, `Greater`, `Less`, `GreaterEqual`, `LessEqual`) - test a boolean (`True`, `False`) - ensure a value is included in a vector (`Contains`) - group multiple assertions defined by iterators (`Multiple`) You can also create your own assertion types by implementing the `Assert` trait. ## License Licensed under either of - Apache License, Version 2.0, ([LICENSE-APACHE](LICENSE-APACHE) or http://www.apache.org/licenses/LICENSE-2.0) - MIT license ([LICENSE-MIT](LICENSE-MIT) or http://opensource.org/licenses/MIT) at your option.