Assertion macros for Rust

栏目: IT技术 · 发布时间: 6年前

内容简介:I started writing integration tests forFor example, let’s say that we have some function which returnsWe always can test if

I started writing integration tests for heim recently (better late than never) and the testing code started to bloat up really quick; it’s not very easy to check the logic correctness elegantly with what assertion macros we have in the Rust standard library, especially when we are working with the commonly used Result and Option types.

For example, let’s say that we have some function which returns io::Result<i32> and we want to test if it returns Ok(42) as expected:

fn foo() -> io::Result<i32> {
    Err(io::Error::new(io::ErrorKind::Other, "example purposes"))
}

#[test]
fn test_foo() {
    let result = foo();
}

We always can test if Result is Ok with the is_ok method:

assert!(result.is_ok());

// This assert will panic with the following message:
//
// thread 'main' panicked at 'assertion failed: r.is_ok()', examples/foo.rs:10:5

The panic message is not really helpful, because we have no idea what error caused the assertion failure, and in addition we still need to get value from the Ok variant somehow if this one assertion is correct. Maybe we should use .unwrap instead?

let value = result.unwrap();
assert_eq!(value, 42);

// `.unwrap()` will panic too:
//
// thread 'main' panicked at 'called `Result::unwrap()` on an `Err` value: 
// Custom { kind: Other, error: "example purposes" }', examples/foo.rs:11:17

That’s better! Should we shorten this quick test now?

#[test]
fn test_foo() {
	assert_eq!(do_foo().unwrap(), 42);
}

Okay, that kinda does the trick both for Result and Option types.

Can we shorten it a little bit more? Of course, let’s write our own assertion macro!

macro_rules! assert_ok_eq {
    ($cond:expr, $expected:expr) => {
        match $cond {
            Ok(t) => {
                assert_eq!(t, $expected);
            },
            e @ Err(..) => {
                panic!("assertion failed, expected Ok(..), got {:?}", e);
            }
        }
    };
}

It is a pretty simple macro, all it does is a match on the first macro argument and if is Ok(t) , it compares that t with the second macro argument:

#[test]
fn test_foo() {
	assert_ok_eq!(foo(), 42);
}

// # If `foo()` returned an `Err`:
//
// thread 'main' panicked at 'assertion failed, expected Ok(..),
//     got Err(Custom { kind: Other, error: "example purposes" })', examples/foo.rs:8:5
//
// # And if it returned an `Ok(1)`:
//
// thread 'main' panicked at 'assertion failed: `(left == right)`
//     left: `1`,
//     right: `42`', examples/foo.rs:8:5

Amazing, not only we had reduced the visual noise in this line now, but also declared our expectations in the macro name — it should be an Ok variant and its value should be equal to the second argument.

More macros!

Once you have started making more abstractions, it is hard to stop, so I ended up with a separate crate called “ claim ", which provides a lot of assertion macros to supplement what we already have in libstd :

  • for comparison: assert_ge , assert_gt , assert_le , and assert_lt
  • matching : assert_matches
  • Result : assert_ok , assert_err , and assert_ok_eq
  • Option : assert_some , assert_none , and assert_some_eq
  • and Poll : assert_pending , assert_ready , assert_ready_ok , assert_ready_err , and assert_ready_eq

Of course, other crates with the similar purpose already exists, there are assert2 , spectral , more-asserts , totems ,galvanic-assert, a lot of them !

Why I should use this one?

  1. First of all, claim macros are quite conservative: only the most popular cases handled and for each one case there is a separate macro; for example, there is a assert_some_eq!(Some(42), 42) and not the assert_some!(Some(42), value == 42) (because where that value came from?!)

  2. There is no “fluent” approach also, because I personally prefer not to use these half-assed DSLs, where each one is a unique and impossible to remember. I mean, why: it.expected_to_be_okay().and.equal_to(5) ?

  3. Great thing is that current macros system had not changed that much for a last few years, so almost all those macros can be used with any modern enough Rust version; CI checks compatibility back to Rust 1.10 version, but I expect that it should also work with 1.0 version too.

  4. Yet, some types are just not available in older versions, for example, Poll was introduced in Rust 1.36 and macros syntax needed to implement matches! macro for all Rust editions was added in Rust 1.32. With the help of autocfg crate, claim automatically exposes corresponding macros if used Rust compiler can support them, so you don’t even need to enable any features manually!

  5. It does not need std at all, meaning that it can be used in the #![no-std] environments too

  6. And it also has no dependencies (except for a small build one)!

So, it’s a very tiny crate, which makes panic messages a bit better. It is not much, but can be useful in some cases.

Links


以上就是本文的全部内容,希望对大家的学习有所帮助,也希望大家多多支持 码农网

查看所有标签

本站部分资源来源于网络,本站转载出于传递更多信息之目的,版权归原作者或者来源机构所有,如转载稿涉及版权问题,请联系我们

翻转课堂的可汗学院

翻转课堂的可汗学院

萨尔曼·可汗(Salman Khan) / 刘婧 / 浙江人民出版社 / 2014-4-1 / 49.00元

MIT和哈佛毕业的高材生缘何放弃金融分析师工作投身教育事业?YouTube上的“可汗学院频道”至今共吸引了163.3万订阅者,观看次数超过3.55亿次,它为什么如此大受欢迎?创始人萨尔曼·可汗阐述属于未来的教育理念——让地球上的任何人都能随时随地享受世界一流的免费教育! 现行教育模式已有200余年历史,可汗认为,在互联网蓬勃发展、社交网络盛况空前的时代,免费、灵活、适合个体、全球共享的教育才......一起来看看 《翻转课堂的可汗学院》 这本书的介绍吧!

HTML 压缩/解压工具
HTML 压缩/解压工具

在线压缩/解压 HTML 代码

图片转BASE64编码
图片转BASE64编码

在线图片转Base64编码工具

XML、JSON 在线转换
XML、JSON 在线转换

在线XML、JSON转换工具