0


GoogleTest 测试框架

文章目录

GoogleTest(GTest) 是谷歌开源的 C++ 单元测试框架。

1、单元测试

**单元测试

unit testing

是指对软件中的最小可测试单元进行检查和验证,包括函数、类、模块、复杂交互逻辑等。gtest 中单元测试以单例模式实现。每个单元测试包含若干个测试套件

test suite

,测试套件是指一组功能相同的测试脚本或过程。每个测试套件包含多个测试案例

test case

**,测试同一个功能的不同方向。

根据 gtest 官方文档,一个好的单元测试应该满足:

  • 独立、可重复:测试案例可单独执行,错误可重复发生。
  • 反映测试代码结构:功能完整,体现完备逻辑。对于关联功能,一个功能是否影响其他功能
  • 可移植、可复用:跨平台。
  • 尽可能多的出错信息:不会因为一次失败而停止,会继续测试下一个测试案例。一次测试发现多个错误。
  • 自动跟踪所有测试而无需枚举:向容器中添加
  • 测试高效:测试间重用资源,mock 模拟复杂交互

如何整体使用单元测试

  • 每个类或功能块加上测试案例
  • test 目录加上所有的测试
  • 一个单元测试可能散落在多个文件

单元测试中的打桩,是指用来代替关联代码或者未实现代码的代码,即用桩函数代替原函数。打桩测试由 gtest 里的 gmock 来实现。

2、GTest 安装

GTest 官网

# 下载git clone https://github.com/google/googletest.git
# 安装cd googletest
cmake CMakeLists.txt
makesudomakeinstall

源码文件中的 lib 库,包含 gtest 库和 gmock 库。

libgtest.a  libgtest_main.a  libgmock.a  libgmock_main.a

当测试代码有 main 函数,使用不带 main 的静态库,否则使用带 main 的静态库。

# 无 main 函数
g++ sample.cc sample_unittest.cc -o sample -lgtest-lgtest_main-lpthread# 有 main 函数
g++ gmock_output_test_.cc -o output -lgtest-lgmock-lpthread

若需要编写 main 函数,关键在于添加两个地方

