软件工程师必备的技能 | 单元测试
扫描二维码
随时随地手机看文章
前言
测试是软件开发过程中一个必须的环节,测试确保软件的质量符合预期。
- GCC
- CMake
- Google Test
- gcov
- lcov
演示项目
为了方便本文的讲解,我专门编写了一个演示项目作为代码示例。
cd gtest-and-coverage
./make_all.sh
要运行这个项目,你的机器上必须先安装好前面提到的工具。如果没有,请阅读下文以了解如何安装它们。
如果你使用的是Mac系统,下文假设你的系统上已经安装了brew[2]包管理器。如果没有,请通过下面这条命令安装它:/usr/bin/ruby -e "$(curl -fsSL https://raw.githubusercontent.com/Homebrew/install/master/install)"
项目结构
演示项目的目录结构如下:
├── CMakeLists.txt
├── googletest-release-1.8.1.zip
├── include
│ └── utility.h
├── make_all.sh
├── src
│ └── utility.cpp
└── test
└── unit_test.cpp
这里演示的内容是:以测试一个我们要提供的软件库为例,讲解如何对其进行单元测试并生成测试报告。
当然,在实际上的项目中,一个软件库会通常包含更多的文件,不过这并不影响我们要说明的问题。演示项目中的文件说明如下:
文件名称 | 说明 |
---|---|
make_all.sh | 入口文件,会执行:编译,测试和生成报告等所有工作 |
CMakeLists.txt | 项目的编译文件 |
googletest-release-1.8.1.zip | google test源码压缩包 |
utility.h | 待测试的软件库的头文件 |
utility.cpp | 待测试的软件库的实现文件 |
unit_test.cpp | 对软件库进行单元测试的代码 |
测试环境
演示项目在如下的环境中测试过。
- MacBook Pro
- 操作系统:macOS Mojave 10.14.1
- 编译器:Apple LLVM version 10.0.0 (clang-1000.11.45.2)
- CMake:cmake version 3.12.1
- Google Test: 1.8.1
- lcov: lcov version 1.13
- Ubuntu
- 操作系统:Ubuntu 16.04.5 LTS
- 编译器:gcc (Ubuntu 5.4.0-6ubuntu1~16.04.10) 5.4.0 20160609
- CMake:cmake version 3.5.1
- Google Test:1.8.1
- lcov:lcov version 1.12
关于CMake
为了简化编译的过程,这里使用CMake作为编译工具。关于CMake的更多内容请参见请官网:https://cmake.org[3]。
- Mac系统:
- Ubuntu系统
由于篇幅所限,这里不打算对CMake做过多讲解,读者可以访问其官网或者在网络上搜寻其使用方法。
project(utility) ②
set(CMAKE_CXX_STANDARD 11) ③
set(GTEST googletest-release-1.8.1) ④
include_directories("./include" "${GTEST}/googletest/include/")
link_directories("build/gtest/googlemock/gtest/")
SET(CMAKE_CXX_FLAGS "${CMAKE_CXX_FLAGS} --coverage") ⑤
add_library(${CMAKE_PROJECT_NAME}_lib src/utility.cpp) ⑥
add_executable(unit_test test/unit_test.cpp) ⑦
target_link_libraries(unit_test ${CMAKE_PROJECT_NAME}_lib gtest gtest_main pthread) ⑧
以编号为序,这段代码说明如下:
- 设置使用的CMake最低版本号为2.8.11。
- 指定项目的名称为”utility”,项目名称可以通过${CMAKE_PROJECT_NAME}进行引用。
- 指定使用C 11。
- 这里的三行是编译google test,并将其头文件路径和编译结果的库文件路径添加到环境中。因为后面在编译单元测试代码的时候需要用到。
- 添加--coverage到编译器flag中,这个参数是很重要的,因为这是生成代码覆盖率所必须的。关于该编译参数的说明见这里:Program Instrumentation Options[5]。
- 编译我们的软件库,这里将生成libutility_lib.a库文件。
- 编译单元测试的可执行文件。
- 单元测试的可执行文件需要链接我们开发的软件库以及google test的库。另外,google test依赖了pthread,所以这个库也需要。
关于测试
软件测试有很多种分类方式。从测试的级别来说,可以大致分为:
- 单元测试
- 集成测试
- 系统测试
关于xUnit
xUnit是几种单元测试框架的总称。最早源于Smalltalk的单元测试框架SUnit,它是由Kent Beck[7]开发的。
Google Test介绍
Google Test的项目主页在Github上:Github: Google Test[8]。
- Linux
- Mac OS X
- Windows
- Cygwin
- MinGW
- Windows Mobile
- Symbian
- Chromium projects[9]
- LLVM[10]
- Protocol Buffers[11]
- OpenCV[12]
- tiny-dnn[13]
编译Google Test
关于如何编译Google Test请参见这里:Generic Build Instructions[14]。
使用Google Test
演示项目代码说明
为了便于下文说明,演示项目中包含了几个简单的函数。
可以从这里下载源码以便查看其中的内容:paulQuei/gtest-and-coverage[15]。演示项目中的软件库包含一个头文件和一个实现文件。头文件内容如下:
#ifndef INCLUDE_UTILITY_
#define INCLUDE_UTILITY_
enum CalcType {
ADD,
MINUS,
MULTIPLE,
DIVIDE
};
class Utility {
public:
int ArithmeticCalculation(CalcType op, int a, int b);
double ArithmeticCalculation(CalcType op, double a, double b);
bool IsLeapYear(int year);
};
#endif
这个头文件说明如下:
-
头文件包含了三个函数,前两个用来做int和double类型的四则运算。最后一个判断输入的年份是否是闰年。
-
当然,在实际的工程中,前两个函数合并实现为一个泛型函数更为合适。但这里之所以分成两个,是为了查看代码覆盖率所用。
-
关于
- 能被4整除但不能被100整除的年份为普通闰年。
- 能被100整除,也同时能被400整除的为世纪闰年。
- 其他都不是闰年。
- 例如:1997年不是闰年,2000年是闰年,2016年是闰年,2100不是闰年。
#include "utility.h"
#include
#include
using namespace std;
int Utility::ArithmeticCalculation(CalcType op, int a, int b) {
if (op == ADD) {
return a b;
} else if (op == MINUS) {
return a - b;
} else if (op == MULTIPLE) {
return a * b;
} else {
if (b == 0) {
cout << "CANNO Divided by 0" << endl;
return std::numeric_limits::max();
}
return a / b;
}
}
double Utility::ArithmeticCalculation(CalcType op, double a, double b) {
if (op == ADD) {
return a b;
} else if (op == MINUS) {
return a - b;
} else if (op == MULTIPLE) {
return a * b;
} else {
if (b == 0) {
cout << "CANNO Divided by 0" << endl;
return std::numeric_limits::max();
}
return a / b;
}
}
bool Utility::IsLeapYear(int year) {
if (year % 100 == 0