C++ 单元测试框架 Google Test (gtest)

一、相关概念

Levels of Testing:

Unit Test: Test Individual Component

Integration Test: Test Integrated Component

System Test: Test the entire System

Acceptance Test: Test the final System

gtest是google公司发布的一个跨平台(Liunx、Mac OS、Windows 等) 的C++单元测试框架,它提供了丰富的断言、致命和非致命判断、参数化、”死亡测试”等等。

Test 使用断言来判断测试代码的行为:如果一个 Test 崩溃了或者出现了一个失败的断言,则该 Test 就失败了;反之,它就是成功的。

Test case (有的也叫Test suit) 包括一个或多个 Test。我们应当把 Test 打包、分组,放入 Test Case 中,以便测试代码的结构更加清晰。当一个 Test Case 中的多个 Test 需要共享对象和子程序时,我们可以把这些共享内容放入一个测试夹具(test fixture)类中。一个测试程序可以包含多个 Test Case。

两种断言:

  • ASSERT_* :当断言失败时,产生致命错误,并终止当前函数;
  • EXPECT_* :当断言失败时,产生非致命错误,并且不会终止当前函数。

通常,我们都会选择 EXPECT_*,因为它能让我们在一次测试中测试出更多的失败情况。不过,如果我们想要在出现失败的测试时立即终止程序,则要选择 ASSERT_*。

注意:因为 ASSERT_* 会在失败时立即终止函数,那么就可能跳过后面程序中进行清理工作的代码,由此可能会产生内存泄露。所以我们在使用 ASSERT_* 时,要留心检查堆内存,防止内存泄露。

基本断言

Fatal assertionNonfatal assertionVerifies
ASSERT_TRUE(condition);EXPECT_TRUE(condition);condition is true
ASSERT_FALSE(condition);EXPECT_FALSE(condition);condition is false

二元比较

Fatal assertionNonfatal assertionVerifies
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

EXPECT_FLOAT_EQ(val1, val2);

EXPECT_DOUBLE_EQ(val1, val2);

EXPECT_NEAR(val1, val2, 1e-5);

字符串比较

Fatal assertionNonfatal assertionVerifies
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 contents
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 contents, ignoring case

二、安装

git clone https://github.com/google/googletest
cd googletest
mkdir build
cd build
cmake .. -DCMAKE_CXX_FLAGS='-std=c++11' # 不指定c++11标准会报错
make
sudo make install

安装完之后生成的头文件位于/usr/local/include/gtest/下,静态库文件(libgtest.a, libgtest_main.a)位于/usr/local/lib/下。

三、使用

(1) TEST() 宏

TEST() 宏的第一个参数是 Test Case 的名称,第二个参数是(隶属于第一个Test Case参数的)Test 的名称。一个测试的完整名称包括 Test Case 名称及 Test 的名称,不同 Test Case 的 Test 名称可以相同。googletest 根据 test case 对测试结果进行分组,所以一些相关的 test 应当放入同一个 test case 中。

main.cc:

#include "gtest/gtest.h"
 
bool IsPositive(int num) {
    return num > 0? true : false;
}
 
TEST(PositiveTest, HandlesPositiveInput) {
    EXPECT_TRUE(IsPositive(1));
}

TEST(PositiveTest, HandlesNegativeInput) {
    EXPECT_FALSE(IsPositive(-1));
}

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

上述代码中编写了两个 test,分别为:HandlesPositiveInput 和 HandlesNegativeInput,这两个 test 都属于同一个 test case(PositiveTest),编译并执行上述代码,结果如下:

(2) TEST_F() 宏

