5 个版本 (破坏性更新)

使用旧的 Rust 2015

0.11.0 2022年12月3日
0.10.0 2022年10月28日
0.9.0 2022年4月15日
0.8.0 2021年10月31日
0.7.0 2021年9月18日

#165Rust 模式

Download history 3225/week @ 2024-03-13 2345/week @ 2024-03-20 2208/week @ 2024-03-27 2453/week @ 2024-04-03 2275/week @ 2024-04-10 3166/week @ 2024-04-17 3796/week @ 2024-04-24 3156/week @ 2024-05-01 2757/week @ 2024-05-08 2725/week @ 2024-05-15 2514/week @ 2024-05-22 3167/week @ 2024-05-29 3508/week @ 2024-06-05 4805/week @ 2024-06-12 3070/week @ 2024-06-19 2832/week @ 2024-06-26

每月 14,851 次下载
58 包使用(53 个直接使用)

Apache-2.0

140KB
2.5K SLoC

这是对未维护的包 spectral 的分支。Spectral 五年未变,但仍然非常可用,这个分支的目的是在不破坏现有 API 的情况下添加新的断言功能。

speculoos

Crates.io CI codecov GitHub

Rust 的流畅式测试断言。

受 Google Truth 和其他流畅断言框架的影响。

用法

将此添加到您的 Cargo.toml

[dependencies]
speculoos = "0.9.0"

要快速开始使用断言,只需在测试模块中使用 prelude 模块

use speculoos::prelude::*;

概述

Speculoos 允许您以流畅的方式编写断言,通过区分您要测试的内容、测试对象以及断言方式。

简单断言

例如,要测试生成的值是否等于预期值,您将编写

assert_that(&1).is_equal_to(1);

或者测试 Vec 是否包含一定数量的元素

let test_vec = vec![1,2,3];
assert_that(&test_vec).has_length(3);

可用于断言的方法取决于测试的类型以及实现的特性。

如以下所述,建议使用 assert_that! 宏的形式来为失败的断言提供正确的文件和行号。

失败消息

对于失败的断言,通常的 panic 消息遵循以下格式

    expected: <2>
     but was: <1>

为了对 panic 消息添加额外的说明,您也可以通过调用 asserting(...) 函数而不是 assert_that(...)

asserting(&"test condition").that(&1).is_equal_to(&2);

这将产生

    test condition:
    expected: <2>
     but was: <1>

使用 assert_that! 宏的形式将为您提供失败的断言的文件和行号

    expected: vec to have length <2>
     but was: <1>

    at location: tests/parser.rs:112

命名主题

为了使您的主题实际内容更明显,您可以在assert_that(或asserting)之后调用.named(...),这将打印出提供的&str作为主题名称,如果断言失败。

assert_that(&thing.attributes).named(&"thing attributes").has_length(2);

在失败时,这将显示

    for subject [thing attributes]
    expected: vec to have length <2>
     but was: <1>

映射值

如果您想对结构体中包含的值进行断言,您可以使用闭包调用map(...),这将根据闭包的返回值创建一个新的Spec。然后您可以对映射值调用任何适用的断言。

let test_struct = TestStruct { value: 5 };
assert_that(&test_struct).map(|val| &val.value).is_equal_to(5);

宏定义

如果您将#[macro_use]添加到extern crate声明中,您还可以使用assert_thatasserting的宏形式。

assert_that!(test_vec).has_length(5)

这允许您在不需要故意将其转换为引用的情况下传递要测试的主题。然而,为了保持一致性,您也可以在宏中使用故意引用。

assert_that!(&test_vec).has_length(5)

此外,这将为您提供失败断言的文件和行号(而不是仅内部speculoos恐慌位置)。

断言(基本)

注意:每个断言的描述和示例在下面的readme中进一步说明。

通用

is_equal_to

is_not_equal_to

matches

布尔值

is_true

is_false

数字

is_less_than

is_less_than_or_equal_to

is_greater_than

is_greater_than_or_equal_to

浮点数(可选)

