Googletest入门
简介:为什么使用GoogleTest
googletest可以帮助你写出更好的c++测试用例
googletest是谷歌测试技术团队为谷歌特殊定制的,符合谷歌开发思想的一套测试框架。不论你工作在Linux,Windows还是Mac上,只要你书写C++代码,googletest就可以帮助你。googletest不仅仅在单元测试上可以帮助你,同时也可以支持你做各种其他种类的测试。
所以,组成一个好的测试的是什么呢,googltest有时如何适配的呢?
1.测试用例应该是具有独立性和可重复性的。调试测试用例为成功或者失败是一个痛苦的过程。googltest通过生成不同的对象,来保证每一个用例运行的独立性。当一个测试用例失败时,googltest同样允许它独立运行以进行快速调试;
2.测试用例应该具有良好的组织,并且可以清晰的反映出测试代码结构。googletest使用testsuite组织具有关联性的相关用例,并且使同属一个test suite的用例可以共享数据和子进程。这种常规的组织方式便于用例的查阅理解同时可以使测试用例易于维护。这种特性对于经常需要切换基线测试代码的人员非常方便。
3.测试用例应该具有稳定性和重用性。谷歌有很多跨平台的代码;所以测试用例也应该可以支持跨平台特性。googletest可以在不同平台工作,使用不同的编译器,只需通过使用不同的配置即可实现。
4.当测试用例失败是, 测试框架应该给出尽可能详尽的信息。googletest不会再第一个测试用例失败时就停止。相反,googletest只会停止当前测试用例,并继续下一条用例的执行。当然,再当前测试用例执行时,你也可以设置googletest报告非致命故障。因此,你可以通过这个特性,再单个的“运行-编辑-编译”周期中,检查和修复多个错误。
5.测试框架应该讲测试用例编写人员从用例流程轮子的书写中解放出来,让测试人员专注于测试内容本身。googletest会自动“注册”所有测试用例,不需要测试开发人员列出所有待测用例即可运行它们。
6.测试用例的开发应该是快速而敏捷的。使用googletest,你可以再不同测试用例之间共享,重用数据,并且可以仅仅定义一次setup(初始化)/teardown(环境恢复),让每一条测试用例均相互独立,互不影响。
由于googletest是基于流行的xUnit架构开发的,因此,如果你使用过JUnit或者PyUnit,你将会感觉googletest非常熟悉。当然如果你没有使用过上述两种框架,你也可以花10分钟学习一些基本命令,并开始googletest的测试开发之旅。
一些需要注意声明
注意:可能再Googletest的定义中存在与大众认知中的Test,TestCase和TestSuite有一定的区别,这可能会造成一定的误解
由于历史原因,googltest使用TestCase定义了一组具有关联的测试用例,然而现行的出版物和国际软件测试质量组织中使用了不一样的文字定义了这个定义,那就是TestSuite。
而再googletest中Test这一定义是作为ISTQB中的TestCase的意义。
Test这一定义在ISTQB中的定义也是TestCase的意思,所以在这里不会产生多大问题。
googletest目前已经开始启动了一些替换工作,用来吧TestCase的相关定义替换为TestSuite用来减少在理解上产生的相关偏差。
所以要注意googletest中定义的TEST()等同于ISTQB组织中认为的TestCase。而googletest中的TestCase则是代表ISTQB组织中所认为的TestSuite。
基础原则
当你开始使用googletest的时,你可能开始书写assertions,assertion可以帮助我们判断某种状态是否为真。assertions的结果可以有三种,成功,非致命错误和致命错误。如果一个致命错误出现,将会停止当前执行的功能,否则,程序则会继续。
Test通过使用assertions来确定代码的运行行为,如果一个Test崩溃了或者进入了一个失败的assertion判断,那么测试用例就会失败,否则测试用例会执行陈工。
一个test suite包含一个或多个测试用例。我们应该将测试代码使用test suite组织起来,使测试代码拥有清晰的结构。当多个测试用例存在于同一个test suite中时,需要共享公共对象和子进程,我们可以把这些定义于test fixture类中。
一个测试程序,可以包含多个test suite(测试套)
现在我们将从开始讲解如何书写一个自己的assertion并且建立测试用例和测试套入手,来讲解如何书写一个测试程序。
Assertions(断言)
googletest的assertion是一个类似于函数调用的宏定义。你可以使用assertion来测试一个类或者一个函数的行为。当一个assertion对象执行失败,googletest会打印assertion的源文件和所在行号,同时也会打印一些失败信息。你也可以在googletest的打印信息上添加一些客制化的失败信息。
我们同样拥有两种形式的assertions(断言),它们做了相同的事情,但对当前执行函数存在不同形式的影响。ASSERT_*
版本产生失败时,会终止当前函数执行。EXPECT__*
版本产生非致命失败时不会终止当前函数的执行。因此,EXPECT__*
更加试用于我们的测试用例的开发,因为它允许我们可以捕获更多中类型的失败信息。不过,我们在出现错误后是否继续运行程序对我们没有意义的情况下,我们也应该积极使用ASSERT_*
。
然而一个失败的ASSERT_*
,会导致当前执行的程序立刻停止,可能跳过在它之后的清理程序,这可能会引起我们的测试代码健壮性减弱。因此,我们应当一直保有除了ASSERT以外的其他检查器,来标识更多的错误;
因此,Gtest提供了一种自定义的错误信息打印方式,这种方式只需简单的在宏定义后面使用<<
运算符或者在这之后的一系列运算符。下面我们针对ASSERT_EQ
和EXPECT_EQ
这两个宏分别提供了两个示例:
ASSERT_EQ(x.size(), y.size()) << "Vectors x and y are of unequal length";
for (int i = 0; i < x.size(); ++i) {
EXPECT_EQ(x[i], y[i]) << "Vectors x and y differ at index " << i;
}
任何可以被包含进入ostream
的对象,尤其类似C strings或者string
对象都可以使用宏定义之后的流式打印。如果一个宽类型的string(wchar_t*
,TCHAR*
使用在windows上UNICODE
编码或者是std::wstring
这样的对象)被一个assertion编入流中,它将会被翻译为UTF-8并打印。
GoogleTest提供了一组断言为我们的测试代码行为做判断。你可以检查布尔型状态,通过运算符比较值,判断字符串的值,浮点型值的判断等等。甚至可以进行用户自定义的相关比较。更高级的功能请参看Assertions Reference;
简单的测试
如何创建测试用例:
1.使用TEST()
这个宏来顶一个一个测试用例的名字,这可以创建一个普通的C++函数,这个函数不会有返回值。
2.在这个函数中,可以包含任何合法的C++代码,同时你也可以使用各种,googletest提供的断言,来帮助检查某些值的正确性。
3.断言可以决定测试用例的结果;如果在测试用例中的任何断言失败(不论是致命失败还是非致命失败),都会导致测试用例结果的失败,否则测试用例执行结果成功;
TEST(TestSuiteName, TestName) {
... test body ...
}
TEST()
的参数,第一个参数是test suite的名字,第二个参数是test的名字,两个参数必须符合C++命名规范。他们均不建议包含下划线(_
)。一个测试用例的完成名称是由包含它的test suite和它自己的名字组成的。来自不同测试套的测试用例可以拥有相同的名字。
我们现在使用如下的一个简单例子进行说明
int Factorial(int n); // Returns the factorial of n
一个测试套可能如下:
// Tests factorial of 0.
TEST(FactorialTest, HandlesZeroInput) {
EXPECT_EQ(Factorial(0), 1);
}
// Tests factorial of positive numbers.
TEST(FactorialTest, HandlesPositiveInput) {
EXPECT_EQ(Factorial(1), 1);
EXPECT_EQ(Factorial(2), 2);
EXPECT_EQ(Factorial(3), 6);
EXPECT_EQ(Factorial(8), 40320);
}
googletest通过test suite来组织测试用例的相关结果,所以,逻辑上相同的测试用例应该在同一个test suite当中。换言之,TEST()
的第一个参数应当相同,在上面的例子中我们拥有两个测试用例,HandlesZeroInput
,HandlesPositiveInput
,同属于同一个测试套FactorialTest
.
当命名你的测试用例和测试套时应遵循如下建议:
Test Fixtures:使用相同数据配置的多个测试用例
如果你发现你想写两个或者更多的测试用例,但这些用例使用类似的数据,你可以使用一个test fixture.这可以允许你再几个不同的用例中复用相同的配置。
如何创建一个fixture:
1.创建一个类,继承::testing::Test
。并且使用protected:
,开始函数体,因为,我们需要让fixture成员再子类中继续使用。
2.再类中,声明任意你计划使用的相关对象。
3.如果有必要,写一个默认的构造函数,或Setup()
函数,这个函数为每一个测试用例进行了数据准备工作。一个经常犯的错误是把SetUp()
拼写为Setup()
函数,因此,建议使用override
来确保拼写正确。
4.如果需要的话,我们同样可以书写一个析构函数或者TearDown()
函数去释放一些在SetUp()
函数中创建的一些资源。怎样创建和使用构造,析构函数,请参见FAQ
5.如果需要,也可以为自己的测试用例创建子例程。
当我们使用fixture时,我们将会使用TEST_F()
来代替 TEST()
,去创建对象和子进程。
TEST_F(TestFixtureName, TestName) {
... test body ...
}
使用TEST_F()
与TEST()
的使用类似,但是第一个名字必须是test fixture类的类名,你也可以认为_F
代表fixture。
令人遗憾的是,C++的宏系统不允许我们使用同一个宏去处理两种类型的测试用例。因此,错误的使用了宏将会导致编译错误。
因此,在使用TEST_F()
前,我们必须先定义fixture类否则,我们将会得到一个编译错误:“virtual outside class declaration
”.
对于每一个TEST_F()
所定义的测试用例,googletest会同时创建一个全新的test fixture,同时立即使用它的SetUp
函数进行初始化,然后运行测试,通过TearDown()
进行环境清理,然后删除test fixture。请注意,在同一测试套中的测试用例使用的是不同的test fixture对象,googletest会在生产下一个测试用例的test fixture前删除前一个用例的test fixture,googletes从不为多个测试用例重用test fixture。所有测试用例对test fixture的改变,都不会影响其他测试用例的执行。
以下是一个例子
template <typename E> // E is the element type.
class Queue {
public:
Queue();
void Enqueue(const E& element);
E* Dequeue(); // Returns NULL if the queue is empty.
size_t size() const;
...
};
首先先定一个一个名为Foo的test fixture类
class QueueTest : public ::testing::Test {
protected:
void SetUp() override {
q1_.Enqueue(1);
q2_.Enqueue(2);
q2_.Enqueue(3);
}
// void TearDown() override {}
Queue<int> q0_;
Queue<int> q1_;
Queue<int> q2_;
};
在这里我们无需定义TearDown()
方法,因为析构函数已经替我们在每个测试之后进行了必要的环境清理工作了。
目前我们可以开始书写相关测试用例了
TEST_F(QueueTest, IsEmptyInitially) {
EXPECT_EQ(q0_.size(), 0);
}
TEST_F(QueueTest, DequeueWorks) {
int* n = q0_.Dequeue();
EXPECT_EQ(n, nullptr);
n = q1_.Dequeue();
ASSERT_NE(n, nullptr);
EXPECT_EQ(*n, 1);
EXPECT_EQ(q1_.size(), 0);
delete n;
n = q2_.Dequeue();
ASSERT_NE(n, nullptr);
EXPECT_EQ(*n, 2);
EXPECT_EQ(q2_.size(), 1);
delete n;
}
当这些测试用例运行时,会发生下面的相关事项:
1.googletest 构造了一个QueueTest
对象(我们暂时简称为t1
)
2.t1.SetUp()
初始化t1
3.第一个测试用例在t1
上运行
4.t1.TearDown()
开始清理测试环境
5.t1
对象被销毁
6.上述步骤,在其他用例执行时进行重复