我正在尝试创建一个用于在Rust中测试的断言库。目前,我有类似的声明:
expect(value).to().be().equal_to(4);
将括号放在填充器to
和be
函数上,使其类似于:
expect(value).to.be.equal_to(4);
我认为这要求to
和be
是expect
(Expectation
)返回的结构上的字段。当前看起来像这样:
struct Expectation<V: Debug> {
value: V,
}
是否可以使它像这样:
struct Expectation<V: Debug> {
value: V,
to: Box<Expectation<V>>,
be: Box<Expectation<V>>,
}
to
和be
指向它们所在的结构吗?
我已经尝试过了,但是构建起来很棘手。我什至不确定对象是否移动是安全的(也许可以通过Pin
来防止?)。
我正在寻找任何允许上述expect(value).to.be
语法的解决方案。
答案 0 :(得分:4)
我正在寻找任何允许上述
expect(value).to.be
语法的解决方案。
然后保持简单
fn main() {
expect(4).to.be.equal_to(3);
}
fn expect<T>(actual: T) -> To<T> {
let be = Be {
be: Expectation(actual),
};
To { to: be }
}
struct To<T> {
pub to: Be<T>,
}
struct Be<T> {
pub be: Expectation<T>,
}
struct Expectation<T>(T);
impl<T> Expectation<T> {
fn equal_to<U>(&self, expected: U)
where
U: PartialEq<T>,
{
if expected != self.0 {
panic!("Report error")
}
}
}
(可选)要跳过[
to
和be
]
use std::{ops::Deref, rc::Rc};
fn main() {
expect(4).to.be.equal_to(3);
expect(4).to.equal_to(3);
expect(4).equal_to(3);
}
fn expect<T>(actual: T) -> Expectation<T> {
let core = Core(Rc::new(actual));
let be = Be { core: core.clone() };
let to = To {
be,
core: core.clone(),
};
Expectation {
to,
core: core.clone(),
}
}
struct Expectation<T> {
pub to: To<T>,
core: Core<T>,
}
impl<T> Deref for Expectation<T> {
type Target = Core<T>;
fn deref(&self) -> &Core<T> {
&self.core
}
}
struct To<T> {
pub be: Be<T>,
core: Core<T>,
}
impl<T> Deref for To<T> {
type Target = Core<T>;
fn deref(&self) -> &Core<T> {
&self.core
}
}
struct Be<T> {
core: Core<T>,
}
impl<T> Deref for Be<T> {
type Target = Core<T>;
fn deref(&self) -> &Core<T> {
&self.core
}
}
struct Core<T>(Rc<T>);
impl<T> Clone for Core<T> {
fn clone(&self) -> Self {
Core(self.0.clone())
}
}
impl<T> Core<T> {
fn equal_to<U>(&self, expected: U)
where
U: PartialEq<T>,
{
if expected != *self.0 {
panic!("Report error")
}
}
}
一些宏会减少重复,但是我懒得实际显示;-)
在设计测试断言库时,我会尝试发挥Rust的优势。对我来说,这意味着使用特征可以使人们轻松添加自定义断言。
use crate::testlib::prelude::*;
fn main() {
expect(4).to(be.equal_to(3));
expect(4).to(equal_to(3));
}
mod testlib {
// Shorthand variants that will always be imported.
// Minimize what's in here to avoid name collisions
pub mod prelude {
use super::*;
pub fn expect<A>(actual: A) -> Expectation<A> {
Expectation::new(actual)
}
#[allow(non_upper_case_globals)]
pub static be: Be = Be;
pub fn equal_to<E>(expected: E) -> EqualTo<E> {
EqualTo::new(expected)
}
}
// All the meat of the implementation. Can be divided up nicely.
pub trait Assertion<A> {
fn assert(&self, actual: &A);
}
pub struct Expectation<A>(A);
impl<A> Expectation<A> {
pub fn new(actual: A) -> Self {
Expectation(actual)
}
pub fn to(&self, a: impl Assertion<A>) {
a.assert(&self.0)
}
}
pub struct Be;
impl Be {
pub fn equal_to<E>(&self, expected: E) -> EqualTo<E> {
EqualTo::new(expected)
}
}
pub struct EqualTo<E>(E);
impl<E> EqualTo<E> {
pub fn new(expected: E) -> Self {
EqualTo(expected)
}
}
impl<A, E> Assertion<A> for EqualTo<E>
where
A: PartialEq<E>,
{
fn assert(&self, actual: &A) {
if *actual != self.0 {
panic!("report an error")
}
}
}
}
接下来要研究的步骤:
to_not
和/或not_to
否定匹配符。答案 1 :(得分:2)
我在使用thunk
板条箱懒惰地生成to
和be
方面取得了一些成功:
struct Expectation<V: Debug> {
value: Rc<V>,
to: Thunk<Box<Expectation<V>>>,
be: Thunk<Box<Expectation<V>>>,
}
fn expect<V: Debug>(value: V) -> Expectation<V> {
expect_rc(Rc::new(value))
}
fn expect_rc<V: Debug>(value: Rc<V>) -> Expectation<V> {
let to_cloned = value.clone();
let be_cloned = value.clone();
Expectation {
value,
to: Thunk::defer(|| Box::new(expect_rc(to_cloned))),
be: Thunk::defer(|| Box::new(expect_rc(be_cloned))),
}
}
impl<V: PartialEq + Debug> Expectation<V> {
fn equals<R: Debug>(&self, expected: R)
where
V: PartialEq<R> + Clone,
{
assert_eq!(self.value.deref().clone(), expected);
}
}
由于Deref
+ Deref
conversion的神奇之处,它可以按预期工作:
expect(4).to.be.equal_to(3);
答案 2 :(得分:1)
要设计自定义语法,我只使用一个宏:
macro_rules! expect {
($subject:expr, to, $($attr:tt)*) => {
expect!($subject, $($attr)*)
};
($subject:expr, be, $($attr:tt)*) => {
expect!($subject, $($attr)*)
};
($subject:expr, equal_to $object:expr) => {
assert_eq!($subject, $object)
};
}
expect!(1, to, be, equal_to 1);
仅仅为了获得特定的语法而部署框和自引用结构是过大的。