GoogleTest 测试框架

这篇具有很好参考价值的文章主要介绍了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
make
sudo make install

源码文件中的 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 函数,关键在于添加两个地方

int main(int argc, char **argv) {
  // 1.定义 main 函数:初始化 gtest
  ::testing::InitGoogleTest(&argc, argv);
  // 2.定义 main 函数:开启全部测试
  return RUN_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 = val2
EXPECT_EQ( val1 , val2 )
ASSERT_EQ( val1 , val2 )

// val1 != val2,空指针使用 nullptr
EXPECT_NE( val1 , val2 )
ASSERT_NE( val1 , val2 )

// val1 <= val2
EXPECT_LT( val1 , val2 )
ASSERT_LT( val1 , val2 )

// val1 > val2
EXPECT_GT( val1 , val2 )
ASSERT_GT( val1 , val2 )

// val1 >= val2
EXPECT_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);
}

// 测试阶乘:0
TEST(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>

using namespace std;

class IslandProblem {
public:
    using Matrix = vector<vector<char>>;
    IslandProblem(const initializer_list<vector<char>> list) {
        _islands.assign(list);
    }

    int Do() {
        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:
    bool canUnion(int row, int col) {
        if (row < 0 || row >= (int)_islands.size())
            return false;
        if (col < 0 || col >= (int)_islands[row].size())
            return false;
        if (_islands[row][col] != 1)
            return false;
        return true;
    }
    void unionIsland(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::Test
class QueueTestSmpl3 : public testing::Test {
 protected:
  // 1、实现 SetUp() 接口
  void SetUp() override {
    q1_.Enqueue(1);
    q2_.Enqueue(2);
    q2_.Enqueue(3);
  }
  // 2、实现 TearDown() 接口
  // virtual void TearDown() {
  // }
  
  // 自定义辅助测试成员函数
  // A helper function that some test uses.
  static int Double(int n) { return 2 * n; }

  // A helper function for testing Queue::Map().
  void MapTester(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:事件

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

class TersePrinter : public EmptyTestEventListener {
 private:
  // Called before any test activity starts.
  void OnTestProgramStart(const UnitTest& /* unit_test */) override {}

  // Called after all test activities have ended.
  void OnTestProgramEnd(const UnitTest& unit_test) override {
    fprintf(stdout, "TEST %s\n", unit_test.Passed() ? "PASSED" : "FAILED");
    fflush(stdout);
  }

  // Called before a test starts.
  void OnTestStart(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.
  void OnTestPartResult(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.
  void OnTestEnd(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 操作符,再用静态成员统计两者的次数是否一致。文章来源地址https://www.toymoban.com/news/detail-675845.html

#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 {
// 需要检测的类
class Water {
 public:
  // 类的定义

  // 重载 new 和 delete 函数
  void* operator new(size_t allocation_size) {
    allocated_++;
    return malloc(allocation_size);
  }

  void operator delete(void* block, size_t /* allocation_size */) {
    allocated_--;
    free(block);
  }
   
 static int allocated() { return allocated_; }

 private:
  // 静态成员,统计 new 和 delete 的次数,判断内存泄漏
  static int allocated_;
};

int Water::allocated_ = 0;

// 检测内存泄漏:事件机制
class LeakChecker : public EmptyTestEventListener {
 private:
  // Called before a test starts.
  void OnTestStart(const TestInfo& /* test_info */) override {
    initially_allocated_ = Water::allocated();
  }

  // Called after a test ends.
  void OnTestEnd(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_leaks
TEST(ListenersTest, DoesNotLeak) {
  Water* water = new Water;
  delete water;
}

// 未开启内存泄漏检测?通过判断指针是否为空,来判断是否有内存谢洛
TEST(ListenersTest, LeaksWater) {
  Water* water = new Water;
  EXPECT_TRUE(water != nullptr);
}
}  // namespace

int main(int argc, char** argv) {
  // 1、main 函数定义:初始化 InitGoogleTest
  InitGoogleTest(&argc, argv);

  bool check_for_leaks = false;
  if (argc > 1 && strcmp(argv[1], "--check_for_leaks") == 0)
    check_for_leaks = true;
  else
    printf("%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 函数定义:开启全级测试
  return RUN_ALL_TESTS();
}

到了这里,关于GoogleTest 测试框架的文章就介绍完了。如果您还想了解更多内容,请在右上角搜索TOY模板网以前的文章或继续浏览下面的相关文章,希望大家以后多多支持TOY模板网!

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处: 如若内容造成侵权/违法违规/事实不符,请点击违法举报进行投诉反馈,一经查实,立即删除!

领支付宝红包 赞助服务器费用

相关文章

  • 详解JUnit单元测试框架(打桩测试待更新)示例代码有格式问题,待更新

    单元测试负责对最小的软件设计单元(模块)进行验证,根据软件设计文档中对模块功能的描述,对重要的程序分支进行测试并发现错误。 对于单元测试框架来讲,它主要完成以下几件事。 提供用例组织与执行: 测试用例只有几条时,可以不考虑用例组织,但是用例达到成

    2024年02月05日
    浏览(58)
  • GoogleTest从入门到入门,小白都能看懂的gtest详细教程

    单元测试 项目管理和技术管理中做单元测试,衡量一个软件是否正常的标准,良好的单元测试以及足够多的覆盖率,至少保证关键功能,关键业务的覆盖率接近100%。 gtest是谷歌公司发布的一个跨平台(Linux、Mac OS、Windows等)的C++单元测试框架,它提供了丰富的断言、致命和

    2024年02月07日
    浏览(51)
  • 【kafka性能测试脚本详解、性能测试、性能分析与性能调优】

    Apache Kafka 官方提供了两个客户端性能测试脚本,它们的存放位置如下: 生产者性能测试脚本:$KAFKA_HOME/bin/kafka-producer-perf-test.sh 消费者性能测试脚本:$KAFKA_HOME/bin/kafka-consumer-perf-test.sh kafka-producer-perf-test.sh 支持测试的性能指标包括:吞吐量(throughput)、最大时延(max-latenc

    2024年02月04日
    浏览(64)
  • 一文掌握谷歌 C++ 单元测试框架 GoogleTest

    GoogleTest GoogleTest(简称 GTest) 是 Google 开源的一个跨平台的(Liunx、Mac OS X、Windows等)的 C++ 单元测试框架,可以帮助程序员测试 C++ 程序的结果预期。不仅如此,它还提供了丰富的断言、致命和非致命判断、参数化、”死亡测试”等等。 GoogleTest 官网:https://google.github.io/go

    2024年02月03日
    浏览(43)
  • C++ 测试框架 GoogleTest 初学者入门篇 丙

    *以下内容为本人的学习笔记,如需要转载,请声明原文链接 微信公众号「ENG八戒」https://mp.weixin.qq.com/s/RIztusI3uKRnoHVf0sloeg 开发者虽然主要负责工程里的开发任务,但是每个开发完毕的功能都是需要开发者自测通过的,所以经常会听到开发者提起单元测试的话题。那么今天我就

    2023年04月15日
    浏览(51)
  • 一个简单好用的C++语言单元测试框架-GoogleTest

    GoogleTest 是由 Google 开发的一个用于编写 C++ 单元测试的框架。单元测试中单元的含义,单元就是人为规定的最小的被测功能模块,如C语言中单元指一个函数,Java里单元指一个类,图形化的软件中可以指一个窗口或一个菜单等。在实际项目中,单元测试往往由开发人员完成。

    2024年01月19日
    浏览(97)
  • 全网最全,性能测试-性能瓶颈分析详全,优秀的性能测试工程师养成记...

    内存分析 内存的使用情况是系统性能中重要的因素之一,频繁的页交换及内存泄露都会影响到系统的性能(在这主要以Windows系统为主)。 内存分析用于判断系统有无遇到内存瓶颈,是否需要通过增加内存等手段提高系统性能表现。 1、查看MemoryAvailable Mbytes指标 在对系统进

    2024年02月05日
    浏览(71)
  • 你真的会性能测试吗?性能测试需求分析,从业务到数据(详细)...

    产品需求 业务场景: 一个问卷调查的功能,然后产品和业务会不定时通过前端界面去根据筛选条件查询相关问卷问题的答案明细,但是觉得很慢,让测试这边给出一个指标。 系统架构: MySQL数据库,所有问卷问题相关的数据都存储在同一张表,单台服务器,无缓存,通过一

    2024年02月10日
    浏览(60)
  • 性能测试需求分析和学习

    在实际的工作中系统的性能需求通常是一个笼统的需求,而且有可能给提需求的人并不知道具体的性能需要,所以只能含糊的列出。如果测试人员不搞清楚,就会出现实际要把杀猪刀,需求标明能屠龙!!! 下面,还是举一个讲个我真实经历过的故事来做实例分析。 故事开

    2024年02月09日
    浏览(44)
  • 性能测试分析定位

    当我们在性能测试过程中,遇到TPS无法上去、请求响应时间过长、各类资源利用率遇到瓶颈时,应该如何对它们进行分析定位。   硬件资源不足:服务器的CPU、内存、磁盘等硬件资源不足,无法支撑高并发的请求处理。可以通过增加硬件资源或者优化服务器配置来提升TPS。

    2024年03月26日
    浏览(50)

觉得文章有用就打赏一下文章作者

支付宝扫一扫打赏

博客赞助

微信扫一扫打赏

请作者喝杯咖啡吧~博客赞助

支付宝扫一扫领取红包,优惠每天领

二维码1

领取红包

二维码2

领红包