作为一个TDD的脑残粉,凡事都想测试先行。现在回归C++开发,顿时觉得各种不方便。特别是手头没有好用的单元测试框架,开发效率简直无法忍受,写好的代码重构起来也畏手畏脚。
曾经尝试过CppUnit,作为JUnit的同胞,出自名门,但在C++开发中,尤其在VS环境下实在是太难用了(主要是由于 C++ 没有反射机制,而这是 JUnit 设计的基础)。偶然搜得googletest,一套由 google 发布的开源单元测试框架。
应用 googletest 编写单元测试代码
googletest 是由 Google 公司发布,且遵循 New BSD License (可用作商业用途)的开源项目,并且 googletest 可以支持绝大多数大家所熟知的平台。与 CppUnit 不同的是: googletest 可以自动记录下所有定义好的测试,不需要用户通过列举来指明哪些测试需要运行。
定义单元测试
在应用 googletest 编写单元测试时,使用 TEST()
宏来声明测试函数。如:
清单 1. 用 TEST()
宏声明测试函数
TEST(GlobalConfigurationTest, configurationDataTest)
TEST(GlobalConfigurationTest, noConfigureFileTest)
分别针对同一程序单元 GlobalConfiguration
声明了两个不同的测试函数,以分别对配置数据进行检查configurationDataTest
,以及测试没有配置文件的特殊情况noConfigureFileTest
。
实现单元测试
针对同一程序单元设计出不同的测试场景后(即划分出不同的 Test 后),开发者就可以编写单元测试分别实现这些测试场景了。
在 googletest 中实现单元测试,可通过 ASSERT_*
和 EXPECT_*
断言来对程序运行结果进行检查。 ASSERT_*
版本的断言失败时会产生致命失败,并结束当前函数; EXPECT_*
版本的断言失败时产生非致命失败,但不会中止当前函数。因此, ASSERT_*
常常被用于后续测试逻辑强制依赖的处理结果的断言,如创建对象后检查指针是否为空,若为空,则后续对象方法调用会失败;而 EXPECT_*
则用于即使失败也不会影响后续测试逻辑的处理结果的断言,如某个方法返回结果的多个属性的检查。
googletest 中定义了如下的断言:
表 1: googletest 定义的断言( Assert )
基本断言 | 二进制比较 | 字符串比较 |
---|---|---|
ASSERT_TRUE(condition); condition为真 | ASSERT_EQ(expected,actual); expected==actual | ASSERT_STREQ(expected_str,actual_str); 两个 C 字符串有相同的内容 |
EXPECT_TRUE(condition); condition为真 | EXPECT_EQ(expected,actual); expected==actual | EXPECT_STREQ(expected_str,actual_str); 两个 C 字符串有相同的内容 |
ASSERT_FALSE(condition); condition为假 | ASSERT_NE(val1,val2); val1!=val2 | ASSERT_STRNE(str1,str2); 两个 C 字符串有不同的内容 |
EXPECT_FALSE(condition); condition为假 | EXPECT_NE(val1,val2); val1!=val2 | EXPECT_STRNE(str1,str2); 两个 C 字符串有不同的内容 |
ASSERT_LT(val1,val2); val1<val2 | ASSERT_STRCASEEQ(expected_str,actual_str); 两个 C 字符串有相同的内容,忽略大小写 | |
EXPECT_LT(val1,val2); val1<val2 | EXPECT_STRCASEEQ(expected_str,actual_str); 两个 C 字符串有相同的内容,忽略大小写 | |
ASSERT_LE(val1,val2); val1<=val2 | ASSERT_STRCASENE(expected_str,actual_str); 两个 C 字符串有不同的内容,忽略大小写 | |
EXPECT_LE(val1,val2); val1<=val2 | EXPECT_STRCASENE(expected_str,actual_str); 两个 C 字符串有不同的内容,忽略大小写 | |
ASSERT_GT(val1,val2); val1>val2 | ||
EXPECT_GT(val1,val2); val1>val2 | ||
ASSERT_GE(val1,val2); val1>=val2 | ||
EXPECT_GE(val1,val2); val1>=val2 |
下面的实例演示了上面部分断言的使用:
清单 2. 一个较完整的 googletest 单元测试实例
// Configure.h
#pragma once
#include <string>
#include <vector>
class Configure
{
private:
std::vector<std::string> vItems;
public:
int addItem(std::string str);
std::string getItem(int index);
int getSize();
};
// Configure.cpp
#include "Configure.h"
#include <algorithm>
/**
* @brief Add an item to configuration store. Duplicate item will be ignored
* @param str item to be stored
* @return the index of added configuration item
*/
int Configure::addItem(std::string str)
{
std::vector<std::string>::const_iterator vi=std::find(vItems.begin(), vItems.end(), str);
if (vi != vItems.end())
return vi - vItems.begin();
vItems.push_back(str);
return vItems.size() - 1;
}
/**
* @brief Return the configure item at specified index.
* If the index is out of range, "" will be returned
* @param index the index of item
* @return the item at specified index
*/
std::string Configure::getItem(int index)
{
if (index >= vItems.size())
return "";
else
return vItems.at(index);
}
/// Retrieve the information about how many configuration items we have had
int Configure::getSize()
{
return vItems.size();
}
// ConfigureTest.cpp
#include <gtest/gtest.h>
#include "Configure.h"
TEST(ConfigureTest, addItem)
{
// do some initialization
Configure* pc = new Configure();
// validate the pointer is not null
ASSERT_TRUE(pc != NULL);
// call the method we want to test
pc->addItem("A");
pc->addItem("B");
pc->addItem("A");
// validate the result after operation
EXPECT_EQ(pc->getSize(), 2);
EXPECT_STREQ(pc->getItem(0).c_str(), "A");
EXPECT_STREQ(pc->getItem(1).c_str(), "B");
EXPECT_STREQ(pc->getItem(10).c_str(), "");
delete pc;
}
运行单元测试
在实现完单元测试的测试逻辑后,可以通过 RUN_ALL_TESTS()
来运行它们,如果所有测试成功,该函数返回 0,否则会返回 1 。 RUN_ALL_TESTS()
会运行你链接到的所有测试――它们可以来自不同的测试案例,甚至是来自不同的文件。
因此,运行 googletest 编写的单元测试的一种比较简单可行的方法是:
为每一个被测试的 class 分别创建一个测试文件,并在该文件中编写针对这一 class 的单元测试;
编写一个 Main.cpp
文件,并在其中包含以下代码,以运行所有单元测试:
清单 3. 初始化 googletest 并运行所有测试
#include <gtest/gtest.h>
int main(int argc, char** argv) {
testing::InitGoogleTest(&argc, argv);
// Runs all tests using Google Test.
return RUN_ALL_TESTS();
}
最后,将所有测试代码及 Main.cpp
编译并链接到目标程序中。
此外,在运行可执行目标程序时,可以使用 --gtest_filter
来指定要执行的测试用例,如:
./foo_test
没有指定filter
,运行所有测试;./foo_test --gtest_filter=*
指定filter
为*
,运行所有测试;./foo_test --gtest_filter=FooTest.*
运行测试用例FooTest
的所有测试;./foo_test --gtest_filter=*Null*:*Constructor*
运行所有全名(即测试用例名 + “ . ” + 测试名,如GlobalConfigurationTest.noConfigureFileTest
)含有”Null” 或”Constructor” 的测试;./foo_test --gtest_filter=FooTest.*-FooTest.Bar
运行测试用例FooTest
的所有测试,但不包括FooTest.Bar
。 这一特性在包含大量测试用例的项目中会十分有用。
关于 googletest 的更多信息,请访问其项目主页:http://code.google.com/p/googletest/