Crates.io | test4a |
lib.rs | test4a |
version | 0.1.1 |
source | src |
created_at | 2019-06-23 15:23:59.487148 |
updated_at | 2019-06-23 17:33:37.342216 |
description | Testing library that provides some tools to apply "Advanced" Arrange-Act-Assert testing design. |
homepage | |
repository | https://gitlab.com/Nicolas-Ferre/test4a |
max_upload_size | |
id | 143005 |
size | 60,836 |
Testing library written in Rust that provides some tools to apply "Advanced" Arrange-Act-Assert testing design.
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:
Clone
, but no trait is required with Test4A
)To use Test4A
in your crate, simply include this in your Cargo.toml
:
[dev_dependencies]
test4a = "0.1"
To clearly understand how to use Test4A
, let's write some unit tests for the
usize
type.
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.
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:
For each initial state, we want to test some actions:
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.
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.
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:
Equal
, NotEqual
, Greater
, Less
, GreaterEqual
,
LessEqual
)True
, False
)Contains
)Multiple
)You can also create your own assertion types by implementing the Assert
trait.
Licensed under either of
at your option.