is_close_to

选项

is_some -> (返回一个新的包含Option值的Spec)

is_none

contains_value

路径

exists

does_not_exist

is_a_file

is_a_directory

has_file_name

结果

is_ok -> (返回一个新的包含Ok值的Spec)

is_err -> (返回一个新的包含Err值的Spec)

is_ok_containing

is_err_containing

字符串

starts_with

ends_with

contains

is_empty

向量

has_length

is_empty

HashMaps

has_length

is_empty

contains_key -> (返回一个新的包含键值的Spec)

does_not_contain_key

contains_entry

does_not_contain_entry

HashSet

has_length

is_empty

contains (从迭代器中)

does_not_contain (从迭代器中)

contains_all_of (从迭代器中)

IntoIterator/Iterator

contains

does_not_contain

contains_all_of

mapped_contains

equals_iterator

IntoIterator

matching_contains

可选功能

Num Crate

num crate用于Float断言。此功能默认启用,但如果您不想依赖num,则可以简单地禁用它。

断言(详细)

一般而言,任何测试的类型通常都需要实现至少Debug。其他断言将具有不同的边界。

通用

is_equal_to

断言主题和预期值相等。主题类型必须实现 PartialEq

示例
assert_that(&"hello").is_equal_to(&"hello");
失败信息
	expected: <2>
	 but was: <1>

is_not_equal_to

断言主题和预期值不相等。主题类型必须实现 PartialEq

示例
assert_that(&"hello").is_not_equal_to(&"hello");
失败信息
	expected: <1> not equal to <1>
	 but was: equal

matches

接受一个接受主题类型并返回布尔值的函数。返回false将导致断言失败。

注意:生成的panic消息只会说明实际值。建议您编写自己的断言而不是依赖于这个。

示例
assert_that(&"Hello").matches(|val| val.eq(&"Hello"));
失败信息
	expectation failed for value <"Hello">

布尔值

is_true

断言主题为true。主题类型必须是 bool

示例
assert_that(&true).is_true(); 
失败信息
	expected: bool to be <true>
	 but was: <false>

is_false

断言主题为false。主题类型必须是 bool

示例
assert_that(&false).is_false();
失败信息
	expected: bool to be <false>
	 but was: <true>

数字

is_less_than

断言主题值小于预期值。主题类型必须实现 PartialOrd

示例
assert_that(&1).is_less_than(&2);
失败信息
	expected: value less than <2>
	 but was: <3>

is_less_than_or_equal_to

断言主题小于或等于预期值。主题类型必须实现 PartialOrd

示例
assert_that(&2).is_less_than_or_equal_to(&2);
失败信息
	expected: value less than or equal to <2>
	 but was: <3>

is_greater_than

断言主题大于预期值。主题类型必须实现 PartialOrd

示例
assert_that(&2).is_greater_than(&1);
失败信息
	expected: value greater than <3>
	 but was: <2>

is_greater_than_or_equal_to

断言主题大于或等于预期值。主题类型必须实现 PartialOrd

示例
assert_that(&2).is_greater_than_or_equal_to(&1); 
失败信息
	expected: value greater than or equal to <3>
	 but was: <2>

浮点数(可选)

is_close_to

断言主题值在指定的公差内接近预期值。主题类型必须实现 FloatDebug

示例
assert_that(&2.0f64).is_close_to(2.0f64, 0.01f64);
失败信息
	expected: float close to <1> (tolerance of <0.01>)
	 but was: <2>

选项

is_some -> (返回一个新的包含Option值的Spec)

断言主题是 Some。主题类型必须是一个 Option

如果它是 Some,则返回一个新的包含解包值的 Spec

示例
assert_that(&Some(1)).is_some();
链式调用
assert_that(&option).is_some().is_equal_to(&"Hello");
失败信息
	expected: option[some]
	 but was: option[none]

is_none

断言主题是 None。值类型必须是一个 Option

示例
assert_that(&Option::None::<String>).is_none();
失败信息
	expected: option[none]
	 but was: option<"Hello">

