Crates.io | yare |
lib.rs | yare |
version | 3.0.0 |
source | src |
created_at | 2020-12-30 10:02:31.64217 |
updated_at | 2024-03-08 04:25:32.723719 |
description | Lean parameterized test case library for Rust 🚀 |
homepage | |
repository | https://github.com/foresterre/yare |
max_upload_size | |
id | 329192 |
size | 36,787 |
Procedural macro based parameterized testing library. Run a test case with many different inputs. Test cases can be defined using the 'parameterized' attribute instead of the 'test' attribute.
Features
A first example
fn add5<T: Into<u32>>(component: T) -> u32 {
component.into() + 5
}
#[cfg(test)]
mod tests {
use super::*;
use yare::parameterized;
#[parameterized(
zero_plus_five = { 0, 5 },
one_plus_five = { 1, 6 },
two_plus_five = { 2, 7 },
)]
fn test_add5(input: u16, expected: u32) {
assert_eq!(add5(input), expected);
}
}
An example with values
enum Fruit {
Apple,
Bramble(BrambleFruit),
Pear,
}
trait NameOf {
fn name_of(&self) -> &str;
}
impl NameOf for Fruit {
fn name_of(&self) -> &str {
match self {
Fruit::Apple => "apple",
Fruit::Bramble(fruit) => fruit.name_of(),
Fruit::Pear => "pear",
}
}
}
enum BrambleFruit {
Blackberry,
}
impl NameOf for BrambleFruit {
fn name_of(&self) -> &str {
match self {
BrambleFruit::Blackberry => "blackberry",
}
}
}
#[cfg(test)]
mod tests {
use super::*;
use yare::parameterized;
#[parameterized(
apple = { Fruit::Apple, "apple" },
pear = { Fruit::Pear, "pear" },
blackberry = { Fruit::Bramble(BrambleFruit::Blackberry), "blackberry" },
)]
fn a_fruity_test(fruit: Fruit, name: &str) {
assert_eq!(fruit.name_of(), name)
}
}
While the arguments above were simple values, any expression can be used as argument in a test case.
Example
In the example below, we roll the dice 3 times to generate a seed for later roll_dice function calls.
The first argument seed1
is a function call to roll_dice. This randomness function is seeded with value 0
.
The second argument seed2
is a block expression. In the expression the roll_dice function is called twice.
The test itself takes the maximum of seed1
and seed2
, rolls the die 1000 times, and checks that all values
are valid for a d6 die.
use std::sync::atomic::{AtomicU32, Ordering};
use yare::parameterized;
#[parameterized(
seeding_randomness_with_two_dice_rolls =
{
roll_dice(&AtomicU32::new(0)), // <- This is an expression (a function call)
{ // <- This is also an expression (a block expression)
let from_half = roll_dice( &AtomicU32::new(u32::MAX / 2));
let from_max = roll_dice( &AtomicU32::new(u32::MAX));
u8::min(from_half, from_max)
}
}
)]
fn dicey(seed1: u8, seed2: u8) {
// Creating a base seed in a complicated way for the sake of it.
let max = u8::max(seed1, seed2);
let seed = AtomicU32::new(u32::from(max));
let out_of_bounds_values = (0..1000) // roll the dice 1000 times
.map(|_| roll_dice(&seed))
.find(|value| !(1..=6).contains(value)); // check that the outputs of the dice are just 1, 2, 3, 4, 5 or 6.
assert!(out_of_bounds_values.is_none());
}
By default, the code generation step of the parameterized
attribute will generate test cases marked with a #[test]
attribute. For example, the add5
test from the examples would generate something like:
#[cfg(test)]
mod tests {
use super::*;
use yare::parameterized;
// Approximate generated code from add5 example:
#[cfg(test)]
mod add5 {
use super::*;
#[test]
fn zero_plus_five() {
let input: u16 = 0;
let expected: u32 = 5;
assert_eq!(add5(input), expected);
}
#[test]
fn one_plus_five() {
let input: u16 = 1;
let expected: u32 = 6;
assert_eq!(add5(input), expected);
}
#[test]
fn two_plus_five() {
let input: u16 = 2;
let expected: u32 = 7;
assert_eq!(add5(input), expected);
}
}
}
However, sometimes a different test macro is desired. An example is when writing tests for projects which depend on tokio.
For this, you may want to use #[tokio::test]
(it also requires the test function to also have the async
qualifier).
In Yare, it is possible to specify a custom test macro. To do so, you may add the #[test_macro(...)]
attribute after
a #[parameterized]
attribute.
Custom test macro example: tokio::test
use yare::parameterized;
#[parameterized(
zero_wait = { 0, 0 },
show_paused = { 500, 0 },
)]
#[test_macro(tokio::test(start_paused = true))]
async fn test(wait: u64, time_elapsed: u128) {
let start = std::time::Instant::now();
tokio::time::sleep(tokio::time::Duration::from_millis(wait)).await;
assert_eq!(start.elapsed().as_millis(), time_elapsed);
}
Gotchas:
#[test_macro(...)]
must always be specified after a #[parameterized(...)]
attribute.#[test_macro(...)]
attribute per parameterized test function is allowed.use yare::parameterized as pm
), the test_macro
attribute cannot be renamed,
since it's not actually defined as a separate macro.
Instead, the parameterized
macro parses this attribute as well.Yare supports specifying a return type for a parameterized test function.
Note that the underlying test attribute must also have support for return types. By default, Yare generates individual test cases decorated with the familiar test attribute, which is included with any Rust distribution by default.
Example
use yare::parameterized;
#[parameterized(
ok = { Ok(0) },
// err = { Err("noes!".to_string()) }, <-- enabling this would result in a failed test, since the error code will not be an `ErrorCode::Success`. See the `Termination` trait for more.
)]
fn test(value: Result<u32, String>) -> Result<(), String> {
let v = value?;
assert_eq!(v.unwrap(), 0);
}
Yare supports the following function qualifiers: const
, async
, unsafe
and extern
.
This is particularly useful if you use #[parameterized(...)]
with a custom test macro such as tokio::test
, instead
of the built-in test macro.
Example
use yare::parameterized;
#[parameterized(
purple = { & [128, 0, 128] },
orange = { & [255, 127, 0] },
)]
const extern "C" fn has_reds(streamed_color: &[u8]) {
assert!(streamed_color.first().is_some());
}
If you prefer not to import this library (with use yare::parameterized;
) in every test module, you can put
the following snippet at the top of your crate root:
#[cfg(test)]
#[macro_use]
extern crate yare;
Licensed under either of Apache License, Version 2.0 or MIT license at your option.
Unless you explicitly state otherwise, any contribution intentionally submitted for inclusion in this crate by you, as defined in the Apache-2.0 license, shall be dual licensed as above, without any additional terms or conditions.