使用gtest + Gcov 对C++ project进行单元测试以及单测覆盖率统计
一、背景
单元测试对于开发人员来讲很普通但又经常被忽视,但是并不代表单元测试不重要相反很有必要,列举几条必要性的原因a
- 降低程序的bug出现的可能性。大工程难免包含很多的类方法等,这之间难免有相互耦合的地方,修改其中的一个方法很可能造成其其他的方法出现bug。如果提前写了单元测试,就能很快定位出bug所在
- 能让开发人员降低程序之间的耦合度,让程序能容易维护升级
总之,提前使用起来会让后面的开发维护工作更加轻松
二、gtest使用方法
详细介绍参考官方文档:https://github.com/google/googletest/blob/master/googletest/docs/primer.md
一下的内容以我自己写的一个cpp project为例展开
第一步 安装gtest
mac版本: http://ysonggit.github.io/system/2015/01/01/install-gtest-on-mac-os-yosemite.html
第二步,编写单测
提前知晓:程序单测的对象主要是我的c++ project里写的类方法,这里我只有一个头文件,封装了我的project里使用的大部分方法。如下是步骤。
- 新建一个CPP文件unitTest,将需要测试的类include进来 同时include gtest/gtest.h
- 编写测试case,每个方法的输入输出是什么自己根据不同方法去构造
例子:
我这里有一个Data.h
#ifndef Data_hpp
#define Data_hpp
#include <stdio.h>
#include <vector>
#include <string>
#include <boost/regex.hpp>
class Data {
private:
//std::string context;
std::string week_today;
std::string data_today;
std::string weather_today;
std::string temperature_high_today;
std::string temperature_low_today;
std::string week_tomorrow;
std::string data_tomorrow;
std::string weather_tomorrow;
std::string temperature_hight_tomorrow;
std::string temperature_low_tomorrow;
boost::smatch _what;
boost::regex _reg;
public:
//delete the html's tag extract the contex of the page
std::string replace(std::string context);
//set reg
void setReg(std::string reg);
//select the context from the pages by reg
std::string filterString(std::string context);
//delete the spaces of the pages make sure the context is a type of string which have no spaces
std::string deleteSpaceContext(std::string context);
// data Normalization
std::string dataNormalization(std::string context);
void fillData(int index_week_today, std::string context);
};
#endif /* Data_hpp */
现在要对Data.h写单元测试,一共有六个类函数,每个类函数有不同的输入与输出
unitTest.cpp 内容
#include <iostream>
#include <gtest/gtest.h>
#include "Data.hpp"
#include <string>
std::string test_replace(std::string html) {
Data data;
std::string ret = data.replace(html);
return ret;
}
std::string test_filterString(std::string context, std::string test_reg) {
Data data;
data.setReg(test_reg);
std::string ret = data.filterString(context);
return ret;
}
std::string test_deleteSpaceContext(std::string context) {
Data data;
std::string ret = data.deleteSpaceContext(context);
return ret;
}
std::string test_dataNormalization(std::string context){
Data data;
std::string ret = data.dataNormalization(context);
return ret;
}
TEST(testCase, test0) {
std::string str_repalce = "haha";
EXPECT_EQ(str_repalce, test_replace("<div>haha</div>"));
}
TEST(testCase, test1) {
std::string str_replace = "\n18\n\n\n东风\n";
EXPECT_EQ(str_replace, test_replace("<div class=\"temp\">\n18\n</div>\n<div class=\"direct\">\n东风\n</div>"));
}
TEST(testCase, test2) {
std::string str_filter = "<div class=\"temp\">\n18\n</div>";
std::string test_reg = "<.*?18\n</div>";
EXPECT_EQ(str_filter, test_filterString("<div class=\"temp\">\n18\n</div>\n<div class=\"direct\">\n东风\n</div>", test_reg));
}
TEST(testCase, test3) {
std::string str_deleteSpace = "嘻嘻哈哈";
EXPECT_EQ(str_deleteSpace, test_deleteSpaceContext("嘻嘻 哈哈"));
}
TEST(testCase, test4) {
std::string str_deletespace = "嘻嘻哈哈";
EXPECT_EQ(str_deletespace, test_deleteSpaceContext("嘻 嘻\n哈哈"));
}
TEST(testCase, test5) {
std::string str_dataNorm = "2018晴天晴天";
EXPECT_EQ(str_dataNorm, test_dataNormalization("2018晴晴"));
}
TEST(testCase, test6){
std::string str_dataNorm = "晴天阴天";
EXPECT_EQ(str_dataNorm, test_dataNormalization("晴阴"));
}
TEST(testCase, test7) {
std::string str_dataNorm = "晴天小雨";
EXPECT_EQ(str_dataNorm, test_dataNormalization("晴小雨"));
}
int main(int argc, char *argv[]) {
// insert code here...
std::cout << "Hello, World!\n";
testing::InitGoogleTest(&argc, argv);
//LOG(FATAL)<<"FATAL";
return RUN_ALL_TESTS();
}
main函数里的 testing::InitGoogleTest(&argc, argv); 是入口,case里是EXPECT_EQ判断是否相等,当然还有其他方式的断言,可以参考官方文档
第三步,编译执行单测文件unitTest
就可以看到测试结果 如下图我写了8个单元测试case 全部passed。
三、单元测试覆盖率统计
做好了单元测试但是别人并不知道我们的单元测试做的如何,是否覆盖了所有的需要被测试的类方法或者变量等,所以我们需要对单测case做一个覆盖率统计。其实本质就是看代码执行时候运行了你需要测试文件里面的所有代码,比如switch分支 if分支等。
步骤:
1、安装lcov mac下 brew install lcov
2、重新编译单元测试文件unitTest.cpp 加上-fprofile-arcs -ftest-coverage选项 编译后会生成.gcda .gcno文件
3、运行刚刚编译生成的执行文件
4、执行lcov -d . -t ‘unittest’ -o ‘test.info’ -b . -c
-d: 待覆盖率测试的源码目录,此处假设为src_dir
-t: 目标的名称,可不带引号,此处为target
-o: 生成的覆盖率文件,可自定义,可不带引号
-b: 相对目录的起始位置
-c: capture,采集覆盖率
5、执行 genhtml -o result test.info 生成html文件可视化
打开生成的result文件 index.html就可以看到覆盖率统计情况