2 releases
0.1.1 | Jun 23, 2019 |
---|---|
0.1.0 | Jun 23, 2019 |
#396 in Testing
43KB
864 lines
Test4A
Testing library written in Rust that provides some tools to apply "Advanced" Arrange-Act-Assert testing design.
Table of Contents
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:
#[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 withTest4A
) - Not a single necessary macro
Installation
To use Test4A
in your crate, simply include this in your Cargo.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:
#[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:
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:
fn value_expected(
message: &mut Message,
value: usize,
expected: Expected,
) -> Equal<usize> {
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 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:
#[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
:
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 or http://www.apache.org/licenses/LICENSE-2.0)
- MIT license (LICENSE-MIT or http://opensource.org/licenses/MIT)
at your option.