Ecosyste.ms: Awesome
An open API service indexing awesome lists of open source software.
https://github.com/foresterre/yare
🛶 Lean parametrized testing library for Rust
https://github.com/foresterre/yare
crate hacktoberfest library macros parameterised parameterised-tests parameterized parameterized-tests parametrized parametrized-tests rust rust-lang test testing unit-test unit-testing
Last synced: about 15 hours ago
JSON representation
🛶 Lean parametrized testing library for Rust
- Host: GitHub
- URL: https://github.com/foresterre/yare
- Owner: foresterre
- License: apache-2.0
- Created: 2020-12-30T10:59:20.000Z (about 4 years ago)
- Default Branch: main
- Last Pushed: 2024-12-10T22:28:58.000Z (about 1 month ago)
- Last Synced: 2025-01-19T06:38:36.419Z (3 days ago)
- Topics: crate, hacktoberfest, library, macros, parameterised, parameterised-tests, parameterized, parameterized-tests, parametrized, parametrized-tests, rust, rust-lang, test, testing, unit-test, unit-testing
- Language: Rust
- Homepage: https://docs.rs/yare
- Size: 97.7 KB
- Stars: 21
- Watchers: 3
- Forks: 1
- Open Issues: 2
-
Metadata Files:
- Readme: README.md
- Changelog: CHANGELOG.md
- License: LICENSE-APACHE
Awesome Lists containing this project
README
# Yare ⛵
A lean procedural macro based _parameterized testing library_. Run a test case with many different inputs.
Patameterized test cases are defined using the 'parameterized' attribute instead of a default 'test' attribute.**Features**
- **Parameterized Testing:** Specify different inputs to test multiple scenarios with a single test definition.
- **Be flexible:** Arguments provided to parameterized test cases are expressions.
- **Works out of the box:** Works with any Rust version out of the box. No custom test harness necessary.
- **Concise yet comprehensive:** Minimalstic doesn't need to mean 'featureless'.
- **Familiar syntax:** Maintains code readability with familiar Rustic syntax.
- **Promotes :** Each test case has a user defined name which can be referred back to, and can be used to run
individual test cases.
- **Battle tested:** Used for years in tests of
the [cargo-msrv](https://crates.io/crates/cargo-msrv), [bisector](https://crates.io/crates/bisector)
and [rust-releases](https://crates.io/crates/rust-releases) crates (amongst others).## Table of contents
* [Introduction](#yare-)
* [Examples](#examples-back-to-top)
* [Arguments are expressions](#arguments-are-expressions-back-to-top)
* [Custom test macro (e.g. tokio::test)](#custom-test-macro-eg-tokiotest-back-to-top)
* [Return types](#return-types-back-to-top)
* [Function qualifiers](#function-qualifiers-back-to-top)
* [Global #[parameterized(...)] import](#globally-importing-parameterized-back-to-top)
* [Alternatives](#alternatives-back-to-top)
* [License](#license-back-to-top)## Examples (back to top)
**A first example**
```rust
fn add5>(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**
```rust
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)
}
}
```
## Arguments are expressions (back to top)
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](https://github.com/foresterre/yare/blob/main/src/tests/dice.rs) 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.```rust
use std::sync::atomic::{AtomicU32, Ordering};
use yare::parameterized;#[parameterized(
// A complex input for the sake of showing that inputs are expressions...
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());
}
```## Custom test macro (e.g. tokio::test) (back to top)
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](#examples-back-to-top) would generate something like:```rust
#[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**
```rust,ignore
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:
* The `#[test_macro(...)]` must always be specified after a `#[parameterized(...)]` attribute.
* Only one `#[test_macro(...)]` attribute per parameterized test function is allowed.
* While you can rename the parameterized attribute using import aliassing (
e.g. `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.## Return types (back to top)
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](https://doc.rust-lang.org/reference/attributes/testing.html#the-test-attribute)
attribute, which is included with any Rust distribution by default.**Example**
```rust
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) -> Result<(), String> {
let v = value?;assert_eq!(v.unwrap(), 0);
}```
## Function qualifiers (back to top)
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**
```rust
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());
}
```## Globally importing parameterized (back to top)
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:```rust
#[cfg(test)]
#[macro_use]
extern crate yare;
```## Alternatives (back to top)
If Yare is not quite what you're looking for, there are some alternatives:
- [Parameterized](https://github.com/foresterre/parameterized): Attribute based macro with syntax inspired by JUnit (disclaimer: I authored this one too)
- [Rstest](https://github.com/la10736/rstest)
- [Test-case](https://github.com/frondeus/test-case)## License (back to top)
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.