2 个版本
0.0.2 | 2024年1月18日 |
---|---|
0.0.1 | 2024年1月15日 |
#145 in 测试
485KB
9K SLoC
目录
介绍 Clearcheck
轻松编写表达性和优雅的断言!
clearcheck 旨在使断言语句尽可能清晰和简洁。它允许链式调用多个断言,以实现流畅和直观的语法,从而生成更多自文档化的测试用例。
let pass_phrase = "P@@sw0rd1 zebra alpha";
pass_phrase.should_not_be_empty()
.should_have_at_least_length(10)
.should_contain_all_characters(vec!['@', ' '])
.should_contain_a_digit()
.should_not_contain_ignoring_case("pass")
.should_not_contain_ignoring_case("word");
主要功能
🔹 流畅的 API:链式调用断言以获得自然和可读的体验。
🔹 广泛的断言:涵盖常见验证需求的多种断言。
🔹 可定制:根据特定领域需求扩展自己的断言。
🔹 类型安全:利用 Rust 的类型系统构建,确保断言可靠且具有表现力。
🔹 自定义断言:根据精确需求定制断言,确保对各种数据结构进行全面的验证。
使用方法
将以下内容添加到您的 Cargo.toml
中(无特性)
[dev-dependencies]
clearcheck = { version = "0.0.2" }
将以下内容添加到您的 Cargo.toml
中(所有特性)
[dev-dependencies]
clearcheck = { version = "0.0.2", features = ["num", "date", "regex", "file"] }
chrono = { version = "0.4.31" }
num = { version = "0.4.1" }
regex = { version = "1.10.2" }
walkdir = { version = "2.4.0", features = [] }
断言与匹配器
断言是测试用例的基石,定义了代码必须满足的确切期望。它们充当合同,确保每个数据类型(/数据结构)都遵循其预期行为。
另一方面,匹配器提供了执行这些断言的细粒度工具。它们检查数据并验证数据是否符合特定标准。
本质上,断言协调高级验证逻辑,而匹配器作为代码级别的检查器,确保每个细节都与预期相符。
支持的断言
布尔类型断言
断言
断言 | 描述 |
---|---|
should_be_true | 断言布尔值评估为 true。 |
should_be_false | 断言布尔值评估为 false。 |
使用方法
let value = true;
value.should_be_true();
字符类型断言
断言
断言 | 描述 |
---|---|
should_be_in_inclusive_range | 断言字符位于给定的包含范围内。 |
should_not_be_in_inclusive_range | 断言字符不在给定的包含范围内。 |
should_be_in_exclusive_range | 断言字符在给定的排除范围内。 |
should_not_be_in_exclusive_range | 断言字符不在给定的排除范围内。 |
should_be_equal_ignoring_case | 断言字符等于其他字符,忽略大小写。 |
should_not_be_equal_ignoring_case | 断言字符不等于其他字符,忽略大小写。 |
使用方法
let letter = 'd';
letter.should_be_in_inclusive_range('a'..='d');
let letter = 'D';
letter.should_be_equal_ignoring_case('d');
集合(Vector、数组、切片)
断言
断言 | 描述 |
---|---|
should_have_upper_bound | 断言集合中的所有元素都小于或等于给定元素。 |
should_have_lower_bound | 断言集合中的所有元素都大于或等于给定元素。 |
should_contain_duplicates | 断言集合中至少有一个重复元素。 |
should_not_contain_duplicates | 断言集合中不包含任何重复元素。 |
should_be_equal_ignoring_case | 断言集合中的元素等于其他集合中的元素,忽略大小写差异。(仅适用于元素可以表示为字符串的情况) |
should_not_be_equal_ignoring_case | 断言集合中的元素不等于其他集合中的元素,忽略大小写差异。(仅适用于元素可以表示为字符串的情况) |
should_be_monotonically_increasing | 断言集合中的元素是非递减顺序(允许连续相等的元素)。 |
should_be_monotonically_decreasing | 断言集合中的元素是非递增顺序(允许连续相等的元素)。 |
should_be_strictly_increasing | 断言集合中的元素是严格递增顺序(不允许连续元素相等)。 |
should_be_strictly_decreasing | 断言集合中的元素是严格递减顺序(不允许连续元素相等)。 |
should_contain | 断言集合包含给定元素。 |
should_not_contain | 断言集合不包含给定元素。 |
should_contain_all | 断言集合包含所有给定元素。 |
should_not_contain_all | 断言集合不包含所有给定元素。 |
should_contain_any | 断言集合包含给定元素中的任何一个。 |
should_not_contain_any | 断言集合不包含给定元素中的任何一个。 |
should_be_empty | 断言集合为空。 |
should_not_be_empty | 断言集合不为空。 |
基于大小的断言
断言 | 描述 |
---|---|
should_have_size | 断言底层集合的大小正好是给定的大小。 |
should_not_have_size | 断言底层集合的大小不是给定的大小。 |
should_have_at_least_size | 断言底层集合的大小大于或等于给定的大小。 |
should_have_at_most_size | 断言底层集合的大小小于或等于给定的大小。 |
should_be_same_size_as | 断言底层集合的大小与给定集合的大小相同。 |
should_have_size_in_inclusive_range | 断言底层集合的大小在给定的包含范围内。 |
should_not_have_size_in_inclusive_range | 断言底层集合的大小不在给定的包含范围内。 |
should_have_size_in_exclusive_range | 断言底层集合的大小在给定的排除范围内。 |
should_not_have_size_in_exclusive_range | 断言底层集合的大小不在给定的排除范围内。 |
使用方法
let keywords = ["testing", "automation", "clearcheck", "junit"];
keywords.should_not_be_empty()
.should_have_size_in_inclusive_range(4..=10)
.should_not_contain_duplicates()
.should_contain_any(vec!["junit", "clearcheck", "testing"])
.should_not_contain_any(vec!["scalatest", "gotest"]);
日期(由 'date' 功能启用,依赖于 chrono)
断言
断言 | 描述 |
---|---|
should_have_same_year_as | 断言日期与另一个日期具有相同的年份。 |
should_not_have_same_year_as | 断言日期与其他日期不具有相同的年份。 |
should_have_year | 断言日期与给定年份具有相同的年份。 |
should_not_have_year | 断言日期与给定年份不具有相同的年份。 |
should_have_same_month_as | 断言日期与其他日期具有相同的月份。 |
should_not_have_same_month_as | 断言日期与其他日期不具有相同的月份。 |
should_have_month | 断言日期与给定月份具有相同的月份。 |
should_not_have_month | 断言日期与给定月份不具有相同的月份。 |
should_have_same_day_as | 断言日期与其他日期具有相同的日期。 |
should_not_have_same_day_as | 断言日期与其他日期不具有相同的日期。 |
should_have_day | 断言日期与给定日期具有相同的日期。 |
should_not_have_day | 断言日期与给定日期不具有相同的日期。 |
should_be_a_leap_year | 断言日期在闰年。 |
should_not_be_a_leap_year | 断言日期不在闰年。 |
使用方法
use chrono::NaiveDate;
let date = NaiveDate::from_ymd_opt(2024, 1, 10).unwrap();
date
.should_be_a_leap_year()
.should_have_month(1)
.should_be_greater_than(&NaiveDate::from_ymd_opt(2023, 1, 10).unwrap());
文件路径(由 'file' 功能启用,依赖于 walkdir)
断言
断言 | 描述 |
---|---|
should_be_a_directory | 断言路径是目录。 |
should_be_a_file | 断言路径是文件。 |
should_be_a_symbolic_link | 断言路径是符号链接。 |
should_be_zero_sized | 断言路径对应于零大小文件。 |
should_not_be_zero_sized | 断言路径对应于非零大小文件。 |
should_be_readonly | 断言路径对应于只读文件。 |
should_be_writable | 断言路径对应于可写文件。 |
should_be_absolute | 断言路径是绝对路径。 |
should_be_relative | 断言路径是相对路径。 |
should_have_extension | 断言路径对应于具有给定扩展名的文件。 |
should_not_have_extension | 断言路径对应于没有给定扩展名的文件。 |
should_contain_file_name | 断言路径对应于包含给定文件名的目录。 |
should_not_contain_file_name | 断言路径对应于不包含给定文件名的目录。 |
should_contain_all_file_names | 断言路径对应于包含所有给定文件名的目录。 |
should_not_contain_all_file_names | 断言路径对应于不包含所有给定文件名的目录。 |
should_contain_any_of_file_names | 断言路径对应于包含任何给定文件名的目录。 |
should_not_contain_any_of_file_names | 断言路径对应于不包含任何给定文件名的目录。 |
使用方法
use tempdir::TempDir;
let temporary_directory = TempDir::new(".").unwrap();
let file_path_junit = temporary_directory.path().join("junit.txt");
let file_path_clearcheck = temporary_directory.path().join("clearcheck.txt");
let _ = File::create(file_path_junit).unwrap();
let _ = File::create(file_path_clearcheck).unwrap();
let directory_path = temporary_directory.path();
directory_path
.should_be_a_directory()
.should_contain_any_of_file_names(vec!["junit.txt", "clearcheck.txt"]);
浮点数(由 'num' 功能启用,依赖于 num)
断言
断言 | 描述 |
---|---|
should_be_nan | 断言浮点值是 NaN(不是一个数)。 |
should_not_be_nan | 断言浮点值不是 NaN(不是一个数)。 |
should_be_zero | 断言浮点值是零。 |
should_not_be_zero | 断言浮点值不是零。 |
should_be_positive | 断言浮点值是正数。 |
should_be_negative | 断言浮点值是负数。 |
should_be_in_inclusive_range_with_tolerance | 断言浮点值在给定包含范围内,并具有容差。 |
should_not_be_in_inclusive_range_with_tolerance | 断言浮点值不在给定包含范围内,并具有容差。 |
should_be_in_exclusive_range_with_tolerance | 断言浮点值在给定排除范围内,并具有容差。 |
should_not_be_in_exclusive_range_with_tolerance | 断言浮点值不在给定的容忍度范围内的排他区间内。 |
使用方法
let value: f64 = 1.34589;
value
.should_not_be_nan()
.should_be_positive()
.should_be_in_inclusive_range_with_tolerance(1.11..=1.3458, 0.23);
整数(由'num'功能启用,取决于num)
断言
断言 | 描述 |
---|---|
should_be_positive | 断言整数值为正。 |
should_be_negative | 断言整数值为负。 |
should_be_even | 断言整数值为偶数。 |
should_be_odd | 断言整数值为奇数。 |
should_be_zero | 断言整数值为零。 |
should_not_be_zero | 断言整数值不为零。 |
使用方法
let value = 24;
value
.should_be_positive()
.should_be_even()
.should_be_in_inclusive_range(10..=40);
HashMap 类型断言
断言
断言 | 描述 |
---|---|
should_contain_key | 断言HashMap包含给定的键。 |
should_not_contain_key | 断言HashMap不包含给定的键。 |
should_contain_all_keys | 断言HashMap包含所有给定的键。 |
should_not_contain_all_keys | 断言HashMap不包含所有给定的键。 |
should_contain_any_of_keys | 断言HashMap包含给定的任一键。 |
should_not_contain_any_of_keys | 断言HashMap不包含给定的任一键。 |
should_contain_value | 断言HashMap包含给定的值。 |
should_not_contain_value | 断言HashMap不包含给定的值。 |
should_contain_all_values | 断言HashMap包含所有给定的值。 |
should_not_contain_all_values | 断言HashMap不包含所有给定的值。 |
should_contain_any_of_values | 断言HashMap包含给定的任一值。 |
should_not_contain_any_of_values | 断言HashMap不包含给定的任一值。 |
should_contain | 断言HashMap包含给定的键和值。 |
should_not_contain | 断言HashMap不包含给定的键和值。 |
should_contain_all | 断言HashMap包含给定HashMap的所有条目。 |
should_not_contain_all | 断言HashMap不包含给定HashMap的所有条目。 |
should_contain_any | 断言HashMap包含给定HashMap的任一条目。 |
should_not_contain_any | 断言HashMap不包含给定HashMap的任一条目。 |
should_be_empty | 断言HashMap为空。 |
should_not_be_empty | 断言HashMap不为空。 |
+ | 基于大小的断言. |
使用方法
#[derive(Eq, Debug, PartialEq, Hash)]
struct Book {
id: usize,
title: &'static str,
}
impl Book {
fn new(id: usize, title: &'static str) -> Self {
Book { id, title }
}
}
let mut book_id_by_name = HashMap::new();
book_id_by_name.insert("Database internals", 1);
book_id_by_name.insert("Designing data intensive applications", 2);
book_id_by_name
.should_not_be_empty()
.should_contain_key("Database internals")
.should_contain_value(&1)
.should_have_at_least_size(2)
.should_contain("Database internals", &1);
Option 类型断言
断言
断言 | 描述 |
---|---|
should_be_some | 断言Option评估为Some。 |
should_be_none | 断言Option评估为None。 |
使用方法
let option = Some("clearcheck");
option.should_be_some();
Result 类型断言
断言
断言 | 描述 |
---|---|
should_be_ok | 断言Result评估为Ok。 |
should_be_err | Result评估为Err。 |
使用方法
let value: Result<i32, &str> = Ok(32);
value.should_be_ok();
T: PartialOrd
断言
断言 | 描述 |
---|---|
should_be_greater_than | 断言self值根据PartialOrd实现大于给定的值(other)。 |
should_be_greater_than_equal_to | 断言self值根据PartialOrd实现大于或等于给定的值(other)。 |
should_be_less_than | 断言self值根据PartialOrd实现小于给定的值(other)。 |
should_be_less_than_equal_to | 断言self值根据PartialOrd实现小于或等于给定的值(other)。 |
should_not_be_greater_than | 断言self值根据PartialOrd实现不大于给定的值(other)。 |
should_not_be_greater_than_equal_to | 断言self值根据PartialOrd实现不大于或等于给定的值(other)。 |
should_not_be_less_than | 断言self值根据PartialOrd实现不小于给定的值(other)。 |
should_not_be_less_than_equal_to | 断言self值根据PartialOrd实现不小于或等于给定的值(other)。 |
should_be_in_inclusive_range | 断言self值在给定的包含范围内。 |
should_not_be_in_inclusive_range | 断言self值不在给定的包含范围内。 |
should_be_in_exclusive_range | 断言self值在给定的排他范围内。 |
should_not_be_in_exclusive_range | 断言self的值不在给定的排他性范围内。 |
使用方法
let value = 12.56;
value
.should_be_greater_than(&10.90)
.should_be_less_than(&15.98)
.should_be_in_inclusive_range(10.90..=13.10);
T: Eq
断言
断言 | 描述 |
---|---|
should_equal | 断言self持有的值等于other。 |
should_not_equal | 断言self持有的值不等于other。 |
使用方法
#[derive(Debug, Eq, PartialEq)]
struct Book { name: &'static str }
let books = vec![
Book {name: "Database internals"},
Book {name: "Rust in action"}
];
let other = vec![
Book {name: "Database internals"},
Book {name: "Rust in action"}
];
books.should_equal(&other);
字符串类型断言
断言
断言 | 描述 |
---|---|
should_begin_with | 断言字符串以给定的前缀开头。 |
should_not_begin_with | 断言字符串不以给定的前缀开头。 |
should_end_with | 断言字符串以给定的后缀结尾。 |
should_not_end_with | 断言字符串不以给定的后缀结尾。 |
should_be_lower_case | 断言字符串是小写。 |
should_be_upper_case | 断言字符串是大写。 |
should_be_equal_ignoring_case | 断言字符串等于其他字符串,忽略大小写。 |
should_not_be_equal_ignoring_case | 断言字符串不等于其他字符串,忽略大小写。 |
should_only_contain_digits | 断言字符串只包含数字。 |
should_contain_a_digit | 断言字符串包含一个数字。 |
should_not_contain_digits | 断言字符串不包含任何数字。 |
should_contain_character | 断言字符串包含给定的字符。 |
should_not_contain_character | 断言字符串不包含给定的字符。 |
should_contain_all_characters | 断言字符串包含所有给定的字符。 |
should_not_contain_all_characters | 断言字符串不包含所有给定的字符。 |
should_contain_any_characters | 断言字符串包含给定的任何字符。 |
should_not_contain_any_characters | 断言字符串不包含给定的任何字符。 |
should_contain | 断言字符串包含给定的子字符串。 |
should_not_contain | 断言字符串不包含给定的子字符串。 |
should_contain_ignoring_case | 断言字符串包含子字符串,忽略大小写差异。 |
should_not_contain_ignoring_case | 断言字符串不包含子字符串,忽略大小写差异。 |
should_be_empty | 断言字符串为空(零字符)。 |
should_not_be_empty | 断言字符串不为空。 |
should_be_numeric | 断言字符串是数值的。 |
should_not_be_numeric | 断言字符串不是数值的。 |
should_match | 断言字符串与给定的正则表达式匹配。(由 'regex' 功能启用,取决于regex) |
should_not_match | 断言字符串不与给定的正则表达式匹配。(由 'regex' 功能启用,取决于regex) |
基于长度的断言
断言 | 描述 |
---|---|
should_have_length | 断言字符串的长度正好是给定的长度。 |
should_not_have_length | 断言字符串的长度不是给定的长度。 |
should_have_at_least_length | 断言字符串的长度大于或等于给定的长度。 |
should_have_at_most_length | 断言字符串的长度小于或等于给定的长度。 |
should_have_length_in_inclusive_range | 断言字符串的长度在给定的包含范围内。 |
should_not_have_length_in_inclusive_range | 断言字符串的长度不在给定的包含范围内。 |
should_have_length_in_exclusive_range | 断言字符串的长度在给定的排他性范围内。 |
should_not_have_length_in_exclusive_range | 断言字符串的长度不在给定的排他性范围内。 |
使用方法
let pass_phrase = "P@@sw0rd1 zebra alpha";
pass_phrase.should_not_be_empty()
.should_have_at_least_length(10)
.should_contain_all_characters(vec!['@', ' '])
.should_contain_a_digit()
.should_not_contain_ignoring_case("pass")
.should_not_contain_ignoring_case("word");
变更日志
版本 0.0.2
版本0.0.2重构了字符串断言以消除重复,并引入了以下断言
集合(基于谓词的断言)
断言 | 描述 |
---|---|
should_satisfy_for_all | 断言集合中所有元素都满足给定的谓词。 |
should_not_satisfy_for_all | 断言集合中并非所有元素都满足给定的谓词。 |
should_satisfy_for_any | 断言集合中的任何元素都满足给定的谓词。 |
should_not_satisfy_for_any | 断言集合中没有任何元素满足给定的谓词。 |
集合(基于最小-最大值的断言)
断言 | 描述 |
---|---|
should_have_min | 断言底层集合中的最小值等于给定的最小值。 |
should_not_have_min | 断言底层集合中的最小值不等于给定的最小值。 |
should_have_max | 断言底层集合中的最大值等于给定的最大值。 |
should_not_have_max | 断言底层集合中的最大值不等于给定的最大值。 |
should_have_min_in_inclusive_range | 断言底层集合中的最小值在给定的包含范围内。 |
should_not_have_min_in_inclusive_range | 断言底层集合中的最小值不在给定的包含范围内。 |
should_have_min_in_exclusive_range | 断言底层集合中的最小值在给定的不包含范围内。 |
should_not_have_min_in_exclusive_range | 断言底层集合中的最小值不在给定的不包含范围内。 |
should_have_max_in_inclusive_range | 断言底层集合中的最大值在给定的包含范围内。 |
should_not_have_max_in_inclusive_range | 断言底层集合中的最大值不在给定的包含范围内。 |
should_have_max_in_exclusive_range | 断言底层集合中的最大值在给定的不包含范围内。 |
should_not_have_max_in_exclusive_range | 断言底层集合中的最大值不在给定的不包含范围内。 |
选项(基于谓词的断言)
断言 | 描述 |
---|---|
should_be_some_and_satisfy | 断言Option值是Some并且满足给定的谓词。 |
should_be_some_and_not_satisfy | 断言Option值是Some但不满足给定的谓词。 |
结果(基于谓词的断言)
断言 | 描述 |
---|---|
should_be_ok_and_satisfy | 断言Result值是Ok并且满足给定的谓词。 |
should_be_ok_and_not_satisfy | 断言Result值是Ok但不满足给定的谓词。 |
释放自定义匹配器和断言的威力
虽然这个crate(软件包)包含了许多现成的断言,但有时您的测试需求可能需要定制的解决方案。"clearcheck"允许您创建自己的自定义匹配器和断言!
可能性是无限的
- 领域特定验证:创建理解您业务逻辑细微差别的断言。
- 增强可读性:编写清晰简洁的匹配器,使其反映领域词汇,使您的测试具有自文档性和可理解性。
- 减少冗余:通过封装在可重用匹配器中的复杂验证逻辑来消除重复代码。
让我们创建一个具有特定标准(如长度、数字和禁止模式)的自定义密码匹配器。
fn be_a_valid_password<T: AsRef<str> + Debug>() -> Matchers<T> {
MatchersBuilder::start_building_with_inverted(be_empty().boxed())
.push(have_atleast_same_length(10).boxed())
.push(contain_a_digit().boxed())
.push(contain_any_of_characters(vec!['@', '#']).boxed())
.push_inverted(begin_with("pass").boxed())
.push_inverted(contain_ignoring_case("pass").boxed())
.push_inverted(contain_ignoring_case("word").boxed())
.combine_as_and()
}
匹配器强制以下条件
- 输入不能为空。
- 输入必须至少有10个字符长度。
- 输入必须至少包含一个数字。
- 输入必须包含以下字符之一:'@','#'。
- 输入不能以字符串"pass"开头(不区分大小写)。
- 输入不能包含字符串"pass"或"word"(不区分大小写)。
让我们将其组合成一个用于有效密码的强大断言。
trait PasswordAssertion {
fn should_be_a_valid_password(&self) -> &Self;
}
impl PasswordAssertion for &str {
fn should_be_a_valid_password(&self) -> &Self {
self.should(&be_a_valid_password());
self
}
}
现在是时候使用我们的密码断言了。
#[test]
fn should_be_a_valid_password() {
let password = "P@@sw0rd9082";
password.should_be_a_valid_password();
}
让我们为密码的有效性添加一个额外的条件。它必须大于字符串"pass"。这意味着,一个密码有效如果
- 它满足之前的匹配器条件
- 它大于字符串"pass"
看起来我们可以在现有的be_a_valid_password
方法中组合一个OrderedMatcher
。
匹配器兼容性背后的关键思想是,只有当匹配器可以处理相同的数据类型时,它们才能组合。
在这种情况下,OrderedMatcher
可以处理任何部分有序类型(T: PartialOrd)
,而be_a_valid_password
中的其他匹配器与字符串切片(&str)一起工作。
为了解决这个问题,可以创建一个单独的匹配器be_greater_than_pass
来处理“大于'pass'”条件。此匹配器与字符串值一起工作,确保兼容性。
fn be_greater_than_pass() -> Matchers<String> {
MatchersBuilder::start_building(be_greater_than(String::from("pass")).boxed())
.push_inverted(be_empty().boxed())
.combine_as_and()
}
它返回一个Matchers<String>
实例,因此现在为String
实现了PasswordAssertion
。现在,方法should_be_a_valid_password
使用组合的匹配器来断言密码是否有效。
impl PasswordAssertion for String {
fn should_be_a_valid_password(&self) -> &Self {
self.should(&be_a_valid_password());
self.should(&be_greater_than_pass());
self
}
}
#[test]
fn should_be_a_valid_password() {
let password = "P@@sw0rd9082".to_string();
password.should_be_a_valid_password();
}
Rust 特性
clearcheck crate支持以下功能
- date启用日期断言
- file启用文件路径断言
- num启用浮点断言和整数断言
- regex启用字符串正则表达式断言
示例项目
访问示例项目了解此crate的用法。
参考
提及
- Google bard帮助编写文档和README :).
依赖项
~0–8MB
~52K SLoC