intmain(int argc,char**argv){// 1.定义 main 函数:初始化 gtest::testing::InitGoogleTest(&argc, argv);// 2.定义 main 函数:开启全部测试returnRUN_ALL_TESTS();}

3、GTest 原理

GTest 测试底层原理

  • 创建单元测试类,单例模式实现,包含vector<TestSuite*>
  • 根据测试套件名,生成一个TestSuite 类实例,包含vector<TestInfo*>;
  • 根据测试案例名,生成一个Test_info类实例,继承父类 testing::Test
  • 将测试案例实例注册到测试套件中vector<TestSuite*>,测试套件类实例调用run方法执行测试

类的组织层次

// 单元测试,单例模式实现
class Impl {
    // 存储测试套件类实例
    vector<TestSuite*>;
}

// 测试套件
class TestSuite {
    // 存储测试案例类实例
    vector<TestInfo*>;
    // 执行测试套件中的测试案例
    run();
}

// 测试案例
class TestInfo {
    // 执行测试
    TestBody();
};

4、断言

使用测试断言,通过断言其行为来测试类和函数。

ASSERT_*

失败时会生成致命错误,并中止当前功能;

EXPECT_*

失败时生成非致命错误,不会中止当前功能。通常选用

EXPECT_*

所有断言宏都支持输出流,经流输出的信息自动转换为

utf-8

,可利用这一特性输出详细错误信息

EXPECT_TRUE(my_condition) << "My condition is not true";

更多断言的使用,见官方文档:Assertions

明确指定成功或失败

当测试案例中的条件太复杂,不能使用断言,那么自己写判断语句;自己返回成功或者失败;

if(condition){SUCCEED();}else{FAIL();}

布尔条件

EXPECT_TRUE(condition)
ASSERT_TRUE(condition)

EXPECT_FALSE(condition)
ASSERT_FALSE(condition)

二元比较

// val1 = val2EXPECT_EQ( val1 , val2 )ASSERT_EQ( val1 , val2 )// val1 != val2,空指针使用 nullptrEXPECT_NE( val1 , val2 )ASSERT_NE( val1 , val2 )// val1 <= val2EXPECT_LT( val1 , val2 )ASSERT_LT( val1 , val2 )// val1 > val2EXPECT_GT( val1 , val2 )ASSERT_GT( val1 , val2 )// val1 >= val2EXPECT_GE( val1 , val2 )ASSERT_GE( val1 , val2 )

谓词断言

EXPECT_PREDn( pred , val1,..., valn ) \
ASSERT_PREDn( pred , val1,..., valn ) \

例如:测试阶乘函数,参数 1 个

EXPECT_PRED1(Factorial,1)

死亡测试

用于测试程序是否以预期的方式崩溃。

EXPECT_DEATH(func, desc);

5、GTest 使用

测试的方法

  • 基本功能:验证基本逻辑是否正确
  • 边界情况:验证边界值是否正确输出
  • 异常情况:非法输入做出合理错误处理。判断错误的方式 - 函数返回值- 全局变量:linux: Errnowindows: GetLastError,- 异常:抛出异常

5.1、测试1:测试函数

使用

TEST

宏来定义测试案例。

#include"sample1.h"#include<limits.h>#include"gtest/gtest.h"// 使用 TEST 宏定义测试案例 // #define TEST(test_suite_name,test_name)// 测试阶乘:负数TEST(FactorialTest, Negative){// 断言:预期相等 EXPECT_EQ(expected, actual),后面同理EXPECT_EQ(1,Factorial(-5));EXPECT_EQ(1,Factorial(-1));EXPECT_GT(Factorial(-10),0);}// 测试阶乘:0TEST(FactorialTest, Zero){EXPECT_EQ(1,Factorial(0));}// 测试阶乘:正数TEST(FactorialTest, Positive){EXPECT_EQ(1,Factorial(1));EXPECT_EQ(2,Factorial(2));EXPECT_EQ(6,Factorial(3));EXPECT_EQ(40320,Factorial(8));}

编译代码

g++ sample1.cc sample1_unittest.cc -o sample1 -lgtest-lgtest_main-lpthread

测试结果

[==========] Running 6 tests from 2 test suites.
[----------] Global test environment set-up.
[----------] 3 tests from FactorialTest
[ RUN      ] FactorialTest.Negative
[       OK ] FactorialTest.Negative (0 ms)
[ RUN      ] FactorialTest.Zero
[       OK ] FactorialTest.Zero (0 ms)
[ RUN      ] FactorialTest.Positive
[       OK ] FactorialTest.Positive (0 ms)
[----------] 3 tests from FactorialTest (0 ms total)
...
[----------] Global test environment tear-down
[==========] 6 tests from 2 test suites ran. (0 ms total)
[  PASSED  ] 6 tests.

5.2、测试2:测试类

#include<iostream>#include<initializer_list>#include<vector>#include<gtest/gtest.h>usingnamespace std;classIslandProblem{public:using Matrix = vector<vector<char>>;IslandProblem(const initializer_list<vector<char>> list){
        _islands.assign(list);}intDo(){int num =0;for(int row =0; row <(int)_islands.size(); row++){for(int col =0; col <(int)_islands[row].size(); col++){if(canUnion(row, col)){
                    num++;unionIsland(row, col);}}}return num;}protected:boolcanUnion(int row,int col){if(row <0|| row >=(int)_islands.size())returnfalse;if(col <0|| col >=(int)_islands[row].size())returnfalse;if(_islands[row][col]!=1)returnfalse;returntrue;}voidunionIsland(int row,int col){
        _islands[row][col]=2;if(canUnion(row-1, col))unionIsland(row-1, col);if(canUnion(row, col-1))unionIsland(row, col-1);if(canUnion(row+1, col))unionIsland(row+1, col);if(canUnion(row, col+1))unionIsland(row, col+1);}private:
    Matrix _islands;};TEST(IslandProblem, logic){
    IslandProblem ip1{{1,1,1,1},{1,0,1,1},{0,0,0,0},{1,0,1,0}};EXPECT_EQ(ip1.Do(),3);

    IslandProblem ip2{{1,0,1,1},{1,0,1,1},{0,0,0,0},{1,0,1,0}};EXPECT_EQ(ip2.Do(),4);}TEST(IslandProblem, boundary){
    IslandProblem ip1{{1,1,1,1},{1,0,0,1},{1,0,0,1},{1,1,1,1}};EXPECT_EQ(ip1.Do(),1);
    IslandProblem ip2{};EXPECT_EQ(ip2.Do(),0);}TEST(IslandProblem, exception){
    IslandProblem ip1{{-1,1,1,1},{1,0,0,1},{1,0,0,1},{1,1,1,1}};EXPECT_EQ(ip1.Do(),1);}

