单元测试工具gtest使用笔记

date: 2015.11.26; modification:2015.11.26

目录:

1 框架层次

简单地讲, 每个基于gtest的测试过程, 是可以分为多个TestSuite级别, 而每个 TestSuite级别又可以分为多个TestCae级别. 这样分层的结构的好处, 是可以针对不同的 TestSuite级别或者TestCae级别设置不同的参数, 事件机制等, 并且可以与实际测试的各 个模块层级相互对应, 便于管理.

2 gtest的各种断言

这一部分主要总结gtest中的各种测试函数, 也可以称为断言. gtest中, 测试函数可以 理解为分为两类, 一类是ASSERT系列, 一类是EXPECT系列. 一个直观的区别是:

  1. ASSERT_* 系列的断言, 当检查点失败时, 退出当前函数(注意: 并非退出当前案例).

  2. EXPECT_* 系列的断言, 当检查点失败时, 继续往下执行.

先介绍一个简单的断言, EXPECT_EQ, 我们如果这样来写EXPECT_EQ(1,2), 那么执行测试 之后会有如下的输出:

这些输出也可以重定向到XML文件中, 方便进行统一格式的处理和分析.

我们如果需要输出额外的信息, 可以直接使用<<操作符, 例如: EXPECT_EQ(x[i], y[i]) << "Extra output", 有时候这些额外的输出也很有用.

下面还有一些其他常见的断言, 我们在gtest的网站上大概看过一遍, 应该就有了大概的 了解, 名字都很直观.

2.1 布尔值检查:

Fatal assertion             Nonfatal assertion          Verifies 

ASSERT_TRUE(condition);     EXPECT_TRUE(condition);     condition is true 
ASSERT_FALSE(condition);    EXPECT_FALSE(condition);    condition is false

2.2 数值型数据检查:

Fatal assertion                 Nonfatal assertion              Verifies 

ASSERT_EQ(expected, actual);    EXPECT_EQ(expected, actual);    expected == actual 
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(expected_str, actual_str);         EXPECT_STREQ(expected_str, actual_str);         the two C strings have the same content 
ASSERT_STRNE(str1, str2);                       EXPECT_STRNE(str1, str2);                       the two C strings have different content 
ASSERT_STRCASEEQ(expected_str, actual_str);     EXPECT_STRCASEEQ(expected_str, actual_str);     the two C strings have the same content, ignoring case 
ASSERT_STRCASENE(str1, str2);                   EXPECT_STRCASENE(str1, str2);                   the two C strings have different content, ignoring case 

除了上面这些断言, 还有断言可以直接返回成功或者直接返回失败:

Fatal assertion     Nonfatal assertion 

FAIL();             ADD_FAILURE(); 

使用时也很方便:

TEST(ExplicitTest, Demo) {
     ADD_FAILURE() << "Fail, but continue."; 
     FAIL(); <<"Fail, and return." 
}

Gtest还可以检查程序抛出的异常, 可以使用下面这些断言:

Fatal assertion                             Nonfatal assertion                          Verifies 

ASSERT_THROW(statement, exception_type);    EXPECT_THROW(statement, exception_type);    statement throws an exception of the given type 
ASSERT_ANY_THROW(statement);                EXPECT_ANY_THROW(statement);                statement throws an exception of any type 
ASSERT_NO_THROW(statement);                 EXPECT_NO_THROW(statement);                 statement doesn't throw any exception 

对于返回bool值的函数, 还可以直接在断言中传入参数. 这是因为程序员在使用EXPECT_TRUE或ASSERT_TRUE时, 经常希望能够输出更加详细的信息, 比如检查一个函数的返回值TRUE还是FALSE时, 希望能够输出传入的参数是什么, 以便失败后好跟踪. 因此提供了如下的断言:

Fatal assertion                     Nonfatal assertion                      Verifies 

