概述
什么是单元测试?
单元测试是指,对软件中的最小可测试单元在与程序其他部分相隔离的情况下进行检查和验证的工作,这里的最小可测试单元通常是指函数、接口或者类。
单元测试贯穿在开发的整个过程,并伴随着新功能模块的产生而进行。单元测试并不会花费更多的时间,与之相反,在提高代码效率、减少bug数量、有序开展开发工作上,单元测试发挥着很大的作用。
场景示例
一个开发者因为最上层的代码运行没有任何输出,采用单步调试来跟踪并发现了一个bug,在他纠正了这个bug的同时又找到了好几个其它的bug。如此几次过后,bug还是存在。而程序输出这边,仍然没有结果。于是,这个开发者已经完全搞不清为什么会这样,并认为这种没有输出的行为是毫无道理的。
如果针对上面这个场景引入单元测试,情况会是这样:
在开发过程中,每写一个函数就添加一个简单的测试来判断函数功能和所期望的是否一致。在未对刚写的函数做出确认之前,开发者并不会接着写新代码。也就是每写一个函数,必然是在验证其功能可用的情况下才引入新的功能的开发。最后结果则是,因为有单元测试保障每一个新增函数的功能都是可用的,因而最后的最上层程序也是有输出的,而不会出现之前第一种场景里那种完全无厘头的情况。
误区纠正
编写单元测试太费时间。相比在项目结束时才进行的测试工作会花费更多的时间,用在单元测试上的时间是要少得多的。当然,前提是开发者必须要对所要测试的单元要实现什么样的功能,期望输出是怎样的要十分了解才行。
测试代码并不是开发的工作。如果一个开发者把随手编写的一块没有把握的代码随便地扔给测试组,那么实际上这个开发者并没有完成他的工作。实际上,期望别人来清理自己的代码是很不好的做法。
这些代码都能够编译通过。有一种很普遍的误解是,一个成功的编译就是成功的标记;实际上是,任何编译器和解释器都只能验证语法的正确性,而并不能验证行为的正确性。
对一些重要的模块组件或功能接口,编写单元测试是有必要的。能够对接口的参数,期望的结果做些更完善的验证,发现一些潜在的危险。并且借助一些单元测试框架,可以对接口进行压力测试,验证极端情况下的运行情况。
单元测试的好处?
单元测试有助于:
1.模块化您的代码,由于代码的可测试性取决于其设计,因此单元测试有助于将其分解为易于测试的专用部分。
2.避免回归,如果拥有一套单元测试,则可以迭代地运行它,以确保每次添加新功能或进行更改时,一切都能正常工作。
3.记录您的代码,运行,调试甚至只是阅读测试都可以提供许多有关原始代码如何工作的信息,因此您可以将它们用作隐式文档。
如何开展单元测试
实际软件项目中如何开展单元测试?
并不是所有的代码都要进行单元测试,通常只有重要模块或者核心模块的测试中才会采用单元测试。
单元测试的良好做法包括:
为公开的功能(包括类构造函数和运算符)创建测试。
涵盖所有代码路径,并检查琐碎的情况和边缘情况,包括那些输入数据不正确的情况(请参见否定测试)。
确保每个测试独立运行,并且不会阻止其他测试的执行。
以不会影响测试结果运行顺序的方式来组织测试。
最理想的情况是需要把单元测试执行、代码覆盖率统计和持续集成流水线做集成,以确保每次代码递交,都会自动触发单元测试。并在单元测试执行过程中自动统计代码覆盖率,最后以“单元测试通过率”和“代码覆盖率”为标准来决定本次代码递交是否能够被接受。
如何编写有效的单元测试
单元测试的组成部分
一般单元测试由以下几部分组成:
1.测试数据:尽可能稳定,减少对不确定性因素的依赖。
2.逻辑执行体:要明确当前测试用例测试的是哪个函数、哪个分支逻辑,不要一次性覆盖大多。
3.结果校验:尽可能完整,不要只校验函数返回值。
单元测试的原则
单元测试必须遵循的原则:
1. 独立性:单元测试是独立的,可以单独运行,并且不依赖于任何外部因素,如文件系统或数据库。
2. 幂等性:每次运行单元测试应与其结果一致,测试中不要依赖如时间、日期等不确定因素。
3. 快速:不要依赖网络请求等耗时操作。
经验小结
编写单元测试时建议从以下角度思考:
实现什么功能,处理哪些数据,最终输出什么?
异常和边界在哪里?
函数的关键结果是否都验证到?包含返回值和中间值。
函数的风险在哪里,哪部分逻辑不太自信,最容易出错?
并不是所有函数都需要单测,如get/set等逻辑比较简单的的,不一定需要写 。
环境搭建
C++常用单元测试工具介绍
有很多C++测试框架,如Catch, Boost.Test, UnitTest++, lest, bandit, igloo, xUnit++, CppTest, CppUnit, CxxTest, cpputest, googletest, QtTest,cute,doctest以及其它一些。
Gtest介绍
Google C++单元测试框架(简称Gtest),可在多个平台上使用(包括Linux, Mac OS X, Windows, Cygwin和Symbian),它提供了丰富的断言、致命和非致命失败判断,能进行值参数化测试、类型参数化测试。
Doctest介绍
gtest需要安装有时候带来很多不方便。比如网络原因下载安装gtest都可能因为网络原因失败。除了gtest之外,还有很多轻量级易用的单元测试库,比如doctest和catch。相比gtest需要编译/安装,他们都是header only的,直接包含到工程里就可以做单元测试了,portable又没有任何依赖,而且对编译器版本要求也不高。
doctest 是一个新的 C++ 测试框架。与其他功能丰富的替代方案相比,编译时(by orders of magnitude)和运行时是最快的。通过提供一个快速,透明和灵活的测试运行器与简洁的界面,可直接在生产代码中编写测试。
doctest和其他测试框架之间的主要区别在于它很轻而且没有侵入性。