学习gtest测试

了解

单元测试

单元:函数、类等

gtest

gtest是一个跨平台的(Liunx、Mac OS X、Windows、Cygwin、Windows CE and Symbian)C++单元测试框架,由google公司发布。gtest是为在不同平台上为编写C++测试而生成的。它提供了丰富的断言、致命和非致命判断、参数化、”死亡测试”等等。

安装

git clone https://github.com/google/googletest.git
cd googletest
mkdir build
cd build
cmake ..
make && make install

#如果想测试gtest中sample的例子,需要开启
cmake .. -Dgtest_build_samples=ON
make && make install gtest_build_samples=ON

使用

实例1

// gtest_sum.cpp
#include <iostream>
#include <gtest/gtest.h>
 
int sum(int a, int b) {
    return a+b;
}
 
TEST(sum, testSum) {
    EXPECT_EQ(5, sum(2, 3));	// 求合2+3=5
    EXPECT_NE(3, sum(3, 4));	// 求合3+4 != 3
    EXPECT_NE(7, sum(3, 4));
}
// 如果在此处不写main函数,那么在链接库的时候还需要链接-lgtest_main, 否则只需链接-lgtest即可。
#if 0
int main(int argc, char **argv)
{
    testing::InitGoogleTest(&argc, argv);
    return RUN_ALL_TESTS();
}
#endif

1、首先,测试时使用gtest需包含头文件 gtest/gtest.h, 并链接库 gtest_main.lib 和 gtest.lib。

  • 程序中sum函数是我们要进行测试的函数名或称为分类名

  • Test后跟的即为我们要编写的测试方法:

TEST(分类名, 测试名) {
    测试代码
    也是如何测试,设置测试力
}

2、使用下面套路化的main函数启动测试:

int main(int argc, char** argv) {
    testing::InitGoogleTest(&argc, argv);
    return RUN_ALL_TESTS();
}//上述代码中也表明,可不写main函数,但需链接 -lgtest_main

3、测试代码含义:

TEST(sum, testSum) {
    EXPECT_EQ(5, sum(2, 3));    // 求合2+3=5
    EXPECT_NE(3, sum(3, 4));    // 求合3+4 != 3
}

以上面代码段举例,EXPECT_字段的表达的是检查方式,除个别检查外,所有检查都带前缀,在下述两项中二选一:

ASSERT_ : 断言, 不通过检查则中断测试, 当在测试外使用时要求函数返回void。
EXPECT_ : 期望, 不通过检查并不中断测试。

EXPECT和ASSER的区别:

  • expect遇到错误(false)继续执行

  • assert遇到错误(false)跳出当前执行

后缀EQ表达的意思「相等」为5 == sum(2,3);

后缀NE表达的意思「不等」为3 !=sum(3,4);

以下列举给test中对数值的测试后缀:
image-20230501172811936

对字符串的检查后缀:

image-20230501172834779

4、编译运行

g++ -o gtest_sum gtest_sum.cpp  -lgtest_main -lgtest -lpthread

image-20230501185437492

实例2

#include <gtest/gtest.h> 

int MAC(int a, int b, int& sum)
{
    sum += a*b;
    return sum;
}

// 要继承Test这个类,override关键字表示覆盖了基类中的方法
struct Foo:
    public ::testing::Test
{
    //  设置
    virtual void SetUp() override
    {
        printf("hello"); 
    }

    // 拆解 
    virtual void TearDown() override
    {
        printf("world");
    }
};

// 全局变量
int sideEffect = 42;

// 有副作用的函数: 修改了全局变量真的值
bool f()
{
    sideEffect = 16; 
    return true;    // try false  
}

TEST(ExampleTest, DemonstrateGtestMacros)
{
    const bool result = f(); 
    
    EXPECT_EQ(true, result) << "hello";
    ASSERT_TRUE(true);
}

TEST(test1, testAssert)
{
   
   EXPECT_TRUE(true);
}


TEST(ExampleTests, MAC)
{
    int x = 42; int y = 16;
    int sum = 100; int oldSum = sum; 
    int expectedNewSum = oldSum + x*y ;


    EXPECT_EQ(
        expectedNewSum,
        MAC(x, y, sum)
    );

    EXPECT_EQ(
        expectedNewSum,
        sum
    );
}

1、编译运行

g++ -o ExampleTest ExampleTest.cpp  -lgtest_main -lgtest -lpthread

image-20230501192624192

2、一共有三个测试:

  • 【基本】布尔函数测试
  • 布尔变量测试
  • 【固件】函数测试

测试

测试方法分为四种:基本测试、测试固件、异常测试、值参数化测试

1、基本测试

在最基础的测试中,一般只需要包含头文件gtest.h即可。gtest中常用的所有结构体、类、函数、常量等,都通过命名空间testing访问,但是gtest已经把常用的单元测试功能包装成了带参数宏,所以简单测试中可以忽略命名空间。

Test宏开头便定义了一个可执行测试,其参数为被测函数和自定义测试名称,自定义名称也可为中文,例如TEST(Add, 正数){},{}中添加逻辑代码,形成测试力,测试力要求自拟并利用宏达到自己想要的结果。