contains_value

断言主题是一个包含预期值的 Some。主题类型必须是一个 Option

示例
assert_that(&Some(1)).contains_value(&1);
失败信息
	expected: option to contain <"Hi">
	 but was: <"Hello">

路径

exists

断言主题 PathPathBuf 指向一个现有位置。

示例
assert_that(&Path::new("/tmp/file")).exists();
失败信息
	expected: Path of <"/tmp/file"> to exist
	 but was: a non-existent Path

does_not_exist

断言主题 PathPathBuf 不指向一个现有位置。

示例
assert_that(&Path::new("/tmp/file")).does_not_exist();
失败信息
	expected: Path of <"/tmp/file"> to not exist
     but was: a resolvable Path

is_a_file

断言主题 PathPathBuf 指向一个现有文件。

示例
assert_that(&Path::new("/tmp/file")).is_a_file();
失败信息
	expected: Path of <"/tmp/file"> to be a file
	 but was: not a resolvable file

is_a_directory

断言主题 PathPathBuf 指向一个现有目录。

示例
assert_that(&Path::new("/tmp/dir/")).is_a_directory();
失败信息
	expected: Path of <"/tmp/dir/"> to be a directory
	 but was: not a resolvable directory

has_file_name

断言主题 PathPathBuf 拥有预期的文件名。

示例
assert_that(&Path::new("/tmp/file")).has_file_name(&"file");
失败信息
	expected: Path with file name of <pom.xml>
	 but was: <Cargo.toml>

结果

is_ok -> (返回一个新的包含Ok值的Spec)

断言主题是 Ok。值类型必须是一个 Result

如果它是 Ok,则返回一个新的包含解包值的 Spec

示例
assert_that(&Result::Ok::<usize, usize>(1)).is_ok();
链式调用
let result: Result<&str, &str> = Ok("Hello");
assert_that(&result).is_ok().is_equal_to(&"Hello");
失败信息
	expected: result[ok]
	 but was: result[error]<"Oh no">

is_err -> (返回一个新的包含Err值的Spec)

断言主题是 Err。值类型必须是一个 Result

如果它是 Err,则返回一个新的包含解包值的 Spec

注意:这曾经被称为 is_error,但已被重命名为符合标准Rust命名。

示例
assert_that(&Result::Err::<usize, usize>(1)).is_err();
链式调用
let result: Result<&str, &str> = Err("Hello");
assert_that(&result).is_err().is_equal_to(&"Hello");
失败信息
	expected: result[error]
	 but was: result[ok]<"Hello">

is_ok_containing

断言主题是一个包含预期值的 Ok 结果。主题类型必须是 Result

示例
assert_that(&Result::Ok::<usize, usize>(1)).is_ok_containing(&1);
失败信息
	expected: Result[ok] containing <"Hi">
	 but was: Result[ok] containing <"Hello">

is_err_containing

断言主题是一个包含预期值的 Err 结果。主题类型必须是 Result

示例
assert_that(&Result::Err::<usize, usize>(1)).is_err_containing(&1);
失败信息
	expected: Result[err] containing <"Oh no">
	 but was: Result[err] containing <"Whoops">

字符串

starts_with

断言主题 &strString 以提供的 &str 开头。

示例
assert_that(&"Hello").starts_with(&"H");
失败信息
	expected: string starting with <"A">
	 but was: <"Hello">

ends_with

断言主题 &strString 以提供的 &str 结尾。

示例
assert_that(&"Hello").ends_with(&"o");
失败信息
	expected: string ending with <"A">
	 but was: <"Hello">

contains

断言主题 &strString 包含提供的 &str

示例
assert_that(&"Hello").contains(&"e");

does_not_contain

断言主题 &strString 不包含提供的 &str

示例
assert_that(&"Hello").does_not_contain(&"Bonjour");
失败信息
	expected: string containing <"A">
	 but was: <"Hello">

is_empty

断言主题 &strString 表示一个空字符串。

示例
assert_that(&"").is_empty();
失败信息
	expected: an empty string
	 but was: <"Hello">

