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

一、相关概念

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 assertion Nonfatal assertion Verifies
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

字符串比较

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 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 共享的子程序。

当使用 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();
}

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

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)发生冲突。 

编译 

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)

参考

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

https://blog.csdn.net/liitdar/article/details/85712973 

猜你喜欢

转载自blog.csdn.net/A_L_A_N/article/details/106952145