5.3、测试3:测试夹具

用相同的数据配置来测试多个测试案例,实现测试夹具共享,而不是数据共享。

使用测试夹具的方法

  • 在使用测试夹具前,定义测试夹具类,继承基类testing::Test,类成员可访问 protected
  • 实现SetUp()接口:测试前调用,若要初始化变量,重定义该接口,否则跳过。
  • 实现TearDown()接口:测试后调用,若有清理工作要做,重定义该接口,否则跳过。
  • 自定义成员

使用

TEST_F

宏测试夹具

#include"sample3-inl.h"#include"gtest/gtest.h"namespace{// 使用测试夹具,必须继承基类 testing::TestclassQueueTestSmpl3:public testing::Test{protected:// 1、实现 SetUp() 接口voidSetUp()override{
    q1_.Enqueue(1);
    q2_.Enqueue(2);
    q2_.Enqueue(3);}// 2、实现 TearDown() 接口// virtual void TearDown() {// }// 自定义辅助测试成员函数// A helper function that some test uses.staticintDouble(int n){return2* n;}// A helper function for testing Queue::Map().voidMapTester(const Queue<int>* q){// Creates a new queue, where each element is twice as big as the// corresponding one in q.const Queue<int>*const new_q = q->Map(Double);// Verifies that the new queue has the same size as q.ASSERT_EQ(q->Size(), new_q->Size());// Verifies the relationship between the elements of the two queues.for(const QueueNode<int>*n1 = q->Head(),*n2 = new_q->Head();
         n1 !=nullptr; n1 = n1->next(), n2 = n2->next()){EXPECT_EQ(2* n1->element(), n2->element());}delete new_q;}// 自定义数据成员
  Queue<int> q0_;
  Queue<int> q1_;
  Queue<int> q2_;};// 使用 TEST_F 宏测试夹具 TEST_F(test_fixture, test_name)// 测试队列:构造函数TEST_F(QueueTestSmpl3, DefaultConstructor){EXPECT_EQ(0u, q0_.Size());}// 测试队列:出队TEST_F(QueueTestSmpl3, Dequeue){int* n = q0_.Dequeue();EXPECT_TRUE(n ==nullptr);

  n = q1_.Dequeue();ASSERT_TRUE(n !=nullptr);EXPECT_EQ(1,*n);EXPECT_EQ(0u, q1_.Size());delete n;

  n = q2_.Dequeue();ASSERT_TRUE(n !=nullptr);EXPECT_EQ(2,*n);EXPECT_EQ(1u, q2_.Size());delete n;}// 测试队列:map()TEST_F(QueueTestSmpl3, Map){MapTester(&q0_);MapTester(&q1_);MapTester(&q2_);}}// namespace

5.4、测试4:类型参数化

相同的接口,有多个实现,复用测试案例,策略模式。例如:写日志(写磁盘、写数据库、写 kafka)。

使用

TYPED_TEST

宏测试类型参数化

// 枚举测试类型:同一接口的不同实现形式类typedef Types<Class1, Class2, class3,...> Implementations;// 定义测试套件TYPED_TEST_SUITE(TestFixtureSmpl, Implementations);// 使用 `TYPED_TEST`宏测试类型参数化TYPED_TEST(TestFixtureSmpl, TestName)

