2 个版本
0.1.1 | 2021 年 1 月 26 日 |
---|---|
0.1.0 | 2020 年 8 月 15 日 |
#1052 in 异步
13KB
spekt
A std::future::Future
和 Result
-based testing trait for managing the lifecycle of stateful, asynchronous tests.
为什么选择 spekt
大多数 Rust 单元测试基于 panic
终止(通常由 assert!
触发),通过手动实现 Drop
特质进行资源清理。与数据库等有状态的资源同步工作可能如下所示
use postgres::{Client, NoTls, Row, error::Error as PostgresError};
struct PostgresTest {
client: Client
}
impl PostgresTest {
fn new() -> Self {
let mut client = Client::connect("host=localhost user=postgres", NoTls).expect("Error connecting to database");
Self { client }
}
fn add_test_table(&self) -> Result<Row, PostgresError> {
self.client.batch_execute("CREATE TABLE my_test_table ()")
}
}
impl Drop for PostgresTest {
fn drop(&mut self) {
self.client.batch_execute("DROP TABLE my_test_table").expect("Error cleaning up test table");
}
}
#[test]
fn adds_queryable_test_table() {
let client = TestClient::new();
let create_response = client.adds_test_table();
assert!(create_response.is_ok(), "Error creating test table");
let query_response = client.query("SELECT FROM my_test_table");
assert!(query_response.is_ok(), "Error creating test table");
}
虽然这对于许多情况都有效,但这个建议存在一些问题
- 技术上,Rust 不 保证
Drop
会被执行,并且不应该依赖Drop
在所有情况下都会被执行。[链接](http://cglab.ca/%7Eabeinges/blah/everyone-poops/) Drop
也不能是异步的!关于 异步析构函数 已有很多讨论,但至今还没有为async
函数出现可靠的析构函数特质。- 基于
panic
的断言(及其关联的展开)的行为可能在运行时是不可预测的。[链接](https://github.com/tokio-rs/tokio/issues/2002)。这是一个特定的测试问题,目前还没有好的通用解决方案。[链接](https://github.com/tokio-rs/tokio/issues/2699) - 此外,虽然
new
和Drop
对于资源来说是有意义的,但这些约定对于更抽象的“测试”概念来说就不那么合理了。在大多数测试框架中,“测试”的概念是初始化在实际测试之前的某些有状态的测试上下文,可以修改自己的上下文的测试用例,以及在实际测试之后运行的某些清理操作。
spekt
通过提供包含 before ->
test ->
after
生命周期、使用 Result
驱动断言的具有状态、异步测试的 Test
特质,避免了所有这些问题。
如何使用
spekt::Test
可以用于任何 Send + Sync
测试状态,实现一个返回 std::future::Future
的 test()
方法。返回的 Future
是运行时无关的,可以通过 .wait()
同步评估,通过每个测试套件的定制运行时(例如 tokio::runtime::Runtime
),或者通过异步测试运行器(例如 tokio::test
)。
使用 spekt::Test
重写上面的示例
use tokio_postgres::{Client, NoTls, Row, error::Error as PostgresError};
use spekt::Test;
struct PostgresTest {
client: Client
}
// spekt optionally re-exports async_trait
#[spekt::async_trait]
impl Test for PostgresTest {
type Error = anyhow::Error; // any Error will do, but anyhow is recommended
async fn before() -> Result<Self, Self::Error> {
let mut client = Client::connect("host=localhost user=postgres", NoTls).await?;
client.batch_execute("CREATE TABLE my_test_table ()").await?;
Ok(Self { client })
}
async fn after(&self) -> Result<(), Self::Error> {
self.client.batch_execute("DROP TABLE my_test_table")?;
Ok(())
}
}
// any executor will do, but tokio::test is recommended
#[tokio::test]
async fn adds_queryable_test_table() {
// PostgresTest::test runs before() first, passes the output of before() to test() as context,
// and finally runs after() regardless of the result of the test run itself,
// bubbling all Self::Errors to top-level test failures
PostgresTest::test(|context| async move {
context.client.query("SELECT FROM my_test_table").await?;
Ok(())
}).await
}
路线图
- 包含用于具有共享上下文的多个
Test
的Suite
抽象 - 处理自定义断言库,例如
rust-pretty-assertions
- 处理 自定义测试框架
依赖项
~280–740KB
~18K SLoC