坏蛋Dan
知乎@坏蛋Dan
发布时间:2024.1.4

前言

昨天我们学完了生命周期,那么第十章就学完了

坏蛋Dan:rust基础学习--day23

接下来是第十一章


编写自动化测试

有个点得写在最前:不要相信和你对接的测试,即使再牛逼也不要完全信赖测试。(当然,如果你的测试能包揽你的工作并且非常出色,那就当我没说,因为他可能甚至把你的代码也看了)。

相信你也用过一些测试相关的插件。

以前端为例,比如jest之类的。

怎么说呢?如果是业务逻辑相关的,那就有些鸡肋了。比较可能涉及到了多逻辑嵌套等问题,写一堆代码可能效率还不如手动去浏览器上点击。

但如果你是在写插件,在写框架,那就完全不同了。毕竟你可能入口都还没写,如果可以一小块一小块的测试就会很舒服,效率就会比较高。

而在rust中,rust自身是非常看重correctness也就是正确性的,也可说是安全。

因为不容易报错出问题和安全的意思差不多。这里面type system也就是类型系统承担了相当大的一部分负担(也就是对correctness来说起了很大作用)。

但是它也有做不到的地方,这一点任何语言都一样。

举个栗子:我们准备实现一个函数add_two,它接收一个整数并返回整数加二之后的结果。

这块代码肯定是不会报错的,但是结果是错的,我们想要的是2 + 2,而实际结果是2 + 3

这就是rust做不到的地方,它的编译器能帮你确认输入输出的类型,确认生命周期是否正常,但是就是不能帮你看你的代码是怎么写的,你写成什么样子,它不知道,它也不想知道。

简单地说,如果错误来自业务逻辑,那么不好意思,请你自己反省为什么会写出这种代码。

所以为了让你写出来的代码不会丢rust的脸, rust提供了对软件自动化测试的支持。你可以用它来测试你的代码逻辑。

如果你接触过单元测试等,你应该会发现它们通常都有以下三种特点

  1. 需要定义测试数据或者状态,包括最终的数据数据/状态。
  2. 它们跑的代码可以是指定的某一块或者某一个函数。
  3. 断言(assert)运行结果是否符合预期。

测试函数

rust中的test也是基于函数function的,只是添加了注释用来区分是否是测试代码。

rust把这种注释叫做attribute,属性是关于rust代码片段的元数据(如果你看我的文章发现有些话拗口压根看不懂,那就说明我在直接翻译。。。),我们之前就遇到过了。比如:

这里的derive就是一个attribute

所以参照dereive,注释一个test用的是#[test],比如下面这个例子

有个点需要注意,那就是必须注释在fn的上面,和struct需要println!时引入dereive放在struct前面一样。

然后我们终端执行cargo test

当我们执行cargo test时,编译器会生成test runner binary,然后执行所有test

输出和其它语言中的很像,最后有个·summary

ok自然表示测试通过,而failed表示测试不通过。

ignored后面我们会遇到,毕竟我们大部分时候只是想测试一个而已。

至于measure..我也不知道是啥,先mark下。

你应该还注意到了一个点Doc-tests,我们并没有包含任何的文档测试(documentation test) ,我们后面会学到。

如果创建的项目是library的话,那么会自动带有test,这也就是我说的在库中使用会更能发挥作用。

我们来创建一个新的项目(day3的一直用到现在。。。)

我们来写个错误的例子

可以看到输出了错误的结果Make this test fail


使用assert!来检查数据

看到!,那就可以说明这是一个macro。这个宏在之后会非常有用,它接收一个布尔值作为参数,当true的时候表示你这个测试单元ok没问题,但是如果你这个参数是false的话,就会调用panic!,将这个测试单元判定为false

我们来看个例子

super我们之前说过的,表示可以使用上一层作用域的struct等。

assert!接收larger.can_hold(&smaller)的返回结果作为参数。

结果为true,测试没问题。

然后我们反过来

期望的结果应该是false

也是符合预期。


assert_eq!assert_ne!

