2 个不稳定版本
0.1.0 | 2021年2月14日 |
---|---|
0.0.0 | 2020年10月13日 |
#357 在 测试 中
每月38 次下载
42KB
844 行
chronobreak: Rust 确定性时间测试模拟库
chronobreak 是一个测试模拟库,用于确定地测试给定测试主题的任何基于时间的属性。
动机
假设我们编写了一个简单的函数,该函数在达到给定的时间点后返回某个值
use std::time::*;
use std::thread;
fn return_at<T>(time: Instant, t: T) -> T {
if Instant::now() < time {
thread::sleep(time.saturating_duration_since(Instant::now()));
}
t
}
现在我们可能想测试这个函数是否真的像预期的那样休眠
#[test]
fn test_return_at() {
let return_time = Instant::now() + Duration::from_secs(1);
return_at(return_time, 0);
assert_eq! {Instant::now(), return_time};
}
这个测试用例很可能会失败。解决这个问题的常见策略是预期时间在某个区间内,而不是比较精确的相等。但这种方法永远不能保证类似的测试用例能够确定性地成功。
chronobreak 来帮忙
那么我们如何确定性地通过测试呢?
首先,为了使模拟时钟按预期工作,对于 chronobreak 提供模拟的每个导入,使用模拟来编译测试非常重要
#[chronobreak]
use std::time::*; // will be replaced with `use chronobreak::mock::std::time::*; for tests
#[chronobreak]
use std::thread;
为了让不故意遗漏任何模拟变得尽可能容易,chronobreak 还重新导出了不需要模拟的受支持库中的所有项目。
现在我们可以通过简单地用 #[test]
替换 #[chronobreak::test]
来使用模拟时钟进行测试
#[chronobreak::test]
fn test_return_at() {
let return_time = Instant::now() + Duration::from_secs(1);
return_at(return_time, 0);
assert_eq! {Instant::now(), return_time};
}
这里发生的事情是,模拟版本的 thread::sleep
将作为原始函数的装饰器。如果当前测试用例的时钟被模拟,它将向前推进整整一秒。如果没有被模拟,thread::sleep
将直接委托给原始函数。
冻结时钟
除了自动前进时钟的默认行为之外,Chronobreak 允许冻结时钟。这导致所有定时等待都将阻塞,直到其他线程通过手动调用 clock::advance
或 clock::advance_to
或在未冻结状态下执行定时等待来前进时钟。
此功能主要旨在与 extended-apis
功能结合使用,该功能将 Thread::expect_timed_wait
和 JoinHandle::expect_timed_wait
添加到这些类的模拟版本的公共 API 中。这些函数使得在恢复之前等待另一个线程进入定时等待成为可能。这对于测试主题是并发数据结构的情况很有用,必须测试当它从一条线程接收输入时,同时它已经在另一条线程上进入了定时等待时,其行为是否正确。
许可协议
在以下许可协议下使用:
- Apache License, Version 2.0 (LICENSE-APACHE 或 https://apache.ac.cn/licenses/LICENSE-2.0)
- MIT 许可协议 (LICENSE-MIT 或 http://opensource.org/licenses/MIT)
任选其一。
贡献
除非你明确说明,否则根据 Apache-2.0 许可证定义的,你提交的任何有意包含在 Chronobreak 中的贡献,将按照上述方式双重许可,不附加任何额外的条款或条件。
依赖项
约 8-17MB
约 243K SLoC