全文分红三部分:第一部分介绍GoogleTest框架;第二部分是测试;第三部分是我的感想。node
继续吐槽博客园的Markdown。。不如其余博客的好用,格式很容易乱。。。。c++
终于调整好格式了。。。。。。博客园的markdown 好low low low啊git
首先,给出官方文档的连接。如下内容主要是我本身翻译的官方文档并结合本身的理解,总结出的基本要点。若是想要对技术更深刻的了解,建议仍是去看官方文档。最后说明一下,这里的内容仅仅在Linux系统的上执行过,其余的系统应该也是按照相似的步骤进行。Linux下关于如何安装GoogleTest框架,请参考我在CSDN的这篇博客github
使用GoogleTest要先从学习写断言开始,断言用于检测一个测试条件是否为真。断言的结果有三种状况:success, nonfatal failure, fatal failure。若是 fatal failure出现,它将会打断当前的函数;不然程序会正常运行。
一个测试实例能够包含多个测试,咱们须要把这些测试组织成合理的结构。当多个测试实例须要共享公共对象和或者子程序,咱们能够把他们组织到一个测试类中。算法
GoogleTest的断言是一种相似于函数调用的断言机制。咱们能够在GoogleTest自己的断言消息后面定义本身的测试失败信息。下面说明几种不一样断言方式:数组
ASSERT_*
产生fatal failures
,直接终止当前函数的运行EXPECT_*
: 产生nonfatal failures
,不会终止当前函数运行EXPECT_*
: 最经常使用的一种方式,能够容许报告产生一个或者多个failer
为了提供自定义的失败信息,可使用C++的stream流操做把字符输入到断言中,借助于<<
操做符号。
好比:markdown
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的字符串、string对象,甚至能够是广义上的字符(流):Windows的Unicode下的wchar_t、 TCHAR或者C++的std::string
。全部流的输入都会转化成UTF-8
的格式。框架
Name | Academy | score |
---|---|---|
ASSERT_TRUE(condition) | EXPECT_TRUE(condition) | condition is true |
ASSERT_FALSE(condition) | EXPECT_FALSE(condition) | condition is false |
Fatal assertion | Nonfatal assertion | Verifies |
---|---|---|
ASSERT_EQ(val1,val2); | EXPECT_EQ(val1,val2); | val1 == val2 |
ASSERT_NE(val1,val2); | EXPECT_NE(val1,val2); | val1 != val2 |
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); | val1 >= val2 |
若是上述的一个测试失败,那么会打印出val1
和val2
的值。ide
Value参数必须与断言比较运算符兼容,不然有编译错误。这些断言机制可使用用户自定义的结构,可是必须进行运算符重载 。若是使用了自定义结构,咱们最好使用ASSERT_*()
,这样不只会输出比较结果,并且会输出操做数。函数
ASSERT_EQ()
会进行指针比较, 若是使用C风格字符串,那么比较的是地址!!若是要比较值的话,使用ASSERT_STREQ()
, 若是判断C字符串是不是NULL
,使用ASSERT_STREQ(NULL, c_string)
。 若是比较string
,那么使用ASSERT_EQ
。
在这里,比较的是C风格的字符串,若是想要比较string
对象,请使用EXPECT_EQ
、 EXPECT_NE
等,而不是下面的。
Fatal assertion | Nonfatal assertion | Verifies |
---|---|---|
ASSERT_STREQ(str1,str2); | EXPECT_STREQ(str1,str2); | the two C strings have the same content |
ASSERT_STRNE(str1,str2); | EXPECT_STRNE(str1,str2); | the two C strings have different content |
ASSERT_STRCASEEQ(str1,str2); | EXPECT_STRCASEEQ(str1,str2); | the two C strings have the same content, ignoring case |
ASSERT_STRCASENE(str1,str2); | EXPECT_STRCASENE(str1,str2); | the two C strings have different content, ignoring case |
建立一个test:
TEST()
宏定义和命名一个特是函数,这是一个普通的无返回值的C++函数。TEST(testCaseName, testName) { ... test body ... }
testCaseName
是 test case的名字,testName是test case内部测试的名称。二者的名称必须是合法的C++标识符,不容许有下划线( _ ) 。不一样的test case的内部测试能够有相同的独立的名字。
int Factorial(int n); // Returns the factorial of n // Tests factorial of 0. TEST(FactorialTest, HandlesZeroInput) { EXPECT_EQ(1, Factorial(0)); } // Tests factorial of positive numbers. TEST(FactorialTest, HandlesPositiveInput) { EXPECT_EQ(1, Factorial(1)); EXPECT_EQ(2, Factorial(2)); EXPECT_EQ(6, Factorial(3)); EXPECT_EQ(40320, Factorial(8)); }
GoogleTest经过test case组织测试结果,所以逻辑相关的测试必须在一个test case中;换句话说,TEST()
的第一个参数必须相同。上面例子的HandlesZeroInput
和HandlesPositiveInput
都属于FactorialTest
这个测试实例。
Test Fixture容许咱们使用相同对象配置进行不一样的测试。
具体步骤:
::testing::Tes
派生一个类。使用是public:
或者protected:
,由于咱们想要在子类中获取fixture membersSetUp()
函数为每一个测试准备测试对象。TearDown()
函数来释放构造函数或者SetUp()
所申请的资源。 想要理解在何时使用SetUp()
和TearDown()
函数, 阅读这篇文章.当使用一个fixture时,应该使用TEST_F()
而不是TEST()
,由于前者会让咱们获取一个test fixture的对象或者子程序。好比:
TEST_F(test_case_name, test_name) { ... test body ... }
与TEST()
相似,第一个参数是test case的名字。可是第二参数必须是test fixture类的名字。
C++的宏系统不容许咱们建立一个单独的宏来处理各类类型的test。这样作会有编译错误。
对于每一个使用TEST_F()
定义的测试,Google Test会:
SetUp()
马上进行初始化TearDown()
函数清理好比,如今编写一个FIFO队列的测试,队列的实现方式:
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; ... };
首先,定义一个fixture类。假设该类的名称为Foo
, 按照惯例,咱们应该给fixture命名为FooTest
class QueueTest : public ::testing::Test { protected: virtual void SetUp() { q1_.Enqueue(1); q2_.Enqueue(2); q2_.Enqueue(3); } // virtual void TearDown() {} Queue<int> q0_; Queue<int> q1_; Queue<int> q2_; };
本例子中,没用TearDown()
函数,由于没有资源须要释放。
如今 ,使用TEST_F()
测试这个fixture:
// 用于测试是不是空队列 TEST_F(QueueTest, IsEmptyInitially) { EXPECT_EQ(0, q0_.size()); } // 测试出队的工做状态 TEST_F(QueueTest, DequeueWorks) { int* n = q0_.Dequeue(); EXPECT_EQ(NULL, n); // 判断相等的状况 n = q1_.Dequeue(); ASSERT_TRUE(n != NULL); EXPECT_EQ(1, *n); EXPECT_EQ(0, q1_.size()); delete n; n = q2_.Dequeue(); ASSERT_TRUE(n != NULL); EXPECT_EQ(2, *n); EXPECT_EQ(1, q2_.size()); delete n; }
ASSERT_
与EXPECT_
的区别在前面的文章提到了,这里不在赘述。
以上面的例子,说明GoogleTest的测试步骤:
QueueTest
对象, 咱们称之为t1
t1.SetUp()
初始化t1
。t1
进行第一个测试IsEmptyInitially
测试完成后
, t1.TearDown()
清理全部的资源。t1
析构。QueueTest
对象上执行。(DequeueWorks
开始执行上述步骤)TEST()
与TEST_F()
会跟随Google自动进行注册,若是想要执行,咱们不须要从新列出全部定义的测试。
在定义测试以后,使用RUN_ALL_TEST()
。若是测试成功则返回0,不然返回0.,执行这个语句的时候,全部的连接单元都会被测试,它们能够是不一样的test case的。
通常步骤:
SetUp()
函数初始化testTearDown()
释放资源注意,若是构造函数在第2步产生了fatal failure,那么3-5步会自动跳过。一样的,3产生了fatal failure,第4步跳过。
注意: 咱们必须返回RUN_ALL_TEST()
的值,不然gcc
会给出编译错误。也就是说,主函数必须返回RUN_ALL_TEST()
的值!并且RUN_ALL_TEST()
只能执行一次!!
Google Test的官方文档给出了一个测试模板:
#include "this/package/foo.h" #include "gtest/gtest.h" namespace { // The fixture for testing class Foo. class FooTest : public ::testing::Test { protected: // You can remove any or all of the following functions if its body // is empty. FooTest() { // You can do set-up work for each test here. } virtual ~FooTest() { // You can do clean-up work that doesn't throw exceptions here. } // If the constructor and destructor are not enough for setting up // and cleaning up each test, you can define the following methods: virtual void SetUp() { // Code here will be called immediately after the constructor (right // before each test). } virtual void TearDown() { // Code here will be called immediately after each test (right // before the destructor). } // Objects declared here can be used by all tests in the test case for Foo. }; // Tests that the Foo::Bar() method does Abc. TEST_F(FooTest, MethodBarDoesAbc) { const string input_filepath = "this/package/testdata/myinputfile.dat"; const string output_filepath = "this/package/testdata/myoutputfile.dat"; Foo f; EXPECT_EQ(0, f.Bar(input_filepath, output_filepath)); } // Tests that Foo does Xyz. TEST_F(FooTest, DoesXyz) { // Exercises the Xyz feature of Foo. } } // namespace int main(int argc, char **argv) { ::testing::InitGoogleTest(&argc, argv); return RUN_ALL_TESTS(); }
::testing::InitGoogleTest()
用于解析Google Test flags的命令行,而且一处全部已经被识别的标志,具体的用法请参照这个文档
由于本人不用vc++,所以各位用VC的dalao就本身看官方文档吧。。。。。。
以最大子数组线性时间求和问题为例,介绍GoogleTest的测试框架。给出假期刷题时PAT1007原题目连接,并附上AC的代码,算法的原理就不在赘述了:
#include <bits/stdc++.h> using namespace std; int num[10005], N; int main() { cin >> N; bool flag = true; for(int i = 0; i < N; ++i) { cin >> num[i]; if(num[i] >= 0) { flag = false; } } // 注意sum初始化要小于0 int a = 0, b = 0, sum = -1, tmp_sum = 0, tmp_a = 0, tmp_b = 0; while(tmp_b < N) { tmp_sum += num[tmp_b]; if(tmp_sum > sum) { // 更替区间范围 sum = tmp_sum; a = tmp_a; b = tmp_b; } if(tmp_sum < 0) { // 从新开始起点 tmp_sum = 0; tmp_a = tmp_b + 1; } ++tmp_b; } if(flag) { cout << 0 << " " << num[0] << " " << num[N - 1]; } else { cout << sum << " " << num[a] << " " << num[b]; } return 0; }
可是,上述代码很差直接测试,所以,把核心功能分离出来写成函数,同时自定义结构体做为函数的返回值。改进后的代码以下:
实际可能出现的状况有下面几种:
本次测试代码内容较少,就当作是熟悉环境的练习了。。。。。。。。。。。。。。。
代码的Github地址:https://github.com/StudentErick/software_ware_homework
#include <gtest/gtest.h> class Node { public: Node(): sum(0), l(0), r(0) {} // 初始化 int sum, l, r; // 区间和、左右范围,从0开始 }; Node maxFun(int arr[], int N, bool flag) { Node node; int a = 0, b = 0, sum = -1, tmp_sum = 0, tmp_a = 0, tmp_b = 0; while(tmp_b < N) { tmp_sum += arr[tmp_b]; if(tmp_sum > sum) { // 更替区间范围 sum = tmp_sum; a = tmp_a; b = tmp_b; } if(tmp_sum < 0) { // 从新开始起点 tmp_sum = 0; tmp_a = tmp_b + 1; } ++tmp_b; } if(flag) { node.l = 0; node.r = 9; node.sum = 0; } else { node.l = a; node.r = b; node.sum = sum; } return node; } // 须要全局重载 bool operator==(Node a, Node b) { return a.sum == b.sum && a.l == b.l && a.r == b.r; } // 全是负数的测试状况 int num1[10] = { -1, -2, -5, -2, -8, -6, -9, -3, -10, -4}; // 只有一个最大子数组,左右范围是2 7,和是25 int num2[10] = {1, -14, 5, 6, 8, 3, -1, 4, -10, 4}; // 有多个,在这里用两个测试,左右范围应该是2 4 和是19 int num3[10] = {1, -14, 5, 6, 8, -300, 5, 6, 8, -4}; TEST(MYTEST, IsOk) { Node n1, n2, n3; n1.l = 0; n1.r = 9; n1.sum = 0; n2.l = 2; n2.r = 7; n2.sum = 25; n3.l = 2; n3.r = 4; n3.sum = 19; ASSERT_EQ(n1, maxFun(num1, 10, true)); ASSERT_EQ(n2, maxFun(num2, 10, false)); ASSERT_EQ(n3, maxFun(num3, 10, false)); } int main(int argc, char **argv) { ::testing::InitGoogleTest(&argc, argv); return RUN_ALL_TESTS(); }
因为是自定义的返回值,因此根据官方文档的建议,使用ASSERT_*
。测试经过结果以下:
假设更改n3
的l
属性为4,那么测试失败的结果以下:
其中,有相应的不匹配提示。因为本测试比较简单,因此没有使用到test fixture的技术。若是须要,直接套用模板便可。更高级的功能能够参考
本次测试仅经过官方文档进行学习。官方的参考文档是最佳的参考资料。尤为是对于咱们不熟悉的技术领域,更应该经过阅读有关文档进行学习。英文应该做为技术开发人员的一项基本能力,不只仅是为了考研或者是所谓的四六级。不少最新的资料或者比较高端的技术或者一些顶级期刊的论文等,几乎没有中文版的,所以咱们更应该不断提升本身的英文水平。同时,要怀着积极的心态去拥抱新的技术和变化,善于利用工具提升开发或者测试效率。