ASSERT_PRED1(pred1, val1);          EXPECT_PRED1(pred1, val1);              pred1(val1) returns true 
ASSERT_PRED2(pred2, val1, val2);    EXPECT_PRED2(pred2, val1, val2);        pred2(val1, val2) returns true 

这些断言最多支持5个参数的输入, 使用方法如下:

bool func(int m, int n) {
    return func2(m , n) > 1;
}

TEST(SelfDefineTest, BoolTest) {
    int m = 1, n = 2;
    EXPECT_PRED2( func, m, n);
}

有时候, 我们想要自定义一些输出, 并且这些输出又需要与各个函数的输入有关, 我们就可以使用下面的断言:

Fatal assertion                                 Nonfatal assertion                              Verifies 

ASSERT_PRED_FORMAT1(pred_format1, val1);        EXPECT_PRED_FORMAT1(pred_format1, val1);        pred_format1(val1) is successful 
ASSERT_PRED_FORMAT2(pred_format2, val1, val2);  EXPECT_PRED_FORMAT2(pred_format2, val1, val2);  pred_format2(val1, val2) is successful 

具体的使用方法可以查看gtest的帮助手册.

另外, 浮点数的检查需要与整数的检查区别对待.

对"相同"的浮点数的比较:

Fatal assertion                         Nonfatal assertion                      Verifies 

ASSERT_FLOAT_EQ(expected, actual);      EXPECT_FLOAT_EQ(expected, actual);      the two float values are almost equal 
ASSERT_DOUBLE_EQ(expected, actual);     EXPECT_DOUBLE_EQ(expected, actual);     the two double values are almost equal 

对相近的浮点数的比较:

Fatal assertion                         Nonfatal assertion                      Verifies 

ASSERT_NEAR(val1, val2, abs_error);     EXPECT_NEAR(val1, val2, abs_error);     the difference between val1 and val2 doesn't exceed the given absolute error 

3 事件

gtest的事件一共有3种:

  1. 全局的, 所有案例执行的前后.
  2. TestSuite级别的, 在某一批TestCase中第一个TestCase前, 最后一个TestCase执行后.
  3. TestCase级别的, 每个TestCase前后.

3.1 全局事件

实现全局事件的方法是写一个类, 继承testing::Environment类, 实现里面的SetUp和 TearDown方法. 这其中, SetUp()方法会在所有案例执行前执行, 而TearDown()方法会在 所有案例执行后执行.

#include "gtest/gtest.h"
namespace {
    class GrobalEvent : public testing::Environment {
        public:
            virtual void SetUp() {
                std::cout << "gtest introduction example. SetUp." << std::endl;
            }

            virtual void TearDown() {        
                std::cout << "gtest introduction example.TearDown." << std::endl;
            }
    };

    class FooTest : public ::testing::Test {
    };

    TEST_F(FooTest, ZeroEqual) {
        EXPECT_EQ(0,0);
    }

    TEST_F(FooTest, OneEqual) {
        EXPECT_EQ(1,1);
    }

在定制好这个类之后, 我们需要在main函数中使用testing::AddGlobalTestEnvironment 方法将这个类中的事件添加进来, 也就是说在一次测试中我们可以添加多个这样的事件, 至于事件的执行顺序:

int _tmain(int argc, _TCHAR* argv[]) {
    testing::AddGlobalTestEnvironment(new GrobalEvent);
    testing::InitGoogleTest(&argc, argv);
    return RUN_ALL_TESTS();
}

3.2 TestSuite的事件

对于TestSuite的事件, 我们需要写一个类, 继承testing::Test, 然后实现两个静态方法:

  1. SetUpTestCase() 方法在第一个TestCase之前执行
  2. TearDownTestCase() 方法在最后一个TestCase之后执行

我们同样在try.cpp中进行添加:

#include "gtest/gtest.h"

namespace {
    // The fixture for testing class Foo.
    class FooTest : public ::testing::Test {
        protected:
            static void SetUpTestCase() {
                std::cout << " SetUpTestCase." << std::endl;
            }

