Rust入坑指南:步步为营
俗话说:“测试写得好,奖金少不了。”
有经验的开发人员通常会通过单元测试来保证代码基本逻辑的正确性。如果你是一名新手开发者,并且还没体会到单元测试的好处,那么建议你先读一下我之前的一篇文章代码洁癖系列(七):单元测试的地位。
写单元测试一般需要三个步骤:
- 准备测试用例,测试用例要能覆盖尽可能多的代码
- 执行需要测试的代码
- 判断结果,是否是你希望得到的结果
了解了这些以后,我们就来看看在Rust中应该怎么写单元测试。
首先我们建立一个library项目
$ cargo new adder --lib
Created library `adder` project
然后在src/lib.rs文件中开始写测试代码
#[cfg(test)]
mod tests {
#[test]
fn it_works() {
assert_eq!(2 + 2, 4);
}
}
此时在命令行运行 cargo test
就会得到测试结果
可以看到,结果显示,Rust运行了一项测试并且测试通过。后面的Doc-tests我们先放下,以后再聊。
当然,这并不是我们常见的测试,在日常开发中,我们通常是先写我们的业务代码然后再对各个函数进行单元测试,最后还会对某个模块进行集成测试。那么我们就来模拟一下日常开发过程中应该如何来写测试。
单元测试
我们仍然是用上面的项目,先来在src/lib.rs中写一段“业务代码”
pub fn add_two(a: i32) -> i32 {
internal_adder(a, 2)
}
fn internal_adder(a: i32, b: i32) -> i32 {
a + b
}
这是一段非常简单的代码,对外暴露的函数只是一个加2的功能,内部调用了一个两数相加的函数。现在我们就对这个内部函数做一个单元测试。
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn internal() {
assert_eq!(4, internal_adder(2, 2));
}
}
在测试模块中,如果想要使用我们业务代码中的函数,就需要通过 use super::*;
将其引入可用范围。接着,还是执行 cargo test
,测试结果与刚才类似。
测了半天全是通过的没什么意思,单元测试真正的作用是要发现代码中的问题,所以我们来尝试一个错误的试一下。假设我们希望2+2等于5。
这里我们的assert_eq!左右不相等,引起了线程恐慌,因此导致测试失败。结果中给出了失败的原因,引起失败的位置,并且有一句提示: note: run with `RUST_BACKTRACE=1` environment variable to display a backtrace.
我们按照这个提示,设置变量RUST_BACKTRACE=1,此时再执行cargo test
。
Rust就会将错误栈打印出来,根据结果提示,这并不是完整的错误栈,我们还可以将RUST_BACKTRACE设置为full来查看更加详细的信息。这里我就不做演示了。
集成测试
接下来我们再演示一下集成测试。我们通常将集成测试单独放到一个目录中,在lib.rs文件中,rust识别测试mod的名称是tests,同样的,我们在src下创建tests目录。tests目录下就是我们的所有集成测试代码。
如图,integration_test是我们测试代码的文件,common目录下的mod.rs文件中是一些集成测试必要的配置。这里我们只是放了一个空的setup函数。
在集成测试中,我们就要像正常他人使用我们的代码时那样来进行测试,首先需要将我们的mod引入到可用范围,当然还需要加上common的mod。
use adder;
mod common;
#[tests]
fn it_adds_two() {
common::setup();
assert_eq!(4, adder::add_two(2));
}
接着就可以测试我们对外暴露的函数了。
ok,集成测试的方法我们也掌握了。现在来看看一直被我们忽略的Doc-tests吧。
文档测试
我们已经知道,Rust中的注释是双斜线//
,像我们刚刚写的library代码,如果想要把它发布到crate.io上让别人使用,那么我们就需要增加相应的文档,这里文档的每行都应该是三斜线///
开头,而文档中也应该放一些例子供他人参考。
/// Adds two to the number given.
///
/// # Examples
///
/// ```
/// let arg = 5;
/// let answer = adder::add_two(arg);
///
/// assert_eq!(7, answer);
/// ```
pub fn add_two(a: i32) -> i32 {
internal_adder(a, 2)
}
现在我给add_two函数加上了文档,我们再次执行 cargo test
命令。
现在我们就明白了,Doc-tests测试就是运行我们文档中的例子。
常用特性
到目前为止,我们已经知道了在Rust中如何写测试代码了。接下来我们再来了解几个比较常用的特性。
运行指定的测试代码
我们在开发过程中肯定不会每次都去跑全量的单元测试,那样太浪费时间了。通常是我们开发完一个功能之后,编写对应的单元测试,然后单独跑这个测试。那么Rust中能不能单独跑一个单元测试呢?答案是肯定的。
相信细心的同学已经发现了,Rust测试结果中,是针对每个测试单独统计结果,并且每个测试都有自己的名字,像我们前面写的it_works
和internal
。假设我们的代码中同时存在这两个函数,如果你想要单独跑internal这一个测试,就可以使用cargo test internal
命令。
你也可以使用这种方法来执行多个名称类似的测试,假如我们有名称为internal_a
的测试,那么执行cargo test internal
命令时它也会被执行。
忽略某个测试
当我们有一个测试执行时间非常长的时候,我们一般不会轻易去执行,这时如果你想要执行多个测试,除了用我们上面提到的方法,去指定不同的名称列表以外。还可以把这个测试忽略掉。
现在我不想执行internal
测试了,只需要对代码进行如下改动:
#[test]
#[ignore]
fn internal() {
assert_eq!(4, internal_adder(2, 2));
}
这时再来运行测试,结果如图所示。
我们发现此时internal
测试已经被忽略了。
测试异常情况
除了测试代码逻辑正常的情况,我们有时还需要测试一些异常情况,比如接收到非法参数时程序能否返回我们希望看到的异常。
我们首先来看一下如何测试程序返回异常信息。
Rust为我们提供了一个叫做should_panic的注解。我们可以使用它来测试程序是否返回异常:
pub fn add_two(a: i32) -> i32 {
internal_adder(a, 2)
}
fn internal_adder(a: i32, b: i32) -> i32 {
if a < 0 {
panic!("a should bigger than 0");
}
a + b
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
#[should_panic]
fn internal() {
assert_eq!(4, internal_adder(-2, 2));
}
}
此时我们运行测试时就会发现internal测试通过,因为它发生了线程恐慌,这是我们希望看到的结果。
另外,我们还可以再指定我们具体期望的异常,那么就可以在should_panic后面加上expected参数。
#[test]
#[should_panic(expected = "a should be positive")]
fn internal() {
assert_eq!(4, internal_adder(-2, 2));
}
大家可以自行运行一下这段测试代码看看效果。
总结
文中我向大家介绍了在Rust中如何进行单元测试、集成测试,还有比较特殊的文档测试。最后还介绍了3种常见的测试特性。
最后想友情提醒大家一下,在开发过程中,不要写完一堆功能后再开始写单元测试,这时你很有可能会因为测试代码过于繁琐而放弃。建议大家每写一个功能,随即开始进行单元测试,这样也能立即看到自己的代码的执行效果,提高成就感。这就是所谓的“步步为营”。
Rust入坑指南:步步为营的更多相关文章
- Rust入坑指南:核心概念
如果说前面的坑我们一直在用小铲子挖的话,那么今天的坑就是用挖掘机挖的. 今天要介绍的是Rust的一个核心概念:Ownership.全文将分为什么是Ownership以及Ownership的传递类型两部 ...
- Rust入坑指南:鳞次栉比
很久没有挖Rust的坑啦,今天来挖一些排列整齐的坑.没错,就是要介绍一些集合类型的数据类型."鳞次栉比"这个标题是不是显得很有文化? 在Rust入坑指南:常规套路一文中我们已经介绍 ...
- Rust入坑指南:亡羊补牢
如果你已经开始学习Rust,相信你已经体会过Rust编译器的强大.它可以帮助你避免程序中的大部分错误,但是编译器也不是万能的,如果程序写的不恰当,还是会发生错误,让程序崩溃.所以今天我们就来聊一聊Ru ...
- Rust入坑指南:朝生暮死
今天想和大家一起把我们之前挖的坑再刨深一些.在Java中,一个对象能存活多久全靠JVM来决定,程序员并不需要去关心对象的生命周期,但是在Rust中就大不相同,一个对象从生到死我们都需要掌握的很清楚. ...
- Rust入坑指南:齐头并进(上)
我们知道,如今CPU的计算能力已经非常强大,其速度比内存要高出许多个数量级.为了充分利用CPU资源,多数编程语言都提供了并发编程的能力,Rust也不例外. 聊到并发,就离不开多进程和多线程这两个概念. ...
- Rust入坑指南:常规套路
搭建好了开发环境之后,就算是正式跳进Rust的坑了,今天我就要开始继续向下挖了. 由于我们初来乍到 ,对Rust还不熟悉,所以我决定先走一遍常规套路. 变不变的变量 学习一门语言第一个要了解的当然就是 ...
- Rust入坑指南:坑主驾到
欢迎大家和我一起入坑Rust,以后我就是坑主,我主要负责在前面挖坑,各位可以在上面看,有手痒的也可以和我一起挖.这个坑到底有多深?我也不知道,我是抱着有多深就挖多深的心态来的,下面我先跳了,各位请随意 ...
- Rust入坑指南:千人千构
坑越来越深了,在坑里的同学让我看到你们的双手! 前面我们聊过了Rust最基本的几种数据类型.不知道你还记不记得,如果不记得可以先复习一下.上一个坑挖好以后,有同学私信我说坑太深了,下来的时候差点崴了脚 ...
- Rust入坑指南:有条不紊
随着我们的坑越来越多,越来越大,我们必须要对各种坑进行管理了.Rust为我们提供了一套坑务管理系统,方便大家有条不紊的寻找.管理.填埋自己的各种坑. Rust提供给我们一些管理代码的特性: Packa ...
随机推荐
- java架构之路(多线程)JMM和volatile关键字(二)
貌似两个多月没写博客,不知道年前这段时间都去忙了什么. 好久以前写过一次和volatile相关的博客,感觉没写的那么深入吧,这次我们继续说我们的volatile关键字. 复习: 先来简单的复习一遍以前 ...
- Scala与Mongodb实践3-----运算环境的搭建
目的:使的在IDEA中编辑代码,令代码实现mongodb运算,且转换较为便捷 由实验2可知,运算环境的搭建亦需要对数据进行存储和计算,故需要实现类型转换,所以在实验2的基础上搭建环境. 由菜鸟教程可得 ...
- 常见基本数据结构——树,二叉树,二叉查找树,AVL树
常见数据结构——树 处理大量的数据时,链表的线性时间太慢了,不宜使用.在树的数据结构中,其大部分的运行时间平均为O(logN).并且通过对树结构的修改,我们能够保证它的最坏情形下上述的时间界. 树的定 ...
- @RequestParam,@PathVariable,@RequestBody
@RequestParam 和 @PathVariable 注解是用于从request中接收请求的,两个都可以接收参数,关键点不同的是@RequestParam 是从request里面拿取值,而 @P ...
- KVM管理工具 WebVirtMgr
WEB管理工具 WebVirtMgr WebVirtMgr是一个基于libvirt的Web界面,用于管理虚拟机.它允许您创建和配置新域,并调整域的资源分配.VNC查看器为来宾域提供完整的图形控制台.K ...
- numpy 数组的拼接
一.数组的拼接 1.水平拼接 a.格式 np.hstack((数组1, 数组2)) # 注意: 值是元祖 # 0轴长要相同 b.例子 import numpy as np arr1 = np.aran ...
- Djaingo 随机生成验证码(PIL)
基础: https://www.cnblogs.com/wupeiqi/articles/5812291.html 实例: https://www.cnblogs.com/6324TV/p/88112 ...
- MySQL日志及索引
MySQL物理结构: MySQL它是通过文件系统对数据进行储存和管理,从物理结构上分为日志文件和数据文件 日志文件: 日志文件记录了数据库操作的信息和一些错误信息,我们常用的日志文件有:错误日志.二进 ...
- Python学习,第三课 - 数据类型
前言. 本次针对Python中的数据类型,做详细的总结 1.数字 2 是一个整数的例子. 长整数 不过是大一些的整数. 3.23和52.3E-4是浮点数的例子.E标记表示10的幂.在这里,52.3E- ...
- CentOS7安装MySQL、Tomcat和GitBlit记录
一.安装MySQL 1.安装这个发布包 yum localinstall mysql-community-release-el6-5.noarch.rpm 可以通过下面的命令来确认这个仓库被成功添加: ...