当我们想让多个 test 使用同一套数据配置时,就需要用到 Test Fixtures 了。创建 test fixtures 的具体方法如下:

  1. 派生一个继承 ::testing::Test 的类,并将该类中的一些内容声明为 protected 类型,以便在子类中进行访问;
  2. 根据实际情况,编写默认的构造函数或SetUp()函数,来为每个 test 准备所需内容;
  3. 根据实际情况,编写默认的析构函数或TearDown()函数,来释放SetUp()中分配的资源;
  4. 根据实际情况,定义 test 共享的子程序。
  • SetUpTestCase()/TearDownTestCase()​ // Called only once for all test fixtures
  • SetUp()/TearDown()​ // Called for each test fixture​

当使用 fixture 时,我们使用 TEST_F() 宏代替 TEST() 宏,TEST_F() 允许我们在 test fixture 中访问对象和子程序。

注意:TEST_F() 的第一个参数(即 test case 的名称)必须是 test fixture 类的名字。

对于 TEST_F() 定义的每个 test,googletest 将会在运行时创建一个新的 test fixture,并立即通过 SetUp() 对其进行初始化,然后运行 test,之后通过调用 TearDown() 进行数据清理,最后删除 test fixture。需要注意的是,同一个 test case 中不同的 test 具有不同的 test fixture 对象,并且 googletest 每次创建新的 test fixture 前都会先删除之前的 test fixture。多个 test 不会重用相同的 test fixture,某个 test 对 fixture 进行的修改对其他 test 无影响。

main.cc:

#include "gtest/gtest.h"

// 需要测试的类(减法的实现有错)
class Foo {
public:
    Foo(int num) {
        num_ = num;
    };
    void Add(int n) {
        num_ += n;
    };
    void Minus(int n) {
        num_ = n;
    };
    int GetNum() {
        return num_;
    };
private:
    int num_;
};

// 定义测试夹具类FooTest
class FooTest: public testing::Test {
protected:
    Foo* foo;
    // Code here will be called immediately after the constructor (right before each test)
    void SetUp() {
        foo = new Foo(1);
    }
    // Code here will be called immediately after each test (right before the destructor)
    void TearDown() {
        delete foo;
    }
};

TEST_F(FooTest, test_add) {
    EXPECT_EQ(foo->GetNum(), 1);
    foo->Add(1);
    EXPECT_EQ(foo->GetNum(), 2);
}

TEST_F(FooTest, test_minus) {
    EXPECT_EQ(foo->GetNum(), 1);
    foo->Minus(1);
    EXPECT_EQ(foo->GetNum(), 0);
}
 
int main(int argc, char **argv) {
    ::testing::InitGoogleTest(&argc, argv);
    return RUN_ALL_TESTS();
}

上述代码中编写了两个 test,分别为:test_add、和 test_minus,这两个 test 都属于同一个 test case(即 FooTest)。

上述代码中的 test 运行时,主要会进行如下操作:

googletest 构造一个 FooTest 类的对象(我们称之为 f1);
f1.SetUp() 函数对 f1 进行初始化;
使用对象 f1 运行第一个 test(test_add);
f1.TearDown() 在 test 完成后,进行清理工作;
对象 f1 被析构。
上述5个步骤在另一个 FooTest 类的对象(如 f2)中重复,此次会运行 test_minus。
编译并执行上述代码,结果如下:

(3) 全局事件

要实现全局事件,必须写一个类,继承testing::Environment类,实现里面的SetUp和TearDown方法。SetUp方法在所有案例执行前执行;TearDown方法在所有案例执行后执行。除了要继承testing::Environment类,还要定义一个该全局环境的一个对象并将该对象添加到全局环境测试中去。

main.cc 

#include "gtest/gtest.h"

class GlobalTest: public testing::Environment {
public:
    void SetUp() {
        std::cout << "SetUp" << std::endl;
    }
    void TearDown() {
        std::cout << "TearDown" << std::endl;
    }
};

TEST(abs_test, test_1) {
    EXPECT_EQ(std::abs(-1), 1);
}

TEST(abs_test, test_2) {;
    EXPECT_EQ(std::abs(0), 0);
}
 