向量

has_length

断言主题向量的长度等于提供的长度。主题类型必须是 Vec

示例
assert_that(&vec![1, 2, 3, 4]).has_length(4);
失败信息
	expected: vec to have length <1>
	 but was: <3>

is_empty

断言主题向量是空的。主题类型必须是 Vec

示例
let test_vec: Vec<u8> = vec![];
assert_that(&test_vec).is_empty();
失败信息
	expected: an empty vec
	 but was: a vec with length <1>

HashMaps

has_length

断言主题哈希表的长度等于提供的长度。主题类型必须是 HashMap

示例
let mut test_map = HashMap::new();
test_map.insert(1, 1);
test_map.insert(2, 2);

assert_that(&test_map).has_length(2);
失败信息
	expected: hashmap to have length <1>
	 but was: <2>

is_empty

断言主题哈希表是空的。主题类型必须是 HashMap

示例
let test_map: HashMap<u8, u8> = HashMap::new();
assert_that(&test_map).is_empty();
失败信息
	expected: an empty hashmap
	 but was: a hashmap with length <1>

contains_key -> (返回一个新的包含键值的Spec)

断言主题哈希表包含预期的键。主题类型必须是 HashMap

如果键存在,这将返回一个包含关联值的新 Spec

示例
let mut test_map = HashMap::new();
test_map.insert("hello", "hi");

assert_that(&test_map).contains_key(&"hello");
链式调用
let mut test_map = HashMap::new();
test_map.insert("hello", "hi");

assert_that(&test_map).contains_key(&"hello").is_equal_to(&"hi");
失败信息
	expected: hashmap to contain key <"hello">
	 but was: <["hey", "hi"]>

does_not_contain_key

断言主题哈希表不包含提供的键。主题类型必须是 HashMap

示例
let mut test_map = HashMap::new();
test_map.insert("hello", "hi");

assert_that(&test_map).does_not_contain_key(&"hey");
失败信息
	expected: hashmap to not contain key <"hello">
	 but was: present in hashmap

contains_entry

断言主题哈希表包含预期的键和值。主题类型必须是 HashMap

示例
let mut test_map = HashMap::new();
test_map.insert("hello", "hi");

assert_that(&test_map).contains_entry(&"hello", &"hi");
失败信息
    expected: hashmap containing key <"hi"> with value <"hey">
     but was: key <"hi"> with value <"hello"> instead

does_not_contain_entry

断言主题哈希表不包含提供的键和值。主题类型必须是 HashMap

示例
let mut test_map = HashMap::new();
test_map.insert("hello", "hi");

assert_that(&test_map).does_not_contain_entry(&"hello", &"hey");
失败信息
    expected: hashmap to not contain key <"hello"> with value <"hi">
     but was: present in hashmap

HashSet

has_length

断言主题哈希表的长度等于提供的长度。主题类型必须是 HashSet

示例
let mut test_map = HashSet::new();
test_map.insert(1);
test_map.insert(2);

assert_that(&test_map).has_length(2);
失败信息
	expected: hashset to have length <1>
	 but was: <2>

is_empty

断言主题哈希表是空的。主题类型必须是 HashSet

示例
let test_map: HashSet<u8> = HashSet::new();
assert_that(&test_map).is_empty();
失败信息
	expected: an empty hashset
	 but was: a hashset with length <1>

基于迭代器的断言

由于哈希表实现了迭代器,以下所有的迭代器断言也适用(例如 contains & does_not_contain)

IntoIterator/Iterator

contains

断言主题包含提供的值。主题必须实现 IntoIteratorIterator,并且包含的类型必须实现 PartialEqDebug

示例
let test_vec = vec![1,2,3];
assert_that(&test_vec).contains(&2);
失败信息
	expected: iterator to contain <1>
	 but was: <[5, 6]>

does_not_contain

断言主题不包含提供的数据。主题必须实现 IntoIteratorIterator,并且包含的类型必须实现 PartialEqDebug

