工具学习:Google Test测试框架

Why?

产品级的代码写出来肯定是要进行很多测试的,像“程序员酒吧”那种情况,肯定是不允许的。写完代码,测试,找bug修bug的过程非常麻烦。
所以,GoogleTest官方的介绍是:

GoogleTest helps you write better C++ tests.

Google眼中好的测试应该有以下特点:

  • 测试间相互独立,互不影响
  • 测试应该能很好反应被测代码的结构
  • 测试应该是可移植,可复用,可跨平台的
  • 当测试失败时,测试应该能尽可能多地提供问题信息
  • 测试应该具有很高的自动化程度,避免无效劳动
  • 测试应该够快,性能影响较小。

What?

GoogleTest基于xUnit实现。

注意不同命名,Google Test和大部分教科书的术语称呼方式不太一样,GoogleTest中的Test对应教科书中的Test Case

最简单的测试,就是写断言,通过就接着运行,不通过就终止。
一个测试单元包括多个测试(断言),称为test suite,当多个测试需要共享一个对象或者子程序时,可以把他们放到一个 test fixture class种,
一个测试程序包括多个测试单元。

Assertions断言

类似于宏,当出错时会打印Assert所在的源文件以及行号,错误信息
分为:

  • ASSERT_*:出错就会结束当前函数
    • 注意,崩溃后就会直接退出,不会进行内存清理,有可能导致内存泄漏
  • EXPECT_*:出错后输出错误信息,但是不会结束,接着执行

Example:

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的 ,都能输出。
GoogleTest提供了很多不同类型的断言,例如是否相等,比大小,浮点数值,字符串值等等。
官方文档

Test

使用TEST()宏定义一个测试函数,该测试函数没有返回值。

TEST(TestSuiteName, TestName) {
  ... test body ...
}

第一个参数是所属Test Suite的名字,第二个是测试本身的名字,命名方式和函数命名方式相同,二者都不应该带_,不同Test Suite下的测试可以有相同的名字。
Google Test以Test Suite的测试结果进行统计,所以同一个Test Suite下的测试,应该有相同的输入。

Test Fixture

fixture:固定装置,卡具,固定附着物
怎么翻译?测试夹具?

当多个测试需要相同输入的时候,可以采用Test fixture,可以保证为不同测试配置相同的输入

不要使用全局变量来实现相同输入,有可能前一个测试的变动导致后一个测试的失败,但是问题并不在后一个测试,调试起来更麻烦,Test fixture保证不同测试的输入名称相同,配置相同,但并不是一个对象,不会导致这种情况。

配置fixture方式:

  • 继承testing::Test类,并将成员设为protected
  • 在这个类中声明想用的对象
  • 如果该对象需要初始化,可以写一个构造函数或者SetUp()函数
  • 如果需要析构,可以写一个析构函数,或者TearDown()函数,

当使用fixture时,使用TEST_F而不是TEST,TEST_F可以访问fixture的成员对象和成员函数。TEST_F的第一个参数填fixture的名字,而不是Suite的名字

TEST_F(TestFixtureClassName, TestName) {
  ... test body ...
}

Example:

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;
  ...
};

class QueueTest : public testing::Test {
 protected:
  QueueTest() {
     // q0_ remains empty
     q1_.Enqueue(1);
     q2_.Enqueue(2);
     q2_.Enqueue(3);
  }

  // ~QueueTest() override = default;

  Queue<int> q0_;
  Queue<int> q1_;
  Queue<int> q2_;
};

TEST_F(QueueTest, IsEmptyInitially) { //在配置TEST_F前需要先声明好fixture,否则会报错
  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;
}

调用测试

TEST和TEST_F都会隐式在Google Test中注册,不需要再显式调用,只需要在所有定义完后执行RUN_ALL_TESTS()宏就可以执行全部测试,包括不同源文件中的测试。

注意,main函数返回必须返回RUN_ALL_TEST(),因为Google TEST判断一个测试是否通过是根据返回的GoogleTest flags而不是终端输出,所以必须返回,不返回会报错。且RUN_ALL_TESTS()只能调用一次,多次调用也会出问题。

Example

官方文档中给出的一个案例:

#include "this/package/foo.h"

#include <gtest/gtest.h>

namespace my {
namespace project {
namespace {

// The fixture for testing class Foo.
class FooTest : public testing::Test {
 protected:
  // You can remove any or all of the following functions if their bodies would
  // be empty.

  FooTest() {
     // You can do set-up work for each test here.
  }

  ~FooTest() override {
     // 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:

  void SetUp() override {
     // Code here will be called immediately after the constructor (right
     // before each test).
  }

  void TearDown() override {
     // Code here will be called immediately after each test (right
     // before the destructor).
  }

  // Class members declared here can be used by all tests in the test suite
  // for Foo.
};

// Tests that the Foo::Bar() method does Abc.
TEST_F(FooTest, MethodBarDoesAbc) {
  const std::string input_filepath = "this/package/testdata/myinputfile.dat";
  const std::string output_filepath = "this/package/testdata/myoutputfile.dat";
  Foo f;
  EXPECT_EQ(f.Bar(input_filepath, output_filepath), 0);
}

// Tests that Foo does Xyz.
TEST_F(FooTest, DoesXyz) {
  // Exercises the Xyz feature of Foo.
}

}  // namespace
}  // namespace project
}  // namespace my

int main(int argc, char **argv) {
  testing::InitGoogleTest(&argc, argv); //初始化函数,必须调用
  return RUN_ALL_TESTS();
}

Google Test设计是线程安全的,目前在pthread库上是安全的,但是在其他系统的其他线程库上(例如Windows)不确定。

How?

如果使用cmake进行工程管理,可以使用cmake fetch模块添加依赖库地址,然后就可以使用了。

也可以使用find_package或者find_library或者git submodule等方式,总之就是把gtest列成依赖并使cmake能找到库文件就可以了

CMakeList文件:

cmake_minimum_required(VERSION 3.14)
project(my_project)

# GoogleTest requires at least C++14
set(CMAKE_CXX_STANDARD 14)
set(CMAKE_CXX_STANDARD_REQUIRED ON)

include(FetchContent) # 包括FetchContent模块,该模块用于下载依赖项
FetchContent_Declare( # 声明一个依赖
  googletest
  URL https://github.com/google/googletest/archive/03597a01ee50ed33e9dfd640b249b4be3799d395.zip
)
# For Windows: Prevent overriding the parent project's compiler/linker settings
set(gtest_force_shared_crt ON CACHE BOOL "" FORCE) 
# 设置gtest_force_shared_crt为ON,防止覆盖父项目的编译器/链接器设置
FetchContent_MakeAvailable(googletest)

enable_testing() # 启用测试

add_executable(tests main.cpp) # 添加一个可执行文件
target_link_libraries(tests gtest_main) # 链接gtest_main库文件
include(GoogleTest) # 包括GoogleTest模块
gtest_discover_tests(tests) # 使用gtest_discover_tests函数将测试用例添加到测试中

main.cpp:

#include <iostream>
#include <gtest/gtest.h>

int add(int a, int b) {
    return a + b;
}

TEST(AddTest, Positive) {
    EXPECT_EQ(2, add(1, 1));
    EXPECT_EQ(3, add(1, 2));
    EXPECT_EQ(4, add(2, 2));
}

int main(int argc, char **argv) {
    ::testing::InitGoogleTest(&argc, argv);
    return RUN_ALL_TESTS();
}

Ref

官方文档

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值