int main(int argc, char **argv) {
    ::testing::InitGoogleTest(&argc, argv);
    testing::Environment* env = new GlobalTest();
    testing::AddGlobalTestEnvironment(env);
    return RUN_ALL_TESTS();
}

编译并执行上述代码,结果如下: 

(4) 死亡测试

这里的”死亡”指的是程序的崩溃。通常在测试的过程中,我们需要考虑各种各样的输入,有的输入可能直接导致程序崩溃,这个时候我们就要检查程序是否按照预期的方式挂掉,这也就是所谓的”死亡测试”。

死亡测试所用到的宏:

ASSERT_DEATH(参数1,参数2),程序挂了并且错误信息和参数2匹配,此时认为测试通过。如果参数2为空字符串,则只需要看程序挂没挂即可。
ASSERT_EXIT(参数1,参数2,参数3),语句停止并且错误信息和被提前给的信息匹配。

main.cc

#include "gtest/gtest.h"

int func() {
    int *ptr = NULL;
    *ptr = 100;
    return 0;
}

TEST(FuncDeathTest, Nullptr) {
    ASSERT_DEATH(func(), "");
}

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

编译并执行上述代码,结果如下: 

(5) Value Parameterized Tests​ (未测试?)

减少重复测试;更容易覆盖用开/关标志的逻辑。

TEST_P(LeapYearParameterizedTestFixture, OddYearsAreNotLeapYears) {​
    int year = GetParam();​
    ASSERT_FALSE(leapYearCalendar.isLeap(year));​
}​

INSTANTIATE_TEST_CASE_P(​
        LeapYearTests,​
        LeapYearParameterizedTestFixture,​
        ::testing::Values(​
                1, 711, 1989, 2013​
        ));

gmock (未测试?)

A Case for Mock Turtles​

class Turtle {​
  ...​
  virtual ~Turtle() {}​
  virtual void PenUp() = 0;​
  virtual void PenDown() = 0;​
  virtual void Forward(int distance) = 0;​
  virtual void Turn(int degrees) = 0;​
  virtual void GoTo(int x, int y) = 0;​
  virtual int GetX() const = 0;​
  virtual int GetY() const = 0;​
};​

Writing Mock Class

#include "gmock/gmock.h"  // Brings in Google Mock.​
class MockTurtle : public Turtle {​
public:​
  ...​
  MOCK_METHOD0(PenUp, void());​
  MOCK_METHOD0(PenDown, void());​
  MOCK_METHOD1(Forward, void(int distance));​
  MOCK_METHOD1(Turn, void(int degrees));​
  MOCK_METHOD2(GoTo, void(int x, int y));​
  MOCK_CONST_METHOD0(GetX, int());​
  MOCK_CONST_METHOD0(GetY, int());​
};​

Using Mocks​ 

#include "path/to/mock-turtle.h"​
#include "gmock/gmock.h"​
#include "gtest/gtest.h"​
using ::testing::AtLeast;                    ​

TEST(PainterTest, CanDrawSomething) {​
  MockTurtle turtle;                          ​

  EXPECT_CALL(turtle, PenDown()) ​
      .Times(AtLeast(1));​
​
  Painter painter(&turtle);​
  EXPECT_TRUE(painter.DrawCircle(0, 0, 10));​
}

Actions:​

using ::testing::Return;...​
EXPECT_CALL(turtle, GetX())​
    .Times(5)​
    .WillOnce(Return(100))​
    .WillOnce(Return(150))​
    .WillRepeatedly(Return(200));​

 turtle的 GetX() 方法将被调用五次,第一次返回 100,第二次返回 150,然后每次返回 200。如果最后 GetX() 没有被精确调用 5 次,单元将失败。

参考: gMock for Dummies | GoogleTest


main 函数说明

::testing::InitGoogleTest() 函数将会解析命令行中的 googletest 参数,它允许用户通过多样的命令行参数来控制测试程序的行为(即定制命令行参数的功能)。

