1个不稳定版本
0.1.0 | 2023年7月15日 |
---|
#2345 in Rust模式
23KB
128 行
tested-trait
tested-trait
提供了两个宏 -- tested_trait
和 test_impl
-- 可以在特性定义中包含关联测试,并实例化关联测试以测试特性的实现。
示例
考虑一个内存分配器特性,如 GlobalAlloc
。
alloc
方法接受一个描述大小和对齐要求的 Layout
,并返回一个指针 -- 返回的指针应该遵循布局描述,但没有任何东西强制执行此协议。
通过使用 tested_trait
宏注释特性定义,可以将测试与特性关联起来,以验证分配结果是否产生有效对齐的指针 -- 至少对于一系列简单的分配而言。
use std::alloc::Layout;
#[tested_trait]
trait Allocator {
unsafe fn alloc(&mut self, layout: Layout) -> *mut u8;
#[test]
fn alloc_respects_alignment() where Self: Default {
let mut alloc = Self::default();
let layout = Layout::from_size_align(10, 4).unwrap();
for _ in 0..10 {
let ptr = unsafe { alloc.alloc(layout) };
assert_eq!(ptr.align_offset(layout.align()), 0);
}
}
}
注意测试的 where Self: Default
约束,它使用它来构建一个分配器。与独立的测试不同,关联测试可以具有 where
子句,以要求额外的测试功能。
实现者可以使用 test_impl
验证其分配器是否通过这些测试以及与特性关联的任何其他测试。例如,我们可以测试默认的系统分配器
use std::alloc;
#[test_impl]
impl Allocator for alloc::System {
unsafe fn alloc(&mut self, layout: Layout) -> *mut u8 {
alloc::GlobalAlloc::alloc(self, layout)
}
}
... 以及一个忽略对齐的错误的分配器
struct BadAllocator<const SIZE: usize> {
buf: Box<[u8; SIZE]>,
next: usize,
}
// Note the `BadAllocator<1024>: Allocator` argument here -- the implementation is generic,
// so we use it to specify which concrete implementation should be tested.
#[test_impl(BadAllocator<1024>: Allocator)]
impl<const SIZE: usize> Allocator for BadAllocator<SIZE> {
unsafe fn alloc(&mut self, layout: Layout) -> *mut u8 {
if self.next + layout.size() <= self.buf.len() {
let ptr = &mut self.buf[self.next] as *mut u8;
self.next += layout.size();
ptr
} else {
core::ptr::null_mut()
}
}
}
// Implement Default since the associated tests require it -- if this implementation
// is omitted, the #[test_impl] attribute will emit a compilation error.
impl<const SIZE: usize> Default for BadAllocator<SIZE> {
fn default() -> Self {
Self { buf: Box::new([0; SIZE]), next: 0 }
}
}
特性
- 将测试与特性定义关联
- 针对非泛型特性实现和泛型实现的具体实例运行关联测试(见 下面)
- 大多数标准
#[test]
语法(见下文) - 生成的测试的可理解名称:目前,使用
test_impl
标注impl<T> Foo<T> for Bar<T>
生成名为tested_trait_test_impl_Foo_{N}
的测试 -- 理想情况下,它们的名称应该是tested_trait_test_impl_Foo<{T}>_for_Bar<{T}>
,但将类型转换为有效的标识符很困难 - 测试非大小类型实现的 trait
- 支持使用
quickcheck
和proptest
进行基于属性的测试 -
#![no_std]
支持:这个 crate 本身是#![no-std]
,但定义的测试需要std::println!
和 [std::panic::catch_unwind()
]
测试泛型实现
trait 的泛型实现为它们的泛型参数的每个实例生成 具体实现。由于无法测试所有这些实现,因此仅使用 #[test_impl]
标注泛型实现会导致编译失败
# use tested_trait::{tested_trait, test_impl};
#[tested_trait]
trait Wrapper<T> {
fn wrap(value: T) -> Self;
fn unwrap(self) -> T;
#[test]
fn wrap_then_unwrap() where T: Default + PartialEq + Clone {
let value = T::default();
assert!(Self::wrap(value.clone()).unwrap() == value);
}
}
#[test_impl]
impl<T> Wrapper<T> for Option<T> {
fn wrap(value: T) -> Self {
Some(value)
}
fn unwrap(self) -> T {
self.unwrap()
}
}
为了测试此类实现,请向 test_impl
传递非空的 Type: Trait
参数列表,以指定要测试的具体实现
#[test_impl(Option<u32>: Wrapper<u32>, Option<String>: Wrapper<String>)]
impl<T> Wrapper<T> for Option<T> {
fn wrap(value: T) -> Self {
Some(value)
}
fn unwrap(self) -> T {
self.unwrap()
}
}
支持的 #[test]
语法
大多数标准 #[test]
语法是受支持的
#[tested_trait]
trait Foo {
#[test]
fn standard_test() {}
#[test]
fn result_returning_test() -> Result<(), String> {
Ok(())
}
#[test]
#[should_panic]
fn should_panic_test1() {
panic!()
}
#[test]
#[should_panic = "ahhh"]
fn should_panic_test2() {
panic!("ahhhhh")
}
#[test]
#[should_panic(expected = "ahhh")]
fn should_panic_test3() {
panic!("ahhhhh")
}
}
#[test_impl]
impl Foo for () {}
与 trait_tests
的比较
此 crate 提供与 trait_tests
crate 类似的功能,但有以下显著差异
trait_tests
在单独的FooTests
trait 中定义测试,而此 crate 则在 trait 定义中内联定义它们trait_tests
允许对FooTests
特性施加限制,而本库允许对测试函数本身施加限制trait_tests
将测试定义为未标记的关联函数,而本库支持标准的#[test]
语法及其附带的美观性- 根据我的测试,本库的宏比
trait_tests
的宏更卫生且对各种输入更健壮
许可证:MIT 或 Apache-2.0
依赖项
~0.4–0.9MB
~19K SLoC