5.5、测试5:事件

通过事件机制,在测试前后进行埋点处理。事件机制定义如下

classTersePrinter:publicEmptyTestEventListener{private:// Called before any test activity starts.voidOnTestProgramStart(const UnitTest&/* unit_test */)override{}// Called after all test activities have ended.voidOnTestProgramEnd(const UnitTest& unit_test)override{fprintf(stdout,"TEST %s\n", unit_test.Passed()?"PASSED":"FAILED");fflush(stdout);}// Called before a test starts.voidOnTestStart(const TestInfo& test_info)override{fprintf(stdout,"*** Test %s.%s starting.\n", test_info.test_suite_name(),
            test_info.name());fflush(stdout);}// Called after a failed assertion or a SUCCEED() invocation.voidOnTestPartResult(const TestPartResult& test_part_result)override{fprintf(stdout,"%s in %s:%d\n%s\n",
            test_part_result.failed()?"*** Failure":"Success",
            test_part_result.file_name(), test_part_result.line_number(),
            test_part_result.summary());fflush(stdout);}// Called after a test ends.voidOnTestEnd(const TestInfo& test_info)override{fprintf(stdout,"*** Test %s.%s ending.\n", test_info.test_suite_name(),
            test_info.name());fflush(stdout);}};// class TersePrinter

例:内存泄漏

在需要检测的类中,重载 new 和 delete 操作符,再用静态成员统计两者的次数是否一致。

#include<stdio.h>#include<stdlib.h>#include"gtest/gtest.h"using::testing::EmptyTestEventListener;using::testing::InitGoogleTest;using::testing::Test;using::testing::TestEventListeners;using::testing::TestInfo;using::testing::TestPartResult;using::testing::UnitTest;namespace{// 需要检测的类classWater{public:// 类的定义// 重载 new 和 delete 函数void*operatornew(size_t allocation_size){
    allocated_++;returnmalloc(allocation_size);}voidoperatordelete(void* block, size_t /* allocation_size */){
    allocated_--;free(block);}staticintallocated(){return allocated_;}private:// 静态成员,统计 new 和 delete 的次数,判断内存泄漏staticint allocated_;};int Water::allocated_ =0;// 检测内存泄漏:事件机制classLeakChecker:publicEmptyTestEventListener{private:// Called before a test starts.voidOnTestStart(const TestInfo&/* test_info */)override{
    initially_allocated_ =Water::allocated();}// Called after a test ends.voidOnTestEnd(const TestInfo&/* test_info */)override{int difference =Water::allocated()- initially_allocated_;// 输出测试结果EXPECT_LE(difference,0)<<"Leaked "<< difference <<" unit(s) of Water!";}int initially_allocated_;};// 开启内存泄漏检测 --check_for_leaksTEST(ListenersTest, DoesNotLeak){
  Water* water =new Water;delete water;}// 未开启内存泄漏检测?通过判断指针是否为空,来判断是否有内存谢洛TEST(ListenersTest, LeaksWater){
  Water* water =new Water;EXPECT_TRUE(water !=nullptr);}}// namespaceintmain(int argc,char** argv){// 1、main 函数定义:初始化 InitGoogleTestInitGoogleTest(&argc, argv);bool check_for_leaks =false;if(argc >1&&strcmp(argv[1],"--check_for_leaks")==0)
    check_for_leaks =true;elseprintf("%s\n","Run this program with --check_for_leaks to enable leak checking.");// 若启动命令行添加参数 --check_for_leaks,开启内存泄漏检测if(check_for_leaks){
    TestEventListeners& listeners =UnitTest::GetInstance()->listeners();
    listeners.Append(new LeakChecker);}// 2、main 函数定义:开启全级测试returnRUN_ALL_TESTS();}

本文转载自: https://blog.csdn.net/you_fathe/article/details/129018884
版权归原作者 摸鱼呀 所有, 如有侵权,请联系我们删除。

“GoogleTest 测试框架”的评论:

还没有评论