Gcovr 入门指导
在上一节Gtest的学习中,我们对Money Demo通过gtest进行了测试,那么我们的 测试覆盖了代码中所有的情况么?测试的完全么?也许我们不想花费太多的时间 纠结在测试上,所以下面推荐一款代码覆盖率的工具(gcovr)以供大家检查自己的 代码覆盖情况。强调一下,我们最好是在linux下学习gcovr的使用。
那么什么是Gcovr呢,我们看一下Gcovr的guide.pdf中是如何描述的:
“Gcovr provides a utility for managing the use of the GNU gcov utility and generating summarized code coverage results. This command is inspired by the Python coverage.py package, which provides a similar utility in Python. The gcovr command produces either compact human-readable summary reports, machine readable XML reports (in Cobertura format) or simple HTML reports. Thus, gcovr can be viewed as a command-line alternative to the lcov utility, which runs gcov and generates an HTML-formatted report.”
Table of Contents
• 1 Gcovr的下载:
• 2 Gcovr的使用:
o 2.1 Makefile
o 2.2 Gcovr的具体命令参数的使用
2.2.1 -r 参数
2.2.2 –branches
2.2.3 XML输出文件
2.2.4 HTML输出
2.2.5 –html-details
2.2.6 Gcovr的其他命令
• 3 Gcovr注意事项
• 4 作业
• 5 参考文献
1 Gcovr的下载:
http://gcovr.com/
上面给出的连接是gcovr的home page。在home page中我们能下载到有关gcovr 的PDF,HTML等一些指导书籍。
大家可以从下面这个链接处下载到Gcvor,下载最新的3.2即可。
https://github.com/gcovr/gcovr/releases
2 Gcovr的使用:
当我们把下载的gcovr-3.2.zip(或者是gcovr.tar.gz)解压缩,比如我的解压缩 的位置是:/home/yulixiang/work/GtestAndGcovr/gcovr-3.2。
我的Money的源码位置是: /home/yulixiang/work/GtestAndGcovr/SourceCode
那么Gcovr和Money源码SourceCode是同级目录。
下面我们还是根据Money例子来讲述如何使用Gcovr。首先我们要知道那怎么才 能在我们的项目中加入Gcovr。
2.1 Makefile
在Money例子的Makefile编译选项中加入编译选项: -fprofile-arcs -ftest-coverage -fPIC -O0。 在链接选项中加入-lgcov选项即可。就是这么 简单!
// 编译选项
CXXFLAGS +=-c -g -Wall -Wextra -I$(GTEST_DIR)/include -fprofile-arcs -ftest-coverage -fPIC -O0
//链接选项
LDFLAGS += -L$(GTEST_DIR)/lib -lgtest -lpthread -lgcov
在加入这些编译和链接选项之后,整个Makefile的代码如下:
GTEST_DIR=your_gtest_directory
SRC_DIR=your_Money_SouceCode_directory
LDFLAGS += -L$(GTEST_DIR)/lib -lgtest -lpthread -lgcov
CXXFLAGS +=-c -g -Wall -Wextra -I$(GTEST_DIR)/include -fprofile-arcs -ftest-coverage -fPIC -O0
TARGETS = money_unittest
OBJS = money.o gtestMoney.o
CC=g++
.PHONY: clean all test
all: $(TARGETS)
$(TARGETS) : $(OBJS)
$(CC) $^ -o $@ $(LDFLAGS)
money.o : $(SRC_DIR)/Money.cpp $(SRC_DIR)/Money.h
$(CC) $(CXXFLAGS) $< -o $@
gtestMoney.o : $(SRC_DIR)/MoneyTest.cpp $(SRC_DIR)/Money.h
$(CC) $(CXXFLAGS) $< -o $@
clean:
rm -f $(TARGETS) $(OBJS)
rm *.gcno
rm *.gcda
rm *.xml
test:
(
T
A
R
G
E
T
S
)
.
/
(TARGETS) ./
(TARGETS)./(TARGETS)
在更新Makefile之后我们编译(make)就会有.gcno文件产生,我们执行 money_unittest.exe就会有.gcda文件产生。这两种文件是Gcovr分析的时候 会用到的二进制文件。那现在Gcovr分析所需要的文件已经有了怎么使用 Gcovr来分析Money的代码覆盖率呢?
2.2 Gcovr的具体命令参数的使用
2.2.1 -r 参数
因为上文我把gcovr-3.2文件夹放在与我们的SourceCode是同级目录,所以我 们可以在SourceCode中直接执行下面的命令:
// 执行gcovr
~/work/GtestAndGcovr/SourceCode> python …/gcovr-3.2/scripts/gcovr -r .
就会产生如下的结果
‘-r’ 操作只是产生一个简单的报告。从输出的结果我们可以看到Money.cpp文 件共有13行,但之执行了7行文件。覆盖率仅仅是53% 。那么我们很想知道具体都 有哪些行没有覆盖,这个时候用下列这些命令了。
2.2.2 –branches
–branches命令能够总结出每个项目文件有多少个branches,测试用例中覆盖了 多少个branches。
2.2.3 XML输出文件
Gcovr在默认的条件下是生成一个纯文本的表格。Gcovr能够生成一个XML的输 出文件,使用–xml 和 –xml-pretty
~/work/GtestAndGcovr/SourceCode> python …/gcovr-3.2/scripts/gcovr -r . --xml --xml-pretty
由于输出很多,所以我添加到附件XMLOutput.xml文件中,下面是输出的一部分:
–html命令只是产生一个总结所有的文件信息的单一网页。
2.2.5 –html-details
–html-details命令是为每一个项目文件产生一个单独的网页。每一个单独 的网页都包含了该文件详细的代码覆盖情况。
• 注意: 在使用–html-details必须同时使用-o命令。
~/work/GtestAndGcovr/SourceCode> python …/gcovr-3.2/scripts/gcovr -r . --html --html-details -o result.html
执行这个命令后我们可以看到会有:result.html, result.Money.cpp.html, result.Money.h.html和result.MoneyTest.cpp.html四个HTML文件产生。当我们 双击result.html。
可以看到和–html命令一样的页面,但是现在我们能够点击.cpp,跳转到 Money.cpp文件的代码覆盖情况的页面。一般情况下,我们也是最关心.cpp文件的 覆盖情况。
通过这个包含了详细信息的HTML文件,我们可以直观的看到,我们的测试代码没 有覆盖operator==, opreator!= 这两个功能接口,也没有覆盖operator +=中的 一个分支。根据这些详细的信息有助于我们修改测试。
2.2.6 Gcovr的其他命令
通过 gcovr –help 就可以显示出gcovr的命令摘要。
~/work/GtestAndGcovr/SourceCode> python …/gcovr-3.2/scripts/gcovr --help
Usage: gcovr [options]
A utility to run gcov and generate a simple report that summarizes the
coverage
Options:
-h, --help show this help message and exit
–version Print the version number, then exit
-v, --verbose Print progress messages
–object-directory=OBJDIR
Specify the directory that contains the gcov data
files. gcovr must be able to identify the path
between the *.gcda files and the directory where gcc
was originally run. Normally, gcovr can guess
correctly. This option overrides gcovr’s normal path
detection and can specify either the path from gcc to
the gcda file (i.e. what was passed to gcc’s ‘-o’
option), or the path from the gcda file to gcc’s
original working directory.
-o OUTPUT, --output=OUTPUT
Print output to this filename
-k, --keep Keep the temporary *.gcov files generated by gcov. By
default, these are deleted.
-d, --delete Delete the coverage files after they are processed.
These are generated by the users’s program, and by
default gcovr does not remove these files.
-f FILTER, --filter=FILTER
Keep only the data files that match this regular
expression
-e EXCLUDE, --exclude=EXCLUDE
Exclude data files that match this regular expression
–gcov-filter=GCOV_FILTER
Keep only gcov data files that match this regular
expression
–gcov-exclude=GCOV_EXCLUDE
Exclude gcov data files that match this regular
expression
-r ROOT, --root=ROOT Defines the root directory for source files. This is
also used to filter the files, and to standardize the
output.
-x, --xml Generate XML instead of the normal tabular output.
–xml-pretty Generate pretty XML instead of the normal dense
format.
–html Generate HTML instead of the normal tabular output.
–html-details Generate HTML output for source file coverage.
–html-absolute-paths
Set the paths in the HTML report to be absolute
instead of relative
-b, --branches Tabulate the branch coverage instead of the line
coverage.
-u, --sort-uncovered Sort entries by increasing number of uncovered lines.
-p, --sort-percentage
Sort entries by decreasing percentage of covered
lines.
–gcov-executable=GCOV_CMD
Defines the name/path to the gcov executable [defaults
to the GCOV environment variable, if present; else
‘gcov’].
–exclude-unreachable-branches
Exclude from coverage branches which are marked to be
excluded by LCOV/GCOV markers or are determined to be
from lines containing only compiler-generated “dead”
code.
-g, --use-gcov-files Use preprocessed gcov files for analysis.
-s, --print-summary Prints a small report to stdout with line & branch
percentage coverage
3 Gcovr注意事项
我们使用gcovr检查代码的覆盖率,要求是覆盖率越大越好,包括代码branch的 覆盖和line覆盖。
4 作业
通过这一课的学习,要求大家对自己的上一次作业DemoContainer,进行代码覆 盖的检查,并且根据提供的信息,补全我们的代码测试。例如我们的作业可以 达到下面的覆盖结果(gcovr命令用的是gcovr -r SourceCode/ –html –html-details –branch -o result.html)。我们最关心的是.cpp( DemoContainer.cpp)文件的覆盖率,从下面的截图我们可以看出 DemoContainer.cpp文件的line和branch覆盖均为100%。这是最理想的结果。
5 参考文献
http://gcovr.com/
Author: 于丽香 <yu-lx@neusoft.com >
Date: 2018-11-03 15:00:52 CST
HTML generated by org-mode 6.33x in emacs 23
Gtest 测试指导 入门基础(A)
Table of Contents
• 1 Gtest的基本使用,包括下载,安装,编译。
o 1.1 下载
o 1.2 编译
1.2.1 Gtest静态库的编译
1.2.2 Gtest在VS中的编译
• 2 在项目中配置Gtest
o 2.1 Gtest在非VS环境下的配置
o 2.2 Gtest在VS环境下的配置
• 3 Gtest的使用
o 3.1 Makefile
o 3.2 构建代码
o 3.3 Gtest断言的使用
o 3.4 Gtest的异常检查
o 3.5 Gtest的事件机制
o 3.6 Gtest的参数化
o 3.7 Gtest的死亡测试
3.7.1 *_DEATH(statement, regex)
3.7.2 *_EXIT(statement, predicate, regex)
3.7.3 死亡测试运行方式
3.7.4 死亡测试的注意事项
o 3.8 Gtest的运行参数
• 4 作业
o 4.1 编写一个DemoContainer类,按以下接口要求实现:
• 5 参考文献
1 Gtest的基本使用,包括下载,安装,编译。
1.1 下载
直接在google中搜索gtest,第一个就是。也可以从下面地址下载gtest。
https://code.google.com/p/googletest/downloads/
1.2 编译
1.2.1 Gtest静态库的编译
在下载解压后,假设你把gtest源码放在/usr/src/gtest
GTEST_DIR=/usr/src/gtest
设置完GTEST_DIR之后,执行下列的命令
g++ -I
G
T
E
S
T
D
I
R
/
i
n
c
l
u
d
e
−
I
{GTEST_DIR}/include -I
GTESTDIR/include−I{GTEST_DIR} -c ${GTEST_DIR}/src/gtest-all.cc
ar -rv libgtest.a gtest-all.o
来生成libgtest.a。
1.2.2 Gtest在VS中的编译
下载解压后,里面有个msvc目录,使用VS的同学可以直接的打开msvc里面的工 程文件,打开后会提示你升级,升级后,我们直接编译里面的“gtest”工程, 可以直接编过去的。最好是编译Debug和Relese两个版本。
这里需要注意的是:如果你升级gtest是在VS2008中升级,那么你要使用 gtest 进行测试的demo最好也是VS2008工程,不然你会发现很郁闷,你的 demo怎么也编不过。
如果你编译了Debug和Relese两个版本之后,在msvc里面就有两个文件夹 Debug和Release,这两个目录中能看到编译出来的gtestd.lib或gtest.lib文 件。
2 在项目中配置Gtest
2.1 Gtest在非VS环境下的配置
GTEST_DIR=H:/work/myGtest
SRC_DIR=/cygdrive/h/work/Sample
LDFLAGS += -L ( G T E S T D I R ) / l i b − l g t e s t − l p t h r e a d C X X F L A G S + = − c − g − W a l l − W e x t r a − I (GTEST_DIR)/lib -lgtest -lpthread CXXFLAGS += -c -g -Wall -Wextra -I (GTESTDIR)/lib−lgtest−lpthreadCXXFLAGS+=−c−g−Wall−Wextra−I(GTEST_DIR)/include
TARGET = money_unittest
OBJS = money.o gtestMoney.o
CC=g++
.PHONY: clean all test
all: $(TARGET)
$(TARGET) : $(OBJS)
$(CC) $^ -o $@ $(LDFLAGS)
money.o : $(SRC_DIR)/Money.cpp $(SRC_DIR)/Money.h
$(CC) $(CXXFLAGS) $< -o $@
gtestMoney.o : $(SRC_DIR)/MoneyTest.cpp $(SRC_DIR)/Money.h
$(CC) $(CXXFLAGS) $< -o $@
clean:
rm -f $(TARGET) $(OBJS)
test:
(
T
A
R
G
E
T
)
.
/
(TARGET) ./
(TARGET)./(TARGET)
现在就可以在我们的Money工程中使用Gtest了。
3.2 构建代码
TEST(MoneyConstructorTest, TestConstructor)
{
// Set up
const std::string currencyAA(“AA”);
const double longNumber = 123.456;
// Process
Money money(longNumber, currencyAA);
// Check
// Positive test case
EXPECT_EQ(longNumber, money.getAmount());
EXPECT_STREQ("AA", money.getCurrency().c_str());
}
int main(int argc, char** argv)
{
return 0;
}
这段测试代码是测试一个实例money是否创建成功,money的amount和currency是 否与我们创建实例初始化的值是相同的。
而此时执行命令"make"进行编译肯定报错,因为我们还没有Money这个类也没有接 口函数getAmount()和getCurrency()。所以我们要根据测试用例来补充我们的 Money类。
根据测试用例我们知道,Money类要有一个getAmount()这个功能要得到成员 amount。还有一个getCurrency()得到成员currency,而成员amount和currency通 过Money的构造函数进行初始化。所以接下来在我们的代码中实现这部分功能。
//file:Money.h
#ifndef MONEY_H
#define MONEY_H
#include
#include
class Money
{
public:
Money( double amount, std::string currency )
: m_amount( amount )
, m_currency( currency )
{
}
double getAmount() const;
std::string getCurrency() const;
private:
double m_amount;
std::string m_currency;
};
#endif
// file Money.cpp
#include “Money.h”
double Money::getAmount() const
{
return m_amount;
}
std::string Money::getCurrency() const
{
return m_currency;
}
这时编译(make)还是编译不过,因为我们没有运行所有的测试用例。那我们用什 么运行测试呢?在MoneyTest.cpp的main函数中添加"RUN_ALL_TESTS()"意思是: 运行所有测试案例。
// file MoneyTest.cpp
int main(int argc, char** argv)
{
return RUN_ALL_TESTS();
}
这个时候编译(make)就会通过。但是执行的时候(make test)会产生下面的错误:
lixiang@lixiang-PC /cygdrive/h/work/Sample
$ make test
./money_unittest
This test program did NOT call ::testing::InitGoogleTest before calling RUN_ALL_
TESTS(). Please fix it.
Makefile:36: recipe for target ‘test’ failed
make: *** [test] Error 1
通过错误我们可以看到需要在MoneyTest.cpp的main函数中加入 testing::InitGoogleTest 所以我们需要继续修改MoneyTest.cpp中的main:
// file MoneyTest.cpp
int main(int argc, char** argv)
{
testing::InitGoogleTest(&argc, argv);
return RUN_ALL_TESTS();
}
这时我们make编译成功。运行money_unittest.exe会有如下输出:
好,到目前为止我们已经完成了Gtest的环境的搭配和基本的构建使用。下面就来 具体的学习一下Gtest针对不同情况的测试吧。
3.3 Gtest断言的使用
上面就是一个简单的测试案例。这个我们使用了TEST这个gtest包装好的宏, 它有两个参数,官方的解释:#define TEST(test_case_name, test_name) GTEST_TEST(test_case_name, test_name)。
对检查点的检查,我们上面使用到了EXPECT_EQ和EXPECT_STREQ这两个宏, 其中EXPECT_EQ这个宏用来比较两个数值是否相等。 EXPECT_STREQ(expected_str, actual_str)是比较两个C字符串内容(同时 支持char* 和 wchar_t*).
Gtest采用了大量的宏来包装断言,此断言不同于C语言的断言(assert),按照 使用方法分为两类:
EXPECT_* 系列断言失败时,案例继续往下执行。
ASSERT_* 系列断言失败时,退出当前函数,ASSERT_*后面的代码不会被 执行.(注意:并不是退出当前案例)
在上面的列子中为了我们的案例能够运行起来我们在main函数中添加如下代 码:
testing::InitGoogleTest(&argc, argv);
因为gtest的测试用例允许接收一系列的命令行参数因此我们在 "testing::InitGoogleTest(&argc, argv);"中将命令行参数传给gtest, gtest的命令行参数非常的丰富,有兴趣的大家可以自己查阅资料详细的了解 一下。
下面我们添加一个新的断言:
// file: MoneyTest.cpp
TEST(MoneyConstructorTest, TestConstructor)
{
// Set up
const std::string currencyAA(“AA”);
const double longNumber = 123.456;
// Process
Money money(longNumber, currencyAA);
// Check
EXPECT_EQ(longNumber, money.getAmount());
EXPECT_STREQ(“AA”, money.getCurrency().c_str());
EXPECT_EQ(555, money.getAmount());
std::cout << “Code after EXPECT!” << std::endl;
}
会出现什么结果呢?
这样的结果将会产生!这是因为EXPECT_EQ第一个参数是我们期望的结果是数值 555,而实际的结果是123.456,这与我们的期望不同,所以会有error产生。
值得注意的是在"EXPECT_EQ(555, money.getAmount())“出现error之后仍然继 续执行输出"Code after EXPECT!”.
如果我们期望结果数值不是实际函数的结果数值,那么就用下面的语句。
EXPECT_NE(555, money.getAmount());
这个时候所有的测试就会通过了!
让我们再加上一个ASSERT_*系列的断言,来体会ASSERT与EXPECT区别。
// file MoneyTest.cpp
// Assert test
TEST(MoneyConstructorTest, TestConstructorAssert)
{
// Set up
const std::string currencyBB(“ASSERT”);
const double longNumber = 222.222;
// Process
Money money(longNumber, currencyBB);
// Check
ASSERT_STREQ("AA", money.getCurrency().c_str());
std::cout << "Code after ASSERT!" << std::endl;
}
运行结果是
在ASSERT失败之后, 语句"Code after ASSERT"没有出现! 所以我们注意,最好不要在ASSERT语句后面做释放内存等操作!
现在我们只是依照Money的功能和功能对应的测试用例添加了Money类的构造函数 和接口getAmount(), getCurrency的源码,剩下的接口分别是operator ==, operator != 和 operator +=,大家可以按照想好功能,为功能添加测试用例, 补充功能源码的方式来完成对以上三个未完成的接口的测试。下面是参考代码:
// file Money.h
#ifndef MONEY_H
#define MONEY_H
#include
#include
class IncompatibleMoneyError : public std::runtime_error
{
public:
IncompatibleMoneyError() : std::runtime_error( “Incompatible moneys” )
{
}
};
class Money
{
public:
Money( double amount, std::string currency )
: m_amount( amount )
, m_currency( currency )
{
}
double getAmount() const;
std::string getCurrency() const;
bool operator ==( const Money &other ) const;
bool operator !=( const Money &other ) const;
Money &operator +=( const Money &other );
private:
double m_amount;
std::string m_currency;
};
#endif
// file Money.cpp
#include “Money.h”
double Money::getAmount() const
{
return m_amount;
}
std::string Money::getCurrency() const
{
return m_currency;
}
bool Money::operator ==( const Money &other ) const
{
return m_amount == other.m_amount &&
m_currency == other.m_currency;
}
bool Money::operator !=( const Money &other ) const
{
return !(*this == other);
}
Money & Money::operator +=( const Money &other )
{
if ( m_currency != other.m_currency )
throw IncompatibleMoneyError();
m_amount += other.m_amount;
return *this;
}
大家都可以用EXPECT_TRUE(FALSE)/ASSERT_TRUE(FALSE)书写出对这些函数接口 的测试相应的测试。
gtest的断言按照常用功能依次分为12类,平常主要用到的是一下几类:
|ASSERT_FALSE(condition) |EXPECT_TRUE(condition) |condition == false | |ASSERT_NE(val1, val2) | EXPECT_NE(val1, val2) | val1 != val2 |
布尔值比较
ASSERT_TRUE(condition) EXPECT_TRUE(condition) condition == true
数值型数据比较
ASSERT_EQ(expected, actual) EXPECT_EQ(expected, actual) expected == actual
ASSERT_LT(val1, val2) EXPECT_LT(val1, val2) val1 < val2
ASSERT_LE(val1, val2) EXPECT_LE(val1, val2) val1 <= val2
ASSERT_GT(val1, val2) EXPECT_GT(val1, val2) val1 > val2
ASSERT_GE(val1, val2) EXPECT_GE(val1, val2) val2 >= val2
字符串比较
ASSERT_STREQ(str1, str2) EXPECT_STREQ(str1, str2) 两个C字符串内容相同(同时支持char *和wchart *类型)
ASSERT_STRNE(str1, str2) EXPECT_STRNE(str1, str2) 两个C字符串内容不同(同时支持char *和wchart *类型)
ASSERT_STRCASEEQ(str1,str2) EXPECT_STRCASEEQ(str1,str2) 两个C字符串内容相同,忽略大小写(只支持char *类型)
ASSERT_STRCASENE(str1,str2) EXPECT_STRCASENE(str1,str2) 两个C字符串内容不同,忽略大小写(只支持char *类型)
浮点数比较
ASSERT_FLOAT_EQ(val1,val2) EXPECT_FLOAT_EQ(val1,val2) the two float values are almost equal
ASSERT_DOUBLE_EQ(val1,val2) EXPECT_DOUBLE_EQ(val1,val2) the two double values are almost equal
近似数比较
ASSERT_NEAR(val1, val2, abs_error) EXPECT_NEAR(val1, val2, abs_error 两个数值val1和val2的 绝对值差不超过 abs_error
异常检查
ASSERT_THROW(statement, exception_type) EXPECT_THROW(statement, exception_type) 抛出指定类型异常
ASSERT_THROW(statement) EXPECT_THROW(statement) 抛出任意类型异常
ASSERT_NO_THROW(statement) EXPECT_NO_THROW(statement) 不抛异常
函数值与参数检查(目前最多只支持5个参数)
ASSERT_PRED1(pred1, val1) EXPECT_PRED1(pred1, val1) pred1(val1) returns true
ASSERT_PRED2(pred2, val1,val2) EXPECT_PRED2(pred2, val1, val2) pred2(val1, val2) returns true
Windows HRESULT
ASSERT_HRESULT_SUCCEEDED (expression) EXPECT_HRESULT_SUCCEEDED (expression) expression is a success HRESULT
ASSERT_HRESULT_FAILED (expression) EXPECT_HRESULT_FAILED (expression) expression is a failure HRESUL
自定义格式函数与参数检查(目前最多支持5个参数)
ASSERT_PRED_FORMAT1(pred1, val1) EXPECT_PRED_FORMAT1(pred1, val1) pred1(val1) is successful
ASSERT_PRED_FORMAT1(pred1, val1, val2) EXPECT_PRED_FORMAT1(pred1, val1, val2) pred2(val1, val2) is successful
3.4 Gtest的异常检查
通过上面的表格我们知道gtest能够检测异常状态,异常检查一般用到的是 ASSERT_THROW/EXPECT_THROW。那么我们就来增加一个异常检查的测试。
// file MoneyTest.cpp
TEST(MoneyThrowTest, ThrowTest)
{
// Set up
const Money money123AA(123, “AA”);
// Process
Money money123BB(123, "BB");
EXPECT_ANY_THROW(money123BB += money123AA);
EXPECT_THROW(money123BB += money123AA, IncompatibleMoneyError);
}
那么现在的输出结果是:
MoneyThrowTest.ThrowTest显示的状态是通过,证明已经有异常抛出,测试用例 通过。
3.5 Gtest的事件机制
在开始gtest的事件机制之前,我们需要了解一个概念:测试固件!
很多时候,我们想在不同的测试执行前创建相同的配置环境,在测试执行结束后 执行相应的清理工作,测试固件(Test Fixture)为这种需求提供了方便。在单 元测试中,Fixture的作用是为测试创建辅助性的上下文环境,实现测试的初始化 和终结与测试过程本身的分离,便于不同测试使用相同代码来搭建固定的配置环 境。用体操比赛的说法,测试过程体现了特定测试的自选动作,测试固件则体现 了对一系列测试(在开始和结束时)的规定动作。有些讲单元测试的书籍直接把 测试固件称为Scaffolding(脚手架)。使用测试固件比单纯调用TEST宏稍微麻烦 一些:
class MoneyTestEnvironment : public testing::Environment
{
public:
virtual void SetUp()
{
std::cout << “Money Golbal Event SetUp.” << std::endl;
}
virtual void TearDown()
{
std::cout << "Money Global Event TearDown" << std::endl;
}
};
完成继承类方法实现以后,还需要告诉gtest添加全局事件,我们需要在main函数 中通过testing::AddGlobalTestEnvironment方法添加该全局事件。如果需要增加 全局事件,也可以写多个继承类,然后将事件都添加到测试用例之前。
int main(int argc, char** argv)
{
// Add the global test environment
testing::AddGlobalTestEnvironment(new MoneyTestEnvironment);
testing::InitGoogleTest(&argc, argv);
return RUN_ALL_TESTS();
}
运行的结果是:
从结果中我们可以看到Setup()在所有测试用例开始之前就自动被调用,而 TearDown()是在所有测试用例执行之后被调用。所以一些全局的资源初始化放在 SetUp()中,全局的资源回收放在TearDown()中。
• TestSuite事件
在某些情况下,我们可能不需要在整个全局的范围内有一些共享而需要在各个测 试间共享一个相同的环境来保存和传递状态,或者环境的状态是只读的,可以只 初始化一次,再或者创建环境的过程开销很高,要求只初始化一次。共享某个固 件环境的所有测试合称为一个“测试套件”(Test Suite),gtest中利用静态成 员变量和静态成员函数实现这个概念:
// Test Suite Event Test
class MoneySuiteEventTest : public testing::Test
{
public:
static void SetUpTestCase()
{
std::cout << “Money Test SuiteEventTest SetUpTestCase.” << std::endl;
}
static void TearDownTestCase()
{
std::cout << "Money Test SuiteEventTest TearDownTestCase." << std::endl;
}
};
TEST_F(MoneySuiteEventTest, SuiteTestCaseOne)
{
const std::string currencyCC(“SuiteTestCaseOne”);
const double longNumber = 123.456;
// Process
Money money(longNumber, currencyCC);
// Check
EXPECT_EQ(longNumber, money.getAmount());
EXPECT_STREQ("SuiteTestCaseOne", money.getCurrency().c_str());
}
TEST_F(MoneySuiteEventTest, SuiteTestCaseTwo)
{
const std::string currencyCC(“SuiteTestCaseTwo”);
const double longNumber = 123.456;
// Process
Money money(longNumber, currencyCC);
// Check
EXPECT_EQ(longNumber, money.getAmount());
EXPECT_STREQ("SuiteTestCaseTwo", money.getCurrency().c_str());
}
特别注意使用TEST_F宏的第一个参数必须是上面继承testing::test的继承类名字 (MoneySuiteEventTest)。
运行的结果是:
从输出可以看出SetUpTestCase()是在SuitTestCaseOne之前调用而 TearDownTestCase()是在SuiteTestCaseTwo后调用。
• TestCase事件
与TestSuite事件实现方法相同,需要通过继承testing::Test类,但是只需 要实现里面的SetUp和TearDown两个方法
// Test TestCase
class MoneyTestCaseTest : public testing::Test
{
public:
void SetUp()
{
std::cout << “Money Test MoneyTestCaseTest SetUp.” << std::endl;
}
void TearDown()
{
std::cout << "Money Test MoneyTestCaseTest TearDown." << std::endl;
}
};
TEST_F(MoneyTestCaseTest, TestCaseTestOne)
{
const std::string currencyDD(“TestCaseTestOne”);
const double longNumber = 123.456;
// Process
Money money(longNumber, currencyDD);
// Check
EXPECT_EQ(longNumber, money.getAmount());
}
TEST_F(MoneyTestCaseTest, TestCaseTestTwo)
{
const std::string currencyDD(“TestCaseTestTwo”);
const double longNumber = 123.456;
// Process
Money money(longNumber, currencyDD);
// Check
EXPECT_EQ(longNumber, money.getAmount());
}
运行的结果是
3.6 Gtest的参数化
在设计测试案例时,经常需要考虑给被测函数传入不同的值的情况,以前的做 法一般是写一个通用方法,然后编写在测试案例调用它,即使使用了通用方法, 也需要很多重复性的工作。以下是一般的测试方法,如果需要测试N个数字, 则需要拷贝复制粘贴N次.
Gtest在这里提供了一个灵活的函数参数化测试的方案:
// Parameter Testing one
class MoneyParameterTest : public testing::TestWithParam
{
public:
};
TEST_P(MoneyParameterTest, ParameterTest)
{
double acount = GetParam();
Money money(acount, “Test”);
EXPECT_EQ(acount, money.getAmount());
}
INSTANTIATE_TEST_CASE_P(ParameterTest, MoneyParameterTest,
testing::Values(111.111, 222.2222, 333.3333, 444.44444));
运行的结果是:
Gtest中除了上面testing::Values()数据生成器之外,还有一系列的参数生成器:
Gtest中的参数生成器
Range(begin, end[, step]) 范围在begin~end之间,步长为step,不包括end
Values(v1, v2, …, vN) v1,v2到vN的值
ValuesIn(container) and ValuesIn(begin, end) 从一个C类型的数组或是STL容器,或是迭代器中取值
Bool() 取false 和 true 两个值
Combine(g1, g2, …, gN) 这个比较强悍,它将g1,g2,…gN进行排列组合,g1,g2,…gN本身是一个参数生成器,每次分别从g1,g2,…gN中各取出一个值,组合成一个元组(Tuple)作为一个参数。说明:这个功能只在提供了<tr1/tuple>头的系统中有效。gtest会自动去判断是否支持tr/tuple,如果你的系统确实支持,而gtest判断错误的话,你可以重新定义宏GTEST_HAS_TR1_TUPLE=1。
除了测试用例可以参数化以外,gtest还提供了针对各种不同类型数据时的方案, 以及参数化类型的方案
// Parameter Testing two
// Step one
template
class MoneyTemplateTest : public testing::Test
{
public:
typedef std::list List;
static T shared;
T value;
};
// Step two
typedef testing::Types<double, long double> MoneyTypes;
TYPED_TEST_CASE(MoneyTemplateTest, MoneyTypes);
// Step three
TYPED_TEST(MoneyTemplateTest, TemplateTest)
{
// Inside a test, refer to the special name TypeParam to get the type
// parameter. Since we are inside a derived class template, C++ requires
// us to visit the members of MoneyTemplateTest via ‘this’.
TypeParam n = this->value;
// To refer to typedefs in the fixture, add the 'typename TestFixture::'
// prefix. The 'typename' is required to satisfy the compiler.
typename TestFixture::List values;
values.push_back(n);
}
运行结果:
3.7 Gtest的死亡测试
“死亡测试”名字比较恐怖,这里的“死亡”是指程序的崩溃。通常在测试过 程中,我们需要考虑各种各样的输入,有的输入可能直接导致程序崩溃,这时 我们就需要检查程序是否按照预期的方式挂掉,这也就是所谓的“死亡测试”。 gtest的死亡测试能做到在一个安全的环境下执行崩溃的测试案例,同时又对 崩溃结果进行验证
死亡测试宏定义
ASSERT_DEATH(statement, regex) EXPECT_DEATH(statement, regex) statement crashes with the given error
ASSERT_EXIT(statement, predicate, regex) EXPECT_EXIT(statement, predicate, regex) statement exits with the given error and its exit code matches predicate
3.7.1 _DEATH(statement, regex)
由于有些异常只在Debug下抛出,因此还提供了_DEBUGDEATH,用来处理 Debug和Realease下的不同。 简单来说,通过*_DEATH(statement, regex) 和*_EXIT(statement, predicate, regex),我们可以非常方便的编写导致崩溃 的测试案例,并且在不影响其他案例执行的情况下,对崩溃案例的结果进行检 查。 以下是*DEATH用法介绍:
// Death Test
TEST(MoneyDeathTest, DeathTest)
{
// Set up
Money moneyAA(123.456, “DeathTestOne”);
Money moneyBB(0.123456, “DeathTestTwo”);
// Process
EXPECT_DEATH(moneyAA+=moneyBB ,"Test MoneyDeathTest");
}
死亡测试的结果是:
从运行的结果我们可以看到"死亡测试"是第一个被执行的测试!
3.7.2 *_EXIT(statement, predicate, regex)
• 1. statement
• 2. predicate 在这里必须是一个委托,接收int型参数,并返回bool。只有当返回值为true时,死亡测试案例才算通过。
gtest提供了一些常用的predicate:
• testing::FLAGS_gtest_death_test_style = “fast”;
• threadsafe方式
• testing::FLAGS_gtest_death_test_style = “threadsafe”;
你可以在 main() 里为所有的死亡测试设置测试形式,也可以为某次测试单 独设置。Google Test会在每次测试之前保存这个标记并在测试完成后恢复, 所以你不需要去管这部分工作 。如:
int main(int argc, char** argv)
{
// Add the global test environment
testing::AddGlobalTestEnvironment(new MoneyTestEnvironment);
testing::InitGoogleTest(&argc, argv);
testing::FLAGS_gtest_death_test_style = "fast";
return RUN_ALL_TESTS();
}
3.7.4 死亡测试的注意事项
int main(int argc, char** argv)
{
// Add the global test environment
testing::AddGlobalTestEnvironment(new MoneyTestEnvironment);
testing::InitGoogleTest(&argc, argv);
testing::FLAGS_gtest_death_test_style = "fast";
return RUN_ALL_TESTS();
}
这样就拥有了接收和响应gtest工程命令行参数的能力。如果需要在代码中指定 FLAG,可以使用testing::GTESTFLAG这个宏来设置。比如相对于命令行参 数–gtest_output,可以使用testing::GTEST_FLAG(output) = “xml:”;来设置。 注意这里不需要加–gtest前缀了,同时,推荐将这句放置InitGoogleTest之前, 这样就可以使得对于同样的参数,命令行参数优先级高于代码中指定。
// MoneyTest.cpp
int main(int argc, char** argv)
{
// Output farmat is XML
testing::GTEST_FLAG(output) = “xml:”;
// Add the global test environment
testing::AddGlobalTestEnvironment(new MoneyTestEnvironment);
testing::InitGoogleTest(&argc, argv);
testing::FLAGS_gtest_death_test_style = "fast";
return RUN_ALL_TESTS();
}
如果需要gtest的设置系统环境变量,必须注意的是:
#include
#include
#include <gtest\gtest.h>
#include “Money.h”
// EXPECT Testing
TEST(MoneyConstructorTest, TestConstructor)
{
// Set up
const std::string currencyAA(“AA”);
const double longNumber = 123.456;
// Process
Money money(longNumber, currencyAA);
// Check
EXPECT_EQ(longNumber, money.getAmount());
EXPECT_STREQ("AA", money.getCurrency().c_str());
//EXPECT_EQ(555, money.getAmount());
std::cout << "Code after EXPECT!" << std::endl;
}
// End EXPECT Testing
// ASSERT Testing
TEST(MoneyConstructorTest, TestConstructorAssert)
{
// Set up
const std::string currencyBB(“ASSERT”);
const double longNumber = 222.222;
// Process
Money money(longNumber, currencyBB);
// Check
ASSERT_STREQ("AA", money.getCurrency().c_str());
std::cout << "Code after ASSERT!" << std::endl;
}
// End ASSERT Testing
// Throw Testing
TEST(MoneyThrowTest, ThrowTest)
{
// Set up
const Money money123AA(123, “AA”);
// Process
Money money123BB(123, "BB");
EXPECT_ANY_THROW(money123BB += money123AA);
EXPECT_THROW(money123BB += money123AA, IncompatibleMoneyError);
}
// End Throw Testing
// Global Event Testing
class MoneyTestEnvironment : public testing::Environment
{
public:
virtual void SetUp()
{
std::cout << “Money Global Event SetUp.” << std::endl;
}
virtual void TearDown()
{
std::cout << "Money Global Event TearDown" << std::endl;
}
};
// End Global Event Testing
// Suite Event Testing
class MoneySuiteEventTest : public testing::Test
{
public:
static void SetUpTestCase()
{
std::cout << “Money Test SuiteEventTest SetUpTestCase.” << std::endl;
}
static void TearDownTestCase()
{
std::cout << "Money Test SuiteEventTest TearDownTestCase." << std::endl;
}
};
TEST_F(MoneySuiteEventTest, SuiteTestCaseOne)
{
const std::string currencyCC(“CC”);
const double longNumber = 123.456;
// Process
Money money(longNumber, currencyCC);
// Check
EXPECT_EQ(longNumber, money.getAmount());
EXPECT_STREQ("CC", money.getCurrency().c_str());
}
TEST_F(MoneySuiteEventTest, SuiteTestCaseTwo)
{
const std::string currencyCC(“DD”);
const double longNumber = 123.456;
// Process
Money money(longNumber, currencyCC);
// Check
EXPECT_EQ(longNumber, money.getAmount());
EXPECT_STREQ("DD", money.getCurrency().c_str());
}
// End Suite Event Testing
// TestCase Event Testing
class MoneyTestCaseTest : public testing::Test
{
public:
void SetUp()
{
std::cout << “Money Test MoneyTestCaseTest SetUp.” << std::endl;
}
void TearDown()
{
std::cout << "Money Test MoneyTestCaseTest TearDown." << std::endl;
}
};
TEST_F(MoneyTestCaseTest, TestCaseTestOne)
{
const std::string currencyDD(“TestCaseTestOne”);
const double longNumber = 123.456;
// Process
Money money(longNumber, currencyDD);
// Check
EXPECT_EQ(longNumber, money.getAmount());
}
TEST_F(MoneyTestCaseTest, TestCaseTestTwo)
{
const std::string currencyDD(“TestCaseTestTwo”);
const double longNumber = 123.456;
// Process
Money money(longNumber, currencyDD);
// Check
EXPECT_EQ(longNumber, money.getAmount());
}
// End TestCase Event Testing
// Parameter Testing one
class MoneyParameterTest : public testing::TestWithParam
{
public:
};
TEST_P(MoneyParameterTest, ParameterTest)
{
double acount = GetParam();
Money money(acount, “Test”);
EXPECT_EQ(acount, money.getAmount());
}
INSTANTIATE_TEST_CASE_P(ParameterTest, MoneyParameterTest,
testing::Values(111.111, 222.2222, 333.3333, 444.44444));
// End Parameter Testing one
// Parameter Extual Testing two
// Step one
template
class MoneyTemplateTest : public testing::Test
{
public:
typedef std::list List;
static T shared;
T value;
};
// Step two
typedef testing::Types<double, long double> MoneyTypes;
TYPED_TEST_CASE(MoneyTemplateTest, MoneyTypes);
// Step three
TYPED_TEST(MoneyTemplateTest, TemplateTest)
{
// Inside a test, refer to the special name TypeParam to get the type
// parameter. Since we are inside a derived class template, C++ requires
// us to visit the members of MoneyTemplateTest via ‘this’.
TypeParam n = this->value;
// To refer to typedefs in the fixture, add the 'typename TestFixture::'
// prefix. The 'typename' is required to satisfy the compiler.
typename TestFixture::List values;
values.push_back(n);
}
// End Parameter Extual Testing two
// Death Test
TEST(MoneyDeathTest, DeathTest)
{
// Set up
Money moneyAA(123.456, “DeathTestOne”);
Money moneyBB(0.123456, “DeathTestTwo”);
// Process
EXPECT_DEATH(moneyAA+=moneyBB ,"Test MoneyDeathTest");
}
// End Death Test
int main(int argc, char** argv)
{
testing::GTEST_FLAG(output) = “xml:”;
// Add the global test environment
testing::AddGlobalTestEnvironment(new MoneyTestEnvironment);
testing::InitGoogleTest(&argc, argv);
testing::FLAGS_gtest_death_test_style = "fast";
return RUN_ALL_TESTS();
}
4 作业
4.1 编写一个DemoContainer类,按以下接口要求实现: