#assertions #fluent #matcher #assert #testing #debugging

dev spectral

流畅测试断言

8 个版本 (5 个破坏性更新)

使用旧的 Rust 2015

0.6.0 2016 年 12 月 10 日
0.5.2 2016 年 11 月 13 日
0.5.0 2016 年 10 月 30 日
0.4.0 2016 年 10 月 14 日
0.1.0 2016 年 9 月 12 日

#2457 in Rust 模式

Download history 1744/week @ 2024-03-06 4606/week @ 2024-03-13 3773/week @ 2024-03-20 4792/week @ 2024-03-27 2067/week @ 2024-04-03 2017/week @ 2024-04-10 1687/week @ 2024-04-17 2456/week @ 2024-04-24 2717/week @ 2024-05-01 3146/week @ 2024-05-08 2575/week @ 2024-05-15 2510/week @ 2024-05-22 1773/week @ 2024-05-29 2211/week @ 2024-06-05 2779/week @ 2024-06-12 2396/week @ 2024-06-19

9,349 每月下载量
少于 66 crates 中使用

Apache-2.0

125KB
2K SLoC

spectral

Rust 的流畅测试断言。

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

用法

将以下内容添加到您的 Cargo.toml

[dependencies]
spectral = "0.6.0"

然后将其添加到您的 crate 中

extern crate spectral

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

use spectral::prelude::*;

概述

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

简单的断言

例如,为了测试产生的值是否等于预期值,您可以编写

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

命名主题

为了让您的主题更加明确,您可以在.named(...)之后调用assert_that(或asserting(...).that(...)),如果断言失败,它将打印提供的&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)

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

断言(基本)

注意:每个断言的描述和示例在本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

向量

has_length

is_empty

HashMaps

has_length

is_empty

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

does_not_contain_key

contains_entry

does_not_contain_entry

IntoIterator/Iterator

contains

does_not_contain

contains_all_of

mapped_contains

equals_iterator

IntoIterator

matching_contains

可选功能

Num Crate

使用num crate进行浮点数断言。此功能默认启用,但如果您不想依赖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> to not equal <1>
	 but was: equal

matches

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

注意:生成的恐慌信息将仅显示实际值。建议您编写自己的断言,而不是依赖于这个。

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

布尔值

is_true

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

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

is_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");
失败消息
	expected: string containing <"A">
	 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

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。

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

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

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

例如,要创建一个断言,断言 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();
        }
    }
}

依赖项

~240KB