gtest是Google的C++测试框架,可以帮助开发者更简单快捷得写好C++单元测试。并且无论你是工作在Linux,Windows还是Mac上。
如果你曾写过单元测试,无论用的什么语言,相信你很快就能上手。如果还不了解,那么就从这里开始吧。
如果你想更深入了解测试框架,可以看Kent Beck大神的《测试驱动开发》。
gtest_start/
├─build/ # 构建文件
├─lib/ # 第三方库
├─output/ # 输出目录(中间文件,执行文件)
│ ├─gtest/
│ └─primer/
│ ├─Debug
│ │ └─obj/
│ └─Release
├─src/ # 工程代码
│ └─primer/
├─third_party/ # 第三方库代码
│ └─gtest/
└─tools/ # 工具脚本
这样清理方便些。
# 准备目录
mkdir -p gtest_start/third_party/
cd gtest_start/third_party/
# 获取源码
svn co http://googletest.googlecode.com/svn/trunk/ gtest
gtest本身提供了多平台的构建文件,如下:
We provide build files for some popular build systems: msvc/ for Visual Studio, xcode/ for Mac Xcode, make/ for GNU make, codegear/ for Borland C++ Builder, and the autotools script (deprecated) and CMakeLists.txt for CMake (recommended) in the Google Test root directory.
如果你用的不是以上这些,则可以看make/Makefile或msvc/gtest.sln工程,参考配置。
主要是将gtest-all.cc生成静态库。如果带gtest_main.cc,就省去了写main函数。
Step 1: 创建一个简单函数:
bool IsEven(int num) {
return num % 2 == 0;
}
Step 2: 用TEST()宏定义并命名一个测试方法:
TEST(test_case_name, test_name) {
... test body ...
}
对于IsEven()
,可以这样:
TEST(IsEvenTest, TestA) {
ASSERT_TRUE(IsEven(2));
ASSERT_TRUE(IsEven(3));
}
Step 3: 工程引入gtest_main静态库,运行即可。Linux下记得添加-lpthread
。
运行结果
Running main() from gtest_main.cc
[==========] Running 1 test from 1 test case.
[----------] Global test environment set-up.
[----------] 1 test from IsEvenTest
[ RUN ] IsEvenTest.TestA
[ OK ] IsEvenTest.TestA (0 ms)
[----------] 1 test from IsEvenTest (1 ms total)
[----------] Global test environment tear-down
[==========] 1 test from 1 test case ran. (2 ms total)
[ PASSED ] 1 test.
ASSERT_*()宏,当失败时,会生成致命错误,退出当前TEST()。
ASSERT*()宏相应都会有个EXPECT*()版本,当失败时,其生成的是非致命错误,会继续当前TEST()。
例如
TEST(IsEvenTest, TestA) {
cout << "IsEvenTest start" << endl;
ASSERT_TRUE(IsEven(3)); // failed
ASSERT_FALSE(IsEven(3));
cout << "IsEvenTest end" << endl;
}
TEST(PowerTest, TestA) {
cout << "PowerTest start" << endl;
EXPECT_EQ(9, Power(2, 3)); // failed, expected: 8
EXPECT_EQ(27, Power(3, 3));
cout << "PowerTest end" << endl;
}
运行结果
# ...
[ RUN ] IsEvenTest.TestA
IsEvenTest start
..\src\primer\simple_unittest.cc(10): error: Value of: IsEven(3)
Actual: false
Expected: true
[ FAILED ] IsEvenTest.TestA (3 ms)
# ...
[ RUN ] PowerTest.TestA
PowerTest start
..\src\primer\simple_unittest.cc(17): error: Value of: Power(2, 3)
Actual: 8
Expected: 9
PowerTest end
[ FAILED ] PowerTest.TestA (4 ms)
# ...
[ FAILED ] 2 tests, listed below:
# ...
注意:没有输出"IsEvenTest end”,但有输出"PowerTest end”。
不过只要是失败,都会被统计进最后的tests失败列表。
ASSERT_*()基本的一些宏,请见参考1的Assertions。
当你写的很多测试都用类似的数据作为输入时,这时应当考虑用test fixture。
例如
template<typename T = Data>
class Calculator {
public:
T Plus(T lhs, T rhs) { return lhs + rhs; }
T Minus(T lhs, T rhs) { return lhs - rhs; }
T Multiplies(T lhs, T rhs) { return lhs * rhs; }
T Divides(T lhs, T rhs) { return lhs / rhs; }
};
测试Plus、Minus、Multiplies、Divides时,可以都用两个同样的Data作为数据。这时,你可以创建一个测试套件:
Step 1: 继承::testing::Test
。且以protected:
或public:
开始,以使子类可以访问。
class CalculatorTest : public ::testing::Test {
protected:
// ...
};
Step 2: 声明你要用的数据。这儿是Data:
Data* data_a_;
Data* data_b_;
// 以下为对ab进行四种操作后的结果:
Data plus_ab;
Data minus_ab;
Data multiplies_ab;
Data divides_ab;
Step 3: 在默认构造函数或SetUp()
函数内准备好数据:
CalculatorTest()
: plus_ab(10),
minus_ab(-6),
multiplies_ab(16),
divides_ab(0) {
}
// Sets up the test fixture.
virtual void SetUp() {
data_a_ = new Data(2);
data_b_ = new Data(8);
}
Step 4: 在析构函数或TearDown()
函数内释放数据:
virtual ~CalculatorTest() {
}
// Tears down the test fixture.
virtual void TearDown() {
delete data_a_;
data_a_ = nullptr;
delete data_b_;
data_b_ = nullptr;
}
Step 5: 用TEST_F()宏来做测试,它允许你访问套件内成员:
TEST_F(CalculatorTest, Plus) {
Calculator<> calculator;
EXPECT_EQ(plus_ab, calculator.Plus(*data_a_, *data_b_));
}
运行结果
Running main() from gtest_main.cc
[==========] Running 1 test from 1 test case.
[----------] Global test environment set-up.
[----------] 1 test from CalculatorTest
[ RUN ] CalculatorTest.Plus
[ OK ] CalculatorTest.Plus (0 ms)
[----------] 1 test from CalculatorTest (1 ms total)
[----------] Global test environment tear-down
[==========] 1 test from 1 test case ran. (4 ms total)
[ PASSED ] 1 test.
但这里,如果同时测试四个,Calculator<> calculator;
岂不是要创建四次。因此我们可以考虑将它共享:
Step 1: 定义为一个static
成员:
class CalculatorTest : public ::testing::Test {
protected:
// ...
// Shared by all tests.
static Calculator<>* shared_calculator_;
};
当然,别忘记初始化:
Calculator<>* CalculatorTest::shared_calculator_ = nullptr;
Step 2: 在static SetUpTestCase()
函数内准备数据:
// Sets up the stuff shared by all tests in this test case.
static void SetUpTestCase() {
shared_calculator_ = new Calculator<>;
}
Step 3: 在static TearDownTestCase()
函数内释放数据:
// Tears down the stuff shared by all tests in this test case.
static void TearDownTestCase() {
delete shared_calculator_;
shared_calculator_ = nullptr;
}
Step 4: 然后,TEST_F()宏做测试:
TEST_F(CalculatorTest, Plus) {
EXPECT_EQ(plus_ab, shared_calculator_->Plus(*data_a_, *data_b_));
}
上述是同一个测试用例下共享数据。另外还有全局环境的,见Global Set-Up and Tear-Down。
本文仅仅是初步体验了gtest,另外还有死亡测试,事件监听,以及重复测试、临时禁用等选项。具体请见参考2。
除此之外,其他一些有用的文章也列在了参考里。
以上两个官方文档,非常详细。有必要阅览一遍,并可作为手册。
另外,这个系列也很不错:玩转Google开源C++单元测试框架Google Test系列。把官方文档主要的一些内容都讲述了遍。
还有IBM developerWorks 中国上的几篇:
下载:gtest_start.zip。
build/gtest_start-gcc.cbp # for gnu gcc
build/gtest_start-msvc.cbp # for msvc 2010
.cbp由C::B打开即可。
“src/primer/“下为本文例子,而"src/thoughts/“下是我以前写的内容:
├─src/
│ ├─primer/ # 本文例子
│ └─thoughts/
│ ├─designpattern/ # C++编程思想设计模式一节例子及其测试
│ ├─gtest/ # gtest原生sample,queue测试例子
│ └─stl/ # stl的测试例子
“designpattern/“下例子,“-Wall"有很多警告,也确实是有些问题的,不用太在意。
参考
Linux下运行程序时,报出”/usr/lib/libstdc++.so.6: version 'GLIBCXX_3.4.14' not found"的问题。
先前我升级GCC到了4.8.2,且只是软链接到了高版本的gcc,g++。不知道还要对应升级下libstdc++库。见:【笔记】CentOS上源码安装GCC 4.8.2。
Step 1: 命令检查libstdc++.so使用的GLIBC版本:
strings /usr/lib/libstdc++.so.6 | grep GLIB
输出:
GLIBCXX_3.4
GLIBCXX_3.4.1
...
GLIBCXX_3.4.12
GLIBCXX_3.4.13
GLIBC_2.0
...
GLIBC_2.2
GLIBCXX_FORCE_NEW
GLIBCXX_DEBUG_MESSAGE_LENGTH
没发现'GLIBCXX_3.4.14'。
Step 2: 检查/usr/lib目录下的libstdc++的库文件:
ll /usr/lib/libstdc++*
输出:
lrwxrwxrwx. 1 root root 19 Mar 18 04:57 /usr/lib/libstdc++.so.6 -> libstdc++.so.6.0.13
-rwxr-xr-x. 1 root root 942040 Nov 21 07:28 /usr/lib/libstdc++.so.6.0.13
检查下安装:
yum list libstdc++*
yum install libstdc++
已经是最高版本了。
Step 3: 由GCC版本,搜索了下'libstdc++-4.8.2'。
gcc --version
# gcc (GCC) 4.8.2
发现这篇:Installation of Target Libstdc++,看命令源码目录下有'libstdc++-v3',确实。
cd /home/join/Env/gcc/gcc-4.8.2/
find ./ -name libstdc++.so*
输出:
./stage1-i686-pc-linux-gnu/libstdc++-v3/src/.libs/libstdc++.so.6
./stage1-i686-pc-linux-gnu/libstdc++-v3/src/.libs/libstdc++.so
./stage1-i686-pc-linux-gnu/libstdc++-v3/src/.libs/libstdc++.so.6.0.18
./i686-pc-linux-gnu/libstdc++-v3/src/.libs/libstdc++.so.6
./i686-pc-linux-gnu/libstdc++-v3/src/.libs/libstdc++.so
./i686-pc-linux-gnu/libstdc++-v3/src/.libs/libstdc++.so.6.0.18
./prev-i686-pc-linux-gnu/libstdc++-v3/src/.libs/libstdc++.so.6
./prev-i686-pc-linux-gnu/libstdc++-v3/src/.libs/libstdc++.so
./prev-i686-pc-linux-gnu/libstdc++-v3/src/.libs/libstdc++.so.6.0.18
我这边的环境,有三个路径下有libstdc++.so.6.0.18,libstdc++.so与libstdc++.so.6只是其软链接。
查看各路径下libstdc++.so.6.0.18状态:
stat libstdc++.so.6.0.18
发现各路径下libstdc++.so.6.0.18都一样大小。stage1前缀路径下最早生成,之后prev前缀的,不带前缀的最晚。除了生成时间,另有个Inode不同,存储在文件系统的索引节点(必然的)。
不清楚Inode,所以了解了下:inode,理解inode。
Step 4: 替换老版本libstdc++库:
# 复制到'/usr/lib/'
cd ./i686-pc-linux-gnu/libstdc++-v3/src/.libs/
cp libstdc++.so.6.0.18
cp libstdc++.so.6.0.18 /usr/lib/
# 重新建立软链接
rm -f libstdc++.so.6
ln -s libstdc++.so.6.0.18 libstdc++.so.6
参考