1 个不稳定版本
0.1.0 | 2023年7月15日 |
---|
#15 在 #concrete
在 tested-trait 中使用
27KB
492 行(不包括注释)
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}>
,但将类型转换为有效标识符是困难的 - 针对无大小类型进行特性实现测试
- 支持使用
quickcheck
和proptest
进行基于属性的测试 -
#![no_std]
支持:此包本身是#![no-std]
,但定义的测试需要std::println!
和 [std::panic::catch_unwind()
]
测试泛型实现
特性泛型实现为它们的泛型参数的每个实例生成 具体实现。测试所有这些实现是不可能的,因此使用 #[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
的比较
该软件包提供了类似于 trait_tests
软件包的功能,但有以下显著区别
trait_tests
在单独的FooTests
特性中定义测试,而此软件包在特性定义中直接定义它们trait_tests
允许对FooTests
特性设置界限,而此软件包允许在测试函数本身上设置它们trait_tests
将测试定义为未标记的关联函数,而此软件包支持标准的#[test]
语法及其带来的便利- 根据我的测试,此软件包的宏在处理不同输入时比
trait_tests
的宏更卫生、更健壮
许可证:MIT OR Apache-2.0
依赖项
~0.5–1MB
~20K SLoC