每个宏也可以使用 << 运算符在测试失败时输出自定义信息,如:

ASSERT_EQ(M[i], N[j]) << "i = " << i << ", j = " << j;

2、测试固件

测试固件是可以为所有测试创建一个相同的配置环境,并在测试结束后执行清理工作。对测试固件的使用比基本测试多出来的方法是需要继承Test的类:

  • 从gtest的testing::Test类继承,用public或protect定义以下成员;
  • (可选)整个测试环境的开始和结束,使用默认构造函数和析构函数函数分别为SetUpTestCase()TearDownTestCase()
  • (可选)建立环境:使用默认构造函数,或定义一个虚成员函数virtual void SetUp()
  • (可选)销毁环境:使用析构函数,或定义一个虚成员函数virtual void TearDown()
  • 用TEST_F定义测试,写法与TEST相同,但测试用例名即被测试对象必须为上面定义的类名。

代码和测试结果如下:

// gtest_sum_AB.cpp
#include <gtest/gtest.h> 

class TestSuit1 : public ::testing::Test {
public:
    static void TearDownTestCase() 
    {
        std::cout << "TearDownTestCase" << std::endl;
    }
    static void SetUpTestCase() 
    {
        std::cout << "SetUpTestCase" << std::endl;
    }
 
    virtual void SetUp()
    {
        std::cout << "SetUp" << std::endl;
    }
    virtual void TearDown()
    {
        std::cout << "TearDown" << std::endl;
    }
};
 
int testFunc(int a, int b) 
{
    return a + b;
}
 
// 注意TEST_F
TEST_F(TestSuit1, testCase1) 
{
    std::cout << testFunc(1, 3) << std::endl;;
    
}
 
TEST_F(TestSuit1, testCase2) 
{
    std::cout << testFunc(2, 3) << std::endl;;
}

编译运行:

g++ -o gtest_sum_AB gtest_sum_AB.cpp  -lgtest_main -lgtest -lpthread

image-20230501193233475

可以看出:

  • 先调用SetUpTestCase再调用SetUp
  • SetUpTestCase和SetUpTestCase只使用一次

3、异常测试

gtest为异常测试提供了专用的宏:

image-20230501180524731

此异常测试是测试被测对象是否在运行中出现我们测试用例规定的异常:

  • 如果有异常会执行通过,宏的功能是抛出异常,但实际无法获得被抛出异常的详细信息(如报错文本)。
  • 如使用_THROW宏,遇到异常会判断出现的异常与我们设置的异常是否相同(type),如果不相同会报错,测试失败。

4、值参数化测试

值参数化测试即为被测对象带入大量随机值进行测试,类似于在for循环内使用函数。

gtest中的定位是使用大量随机值来检验算法实现的正确性,或比较同一个接口的不同实现之间的差别。gtest把“集中输入测试参数”的需求抽象出来提供支持,称为值参数化测试(Value Parameterized Test)

值参数化测试包括4个步骤:

  • 从gtest的TestWithParam模板类继承出一个类,因为TestWithParam本身是从Test派生的,所以我们定义的类也就成了一个测试固件类。
  • 在此类中,可实现SetUp、TearDown等方法。测试参数由TestWithParam实现的GetParam()方法依次返回。
  • 使用TEST_P(而不是TEST_F)定义测试。
  • 使用INSTANTIATE_TEST_CASE_P宏集中输入测试参数,它接受3个参数:任意的文本前缀,测试类名,以及测试参数值序列。gtest框架依次使用这些参数值生成测试固件类实例,并执行用户定义的测试。

gtest提供了专门的模板函数来生成参数值序列,如下表所示:

image-20230501180742062

// gtest_addupto.cpp
#include <gtest/gtest.h>  

inline unsigned NaiveAddUpTo(unsigned n) {  
    unsigned sum = 0;  
    for(unsigned i = 1; i <= n; ++i) sum += i;  
    return sum;  
}  
 
inline unsigned FastAddUpTo(unsigned n) {  
    return n*(n+1)/2;  
} 

//参数化测试 
class AddUpToTest : public testing::TestWithParam<unsigned>//继承此类进行值参数化测试
{  
public:  
		AddUpToTest() { n_ = GetParam(); }  //测试参数由此返回
protected:  
		unsigned n_;  
};  
 
TEST_P(AddUpToTest, Calibration) {  
    EXPECT_EQ(NaiveAddUpTo(n_), FastAddUpTo(n_));  //期望a和b相等,不中断
}  
 
INSTANTIATE_TEST_CASE_P(  
    NaiveAndFast, // prefix  前缀
    AddUpToTest,   // test case name  测试用例名称
    testing::Range(1u, 10u) // parameters  参数,u表示unsigned,必需严谨,否则编译错误,
    					//也可以使用 testing::Range<unsigned>(1, 1000)
); 

编译运行:

g++ -o gtest_addupto gtest_addupto.cpp  -lgtest_main -lgtest -lpthread

image-20230501193820134

参考

  1. gtest学习
  2. [C++] gtest入门教程