::testing::InitGoogleTest() 函数必须要在 RUN_ALL_TESTS() 之前调用,否则对应的 flag 可能不会被正常地初始化。

RUN_ALL_TESTS() 会运行所有关联的 test,这些 test 可以来自不同的 test case,甚至不同的源文件。

RUN_ALL_TESTS() 宏在所有 test 都成功时,返回0;否则返回1。

main() 函数必须要返回 RUN_ALL_TESTS() 宏的结果。

RUN_ALL_TESTS() 只能运行一次,多次调用会与 googletest 的一些功能(如 thread-safe death tests)发生冲突。 

单元测试无法测试 main() 内部的逻辑,我们应该保持 main() 尽可能精简,创建对象,然后将它们作为参数传入。

编译 

Google Test 是线程安全的,其线程安全特性要依赖 pthreads 库。 

(1) g++编译

g++ -std=c++11 main.cc /usr/local/lib/libgtest.a -lpthread -o ./bin/test

(2) cmake编译

CMakeLists.txt:

cmake_minimum_required(VERSION 2.8)
project(hello_world)

set(CMAKE_CXX_FLAGS "${CMAKE_CXX_FLAGS} -std=c++11")
set(EXECUTABLE_OUTPUT_PATH ${PROJECT_SOURCE_DIR}/bin)

#enable_testing()
find_package(GTest REQUIRED)
include_directories(${GTEST_INCLUDE_DIR})

add_executable(hello_world main.cc)
target_link_libraries(hello_world ${GTEST_LIBRARY} pthread)

(3) bazel编译

BUILD:

cc_test(
    name = "foo_test",
    srcs = [
        "main.cc",
    ],
    deps = [
        "@gtest//:main",
    ],
)
bazel test :foo_test 

其他
TEST(FooTest, DISABLED_DoesAbc) { ... }​  // Disable existing flakiness temporarily ​

void func() {
  throw std::runtime_error("empty data !");
}
EXPECT_THROW(func(), std::runtime_error);

参考

GitHub - google/googletest: GoogleTest - Google Testing and Mocking Framework

Googletest Primer | GoogleTest

https://github.com/google/googletest/blob/master/googletest/docs/primer.md

GoogleTest测试框架介绍(一)_liitdar的博客-CSDN博客_google test

  • 6
    点赞
  • 15
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
GTestGoogle Test的简称,是一个功能强大的C++单元测试框架。它提供了丰富的断言和测试工具,可以方便地编写、运行和管理测试用例。 首先,我们需要下载并安装GTest框架GTest可以从官方网站下载并编译安装,也可以使用包管理工具进行安装。安装完成后,我们就可以在自己的项目中使用GTest进行单元测试了。 在编写测试用例时,我们需要在一个类中定义多个测试函数。每个测试函数都应该以"TEST"宏开始,并且应该在测试函数中使用多个断言来验证被测试代码的行为。例如,我们可以使用"EXPECT_EQ"断言来验证两个值是否相等。当测试函数执行完毕时,我们可以使用"ASSERT_"宏来检查测试是否通过。 GTest还提供了一些高级功能,例如测试夹具(Test Fixture)和参数化测试(Parameterized Test)等。测试夹具可以帮助我们在测试函数之前和之后执行一些共享的设置和清理操作。参数化测试可以使得我们在一组测试数据上运行相同的测试代码,以验证被测试代码在不同输入条件下的行为。 在运行测试时,我们可以使用GTest提供的命令行工具来执行测试用例。它会输出每个测试函数的执行结果以及总体的测试统计信息。我们也可以在IDE中集成GTest,并通过点击运行按钮来执行测试。 总之,GTest是一个非常强大和方便的单元测试框架,可以帮助我们编写高质量的测试用例并验证被测试代码的正确性。通过充分利用GTest提供的功能,我们可以玩转Google单元测试框架,提升软件开发的质量和效率。

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值