            static void TearDownTestCase() {
                std::cout << " TearDownTestCase." << std::endl;
            }
    };

    TEST_F(FooTest, ZeroEqual) {
        EXPECT_EQ(0,0);
    }

    TEST_F(FooTest, OneEqual) {
        EXPECT_EQ(1,1);
    }
}

在这里, 后面的TEST_F所使用的参数的第一个都是这个类的名字, 其实这就是这个 TestSuite的名字, TEST_F的第一个参数需要是TestSuite.

3.3 TestCase的事件

最后, 是TestCase的事件, 这种事件是挂在每个案例执行前后的, 实现方式和上面的几乎 一样, 不过需要实现的是SetUp方法和TearDown方法: 1. SetUp()方法在每个TestCase之 前执行; 2. TearDown()方法在每个TestCase之后执行. 其实这两个函数已经在上面多次 出现了, 我们进行一些修改:

#include "gtest/gtest.h"
namespace {
    class FooTest : public ::testing::Test {
        protected:
            virtual void SetUp() {
                std::cout << " testcase event setup." << std::endl;
            }

            virtual void TearDown() {
                std::cout << " testcase event teardown." << std::endl; 
            }
    };

    TEST_F(FooTest, ZeroEqual) {
        EXPECT_EQ(0,0);
    }

    TEST_F(FooTest, OneEqual) {
        EXPECT_EQ(1,1);
    }

} //namespace

4 gtest批量测试中的参数传递

有时候, 针对一个函数, 需要使用不同的输入输出组合来进行测试. 这时候很容易出现代 码重复的情况, 而我们知道软件中的很多隐患都是由于拷贝粘贴引起的, 所以我们要尽量 避免代码复制. 在一般的测试框架中, 很容易就会出现这样的代码:

TEST(myTest, aLotTest) {
    EXPECT_TRUE(func(1));
    EXPECT_TRUE(func(10));
    EXPECT_TRUE(func(100));
    EXPECT_TRUE(func(1000));
    EXPECT_TRUE(func(10000)));
}

在gtest中, 就可以避免这种情况的产生. 我们需要首先定义一个类, 继承 testing::TestWithParam<T>, 其中T就是需要参数化的参数类型, 比如对于前面的代码 我们需要参数化一个int型的参数.

class myTest : public::testing::TestWithParam<int> {
};

然后, 我们需要使用到TEST_P这个宏, 这个P就可以被理解为parameterized. 在 TEST_P宏里, 我们就可以使用GetParam()来获取当前的参数的具体值, 如下所示:

TEST_P(myTest, aLotTest) {
    int n =  GetParam();
    EXPECT_TRUE(func (n));
}

最后, 我们使用宏INSTANTIATE_TEST_CASE_P来告诉gtest这个函数的参数范围:

INSTANTIATE_TEST_CASE_P(PreName, myTest, testing::Values(1, 10, 100, 1000, 10000));

4.1 范围参数

Range(begin, end[, step]) 范围在begin~end之间, 步长为step, 不包括end

4.2 数列/数组

Values(v1, v2, ..., vN) v1,v2到vN的值

ValuesIn(container) and ValuesIn(begin, end) 从一个C类型的数组或是STL容器, 或是迭代器中取值

4.3 布尔

Bool() 取false 和 true 两个值

4.4 组合(Combine)

Combine(g1, g2, ..., gN) 这个比较强悍, 它将g1,g2,...gN进行排列组合, g1,g2,...gN本身是一系列参数生成器, 每次分别从g1,g2,..gN中各取出一个值, 组 合成一个元组(Tuple)作为一个参数.

说明: 这个功能只在提供了<tr1/tuple>头的系统中有效. gtest会自动去判断是否支持 tr/tuple, 如果你的系统确实支持, 而gtest判断错误的话, 你可以重新定义宏 GTEST_HAS_TR1_TUPLE=1.

通过gtest的这种机制, 我们就可以提高测试case的代码质量, 减少重复的代码和工作量, 提高效率.