昨天我们学完了生命周期,那么第十章就学完了
接下来是第十一章
有个点得写在最前:不要相信和你对接的测试,即使再牛逼也不要完全信赖测试。(当然,如果你的测试能包揽你的工作并且非常出色,那就当我没说,因为他可能甚至把你的代码也看了)。
相信你也用过一些测试相关的插件。
以前端为例,比如jest
之类的。
怎么说呢?如果是业务逻辑相关的,那就有些鸡肋了。比较可能涉及到了多逻辑嵌套等问题,写一堆代码可能效率还不如手动去浏览器上点击。
但如果你是在写插件,在写框架,那就完全不同了。毕竟你可能入口都还没写,如果可以一小块一小块的测试就会很舒服,效率就会比较高。
而在rust
中,rust
自身是非常看重correctness
也就是正确性的,也可说是安全。
因为不容易报错出问题和安全的意思差不多。这里面type system
也就是类型系统承担了相当大的一部分负担(也就是对correctness
来说起了很大作用)。
但是它也有做不到的地方,这一点任何语言都一样。
举个栗子:我们准备实现一个函数add_two
,它接收一个整数并返回整数加二之后的结果。
这块代码肯定是不会报错的,但是结果是错的,我们想要的是2 + 2
,而实际结果是2 + 3
。
这就是rust
做不到的地方,它的编译器能帮你确认输入输出的类型,确认生命周期是否正常,但是就是不能帮你看你的代码是怎么写的,你写成什么样子,它不知道,它也不想知道。
简单地说,如果错误来自业务逻辑,那么不好意思,请你自己反省为什么会写出这种代码。
所以为了让你写出来的代码不会丢rust
的脸, rust
提供了对软件自动化测试的支持。你可以用它来测试你的代码逻辑。
如果你接触过单元测试等,你应该会发现它们通常都有以下三种特点
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!
你之前应该看到过,eq
是equal
的缩写。那么ne
自然就是no equal
的缩写。
他俩和assert!
是差不多的。
来看下assert_eq
的源码,可以看到接收left
和right
两个参数,其实就是比对这两者的数据是否一样。
直接来看下例子
结果自然是符合预期的2 + 2 == 4
。
ok
的场景不看了,我们来看下fail
的。
assertion failed: (left == right)
。俩参数的结果也有给出,很方便找出问题。
而assert_ne!
我们就不看了,和assert_eq!
相反。
有个点需要注意,那就是两个数据要能对比,同时还要能被调试输出,那么就意味着它俩得实现PartialEq
和Debug
这俩trait
才行。
不过好在大多是标准库里的类型都有impl
这俩trait
。
不过enum
和struct
你得自己去impl
才行,之前讲过了,它俩压根不知道是什么格式,数据可以各种类型,也就是无法确定每一个enum/struct
格式能长得一样,所以rust
并不能给它们impl
这个Debug
和PartialEq
。
不过,根据我们之前使用Debug
的用法,我们可以在struct
顶部注释一个derive
的属性将他俩derive
(只有derivable
的才能被derive
)进入当前上下文。
我们直接来看下例子
可以看到assert!
接收了三个参数,我们来跑下,期望是failed
可以看到我们传入的第二个第三个参数被输出了。
实际上它们被format!
了,这三个assert
方法默认都是把后面几个参数给format!
,然后作为错误提示语句。
当然,后面的参数有多少个都是可以的,但是这里面的第一个参数必须要是一个带有{}
的字符串,这样才能format!
。 有几个参数就来几个{}
。
should_panic
来检查panics
还记得我们前几天学的“啥时候需要用panic
”吗?
在test
中我们需要特殊处理,为什么呢?
因为有时候panic
就是你期望的结果,这样就有冲突了。
来看下例子
我们在greater_than_100
的fn
前面注释了#[should_panic]
,然后我们调用new
方法,但是数据不符合1-100
,按理说是会panic
。
这是非测试阶段正常调用的结果,符合我们的结果。
但是,在test
中,报错才是我们的expect
,所以应该是ok
的状态才行。
现在我们加上#[should_panic]
之后
这回符合预期了。
表示对错,大家应该想到了更加优雅的Result
,在test
中也是支持的。
现在我们来试下
比起assert_eq!
传入参数的方式,通过Err
变体传入错误语句会方便很多。当然,你还可以用更方便的?
操作符,这样就更优雅了。
不过有一点需要注意,那就是如果使用了Result
,那就不能使用#[should_panic]
。还有就是Result
不能作为返回值,也就是返回语句里不能使用?
或者手动return
一个Result
。
当然,你可以使用assert!(res.is_err())
代替,is_err
会返回一个布尔值。
今天有点啰嗦,如果你之前学过自动化测试等,那么这一章完全没有难度。
最后,如果这篇文章对你有帮助,请务必点个赞,谢谢·~
发布于 2022-12-18 22:52・IP 属地广东