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 ...
随机推荐
- 转载 css截取td里面的内容 如何固定td th的宽度
源博客地址:http://blog.csdn.net/u011456552/article/details/53839255 效果图: 源码: <!DOCTYPE html> <ht ...
- Go Web 编程之 静态文件
概述 在 Web 开发中,需要处理很多静态资源文件,如 css/js 和图片文件等.本文将介绍在 Go 语言中如何处理文件请求. 接下来,我们将介绍两种处理文件请求的方式:原始方式和http.File ...
- UGUI源码之EventSystem
今天研究下UGUI的源码,先从EventSystem入手.EventSystem是用来处理点击.键盘输入以及触摸等事件的. 1.BaseInputModule EventSystem开头声明了两个变量 ...
- TensorFlow——Graph的基本操作
1.创建图 在tensorflow中,一个程序默认是建立一个图的,除了系统自动建立图以外,我们还可以手动建立图,并做一些其他的操作. 下面我们使用tf.Graph函数建立图,使用tf.get_defa ...
- Android Gradle脚本从Groovy迁移到Kotlin DSL
Android Gradle从Groovy迁移到Kotlin Android项目用Gradle构建, 其脚本语言之前是Groovy, 目前也提供了Kotlin的支持, 所以可以迁移到Kotlin. 官 ...
- 《C# 爬虫 破境之道》:第一境 爬虫原理 — 第二节:WebRequest
本节主要来介绍一下,在C#中制造爬虫,最为常见.常用.实用的基础类 ------ WebRequest.WebResponse. 先来看一个示例 [1.2.1]: using System; usin ...
- KVM虚拟化基础
关于虚拟化 什么是虚拟化 在计算机技术中,虚拟化(技术)或虚拟技术(英语:Virtualization)是一种资源管理技术,是将计算机的各种实体资源(CPU.内存.磁盘空间.网络适配器等),予以抽象. ...
- 安装xpath helper
1.下载 版本是:2.02的 链接:https://pan.baidu.com/s/1YdyTbWElL904EMQ-9Ougnw 提取码:bxxa 2.无效安装的解决方案 参考链接:https:// ...
- Scrapy信号量
1.类 from scrapy import signals class MySingle(object): def __init__(self): pass @classmethod def fro ...
- c#数字图像处理(六)直方图均衡化
直方图均衡化又称直方图修平,是一种很重要的非线性点运算.使用该方法可以加强图像的局部对比度,尤其是当图像的有用数据的对比度相当接近的时候.通过这种方法,亮度可以更好的在直方图上分布. 直方图均衡化的基 ...