示例
let test_vec = vec![1,2,3];
assert_that(&test_vec).does_not_contain(&4);
失败信息
	expected: iterator to not contain <1>
	 but was: <[1, 2]>

contains_all_of

断言主题包含所有提供的数据。主题必须实现 IntoIteratorIterator,并且包含的类型必须实现 PartialEqDebug

示例
let test_vec = vec![1, 2, 3];
assert_that(&test_vec.iter()).contains_all_of(&vec![&2, &3]);
失败信息
    expected: iterator to contain items <[1, 6]>
     but was: <[1, 2, 3]>

mapped_contains

在断言映射后的主题包含提供的数据之前,映射主题的值。主题必须实现 IntoIterator,映射值的类型必须实现 PartialEq

注意:恐慌信息将引用映射值,而不是原始主题中的值。

示例
#[derive(PartialEq, Debug)]
struct Simple {
    pub val: usize,
}

...

assert_that(&vec![Simple { val: 1 }, Simple { val: 2 } ]).mapped_contains(|x| &x.val, &2);
失败信息
	expected: iterator to contain <5>
	 but was: <[1, 2, 3]>

equals_iterator

断言主题等于提供的迭代器。主题必须实现 IntoIteratorIterator,包含的类型必须实现 PartialEqDebug,并且期望值必须实现 Iterator 和 Clone。

示例
let expected_vec = vec![1,2,3];
let test_vec = vec![1,2,3];
assert_that(&test_vec).equals_iterator(&expected_vec.iter());
失败信息
	expected: Iterator item of <4> (read <[1, 2]>)
	 but was: Iterator item of <3> (read <[1, 2]>)

IntoIterator

matching_contains

使用提供的函数通过匹配项断言主题包含匹配项。主题必须实现 IntoIterator,并且包含的类型必须实现 Debug

示例
let mut test_into_iter = LinkedList::new();
test_into_iter.push_back(TestEnum::Bad);
test_into_iter.push_back(TestEnum::Good);
test_into_iter.push_back(TestEnum::Bad);

assert_that(&test_into_iter).matching_contains(|val| {
    match val {
        &TestEnum::Good => true,
        _ => false
    }
});
失败信息
expectation failed for iterator with values <[Bad, Bad, Bad]>

工作原理

Spec 结构体实现了一系列不同的有界特质,这些特质基于界限类型提供断言。

作为一个单一的例子,长度断言由 VecAssertions 特质提供

pub trait VecAssertions {            
    fn has_length(self, expected: usize);
} 

然后由 Spec 实现

impl<'s, T> VecAssertions for Spec<'s, Vec<T>> {
    fn has_length(self, expected: usize) {
      ...
    }
} 

自然地,特质需要在应用之前通过 use 包含,但为了避免过多的 use 语句,有一个 prelude 模块重新导出常用的断言特质。

创建自己的

要创建自己的断言,只需创建一个新的特质,包含你的断言方法,并针对它实现 Spec。

要使断言失败,在你的断言方法中使用 from_spec(...) 创建一个新的 AssertionFailure 结构体,并在其中传递 self

AssertionFailure 还实现了构建器方法 with_expected(...)with_actual(...)fail(...),这提供了失败测试所需的通常的消息格式。如果您需要更大的对失败消息的控制,您可以调用 fail_with_message(...),这将直接打印提供的信息。

在两种情况下,使用 asserting(...) 提供的任何描述都将始终附加到恐慌信息。

例如,要创建一个断言,表示 Vec 的长度至少是一个特定的值

trait VecAtLeastLength {
    fn has_at_least_length(&mut self, expected: usize);
}

impl<'s, T> VecAtLeastLength for Spec<'s, Vec<T>> {
    fn has_at_least_length(&mut self, expected: usize) {
        let subject = self.subject;
        if expected > subject.len() {
            AssertionFailure::from_spec(self)
                .with_expected(format!("vec with length at least <{}>", expected))
                .with_actual(format!("<{}>", subject.len()))
                .fail();
        }
    }
}

依赖项

~465KB