这个assert!虽然方便,但是也并不是所有时候都适用,准确的说是大部分时候都不适用,我们有时候需要调用function,而function的结果往往不会是一个bool,所以这个时候就需要有数据可以比对,这样才能知道结果是否符合预期。

当然,你也可以使用==比较符,但是不如下面这两个方法优雅。

这个assert_eq!你之前应该看到过,eqequal的缩写。那么ne自然就是no equal的缩写。

他俩和assert!是差不多的。

来看下assert_eq的源码,可以看到接收leftright两个参数,其实就是比对这两者的数据是否一样。

直接来看下例子

结果自然是符合预期的2 + 2 == 4

ok的场景不看了,我们来看下fail的。

assertion failed: (left == right)。俩参数的结果也有给出,很方便找出问题。

assert_ne!我们就不看了,和assert_eq!相反。

有个点需要注意,那就是两个数据要能对比,同时还要能被调试输出,那么就意味着它俩得实现PartialEqDebug这俩trait才行。

不过好在大多是标准库里的类型都有impl这俩trait

不过enumstruct你得自己去impl才行,之前讲过了,它俩压根不知道是什么格式,数据可以各种类型,也就是无法确定每一个enum/struct格式能长得一样,所以rust并不能给它们impl这个DebugPartialEq

不过,根据我们之前使用Debug的用法,我们可以在struct顶部注释一个derive的属性将他俩derive(只有derivable的才能被derive)进入当前上下文。


自定义错误语句

我们直接来看下例子

可以看到assert!接收了三个参数,我们来跑下,期望是failed

可以看到我们传入的第二个第三个参数被输出了。

实际上它们被format!了,这三个assert方法默认都是把后面几个参数给format!,然后作为错误提示语句。

当然,后面的参数有多少个都是可以的,但是这里面的第一个参数必须要是一个带有{}的字符串,这样才能format!。 有几个参数就来几个{}


使用should_panic来检查panics

还记得我们前几天学的“啥时候需要用panic”吗?

test中我们需要特殊处理,为什么呢?

因为有时候panic就是你期望的结果,这样就有冲突了。

来看下例子

我们在greater_than_100fn前面注释了#[should_panic],然后我们调用new方法,但是数据不符合1-100,按理说是会panic

这是非测试阶段正常调用的结果,符合我们的结果。

但是,在test中,报错才是我们的expect,所以应该是ok的状态才行。

现在我们加上#[should_panic]之后

这回符合预期了。


在test中使用Result

表示对错,大家应该想到了更加优雅的Result,在test中也是支持的。

现在我们来试下

比起assert_eq!传入参数的方式,通过Err变体传入错误语句会方便很多。当然,你还可以用更方便的?操作符,这样就更优雅了。

不过有一点需要注意,那就是如果使用了Result,那就不能使用#[should_panic] 。还有就是Result不能作为返回值,也就是返回语句里不能使用?或者手动return一个Result

当然,你可以使用assert!(res.is_err())代替,is_err会返回一个布尔值。


总结

今天有点啰嗦,如果你之前学过自动化测试等,那么这一章完全没有难度。

最后,如果这篇文章对你有帮助,请务必点个赞,谢谢·~

参考

  1. ^rust-wiriting-automated-tests https://doc.rust-lang.org/book/ch11-00-testing.html#writing-automated-tests
  2. ^rust-the-anatomy-of-a-test-function https://doc.rust-lang.org/book/ch11-01-writing-tests.html#the-anatomy-of-a-test-function
  3. ^rust-use-assert!-macro-to-check-data https://doc.rust-lang.org/book/ch11-01-writing-tests.html#checking-results-with-the-assert-macro
  4. ^rust-define-custom-failure-messages https://doc.rust-lang.org/book/ch11-01-writing-tests.html#adding-custom-failure-messages
  5. ^rust-checking-panics-with-should_panic https://doc.rust-lang.org/book/ch11-01-writing-tests.html#checking-for-panics-with-should_panic
  6. ^rust-use-Result-in-test https://doc.rust-lang.org/book/ch11-01-writing-tests.html#using-resultt-e-in-tests

发布于 2022-12-18 22:52・IP 属地广东