GTest单元测试框架的基本使用

GTest

简介: google的单元测试框架

tl;dr

1
2
3
4
5
6
7
8
9
10
11
12
13
#include <iostream>
#include <gtest/gtest.h>

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

TEST(TestFun, Add) {
EXPECT_EQ(add(1, 1), 2);
EXPECT_EQ(add(1, 2), 2) << "add (1, 2) 应该是 " << 3 << "是的的结果是 " << add(1, 2);
ASSERT_EQ(add(2, 2), 4);
}

有一个被测试的方法, add, 通过TEST添加一个测试案例, 可以使用EXPECT和ASSERT两种宏, 实现测试值是否正确, 每个宏后面都可以跟上解释信息。 其中第二个很明显是错误的, 输出结果看下面。

编译命令g++ file.cpp -lgtest -lgtest_main -lpthread -o main

其中用到了三个动态链接库, 第一个是gtest的库, 第二个是运行所有测试的实现, 其实里面就是写了个main函数, 也可以自己写,不连接这个库, 第三个是多线程库, 因为gtest使用了线程。

运行结果:

1573372797119

可以看到失败的地方会有提示。

基本使用

  • 运行指定测试

    --gtest_filter=TestName.*

  • 测试案例宏

    • TEST — 一个测试案例

      原型: TEST(TestCaseName, TestName)

      测试的逻辑是, 每个Case为一组, 组内可以有多个Test, 也就是说, 当测试一个类的时候, 可以这么写 Test(TestClassName, FuntionName)

    • TEST_F

      原型:TEST_F(TestFixtureName, TestName)

      模具测试 当多个测试使用相同或类似的资源的时候, 可以使用模具进行测试, 需要创建模具类, 在里面创建需要的资源。

      SetUp方法和TearDown方法在每次TEST_F执行之前和执行之后执行, 保证每次测试的资源都是没有关联的。

      更多介绍请看模具的使用

    • TEST_P

      //TODO:后续补充

  • 断言

    • ASSERT_* – 当条件判断为假时, 不会往下继续执行

      • ASSERT_EQ(val1, val2)

        断言val1和val2相等, 如果不相等会直接终止程序, 并报告错误, 信息里面有当前测试的案例和测试名称, 以及两个值分别是多少。

        甚至你可以添加一些输出, 让提示更加友好。

        ASSERT_EQ(v1, v2) << "v1 is not equal to v2 , v1 is " << v1 << " v2 is " << v2;

        这样当判断不相等是会输出后面的信息。

    • EXPECT_* – 当条件为假时, 判为faile, 但是会继续向下执行

      用法与ASSERT一样, 唯一的区别就是如果断言值为假也会继续向下执行, 只是报告一个失败,下面的测试会继续进行。

    • 同类的宏

      每个版本的宏都有两个,分别是EXPECT和ASSERT。 区别如上。

      • 大小值比较
      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_TRUE(condition); EXPECT_TRUE(condition); condition is true
        ASSERT_FALSE(condition); EXPECT_FALSE(condition); condition is false
      • 字符串比较

        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

模具的使用

当多个测试使用的数据或对象相同时,可以创建一个模具给这一组测试使用。可以减少重复的代码。

使用步骤

  1. 首先要创建一个模具类并继承::testing::Test
  2. 实现一个构造函数或者Setup(), 以及一个析构函数或者TearDown()
  3. 在构造函数或者Setup()方法里面创建一些测试需要的资源。 同理在析构或TearDown中释放这些资源。

当使用模具进行测试的时候,使用TEST_F 替换TEST即可, 值得注意的是, 宏的第一个参数要改成自己写的模具类的名字。TEST_F(TestFixtureName, TestName) 即TestFixtureName要替换成模具类的名字。然后在函数体里面进行测试即可。

​ 对于每个测试(TEST_F), gtest都会在测试之前调用Setup为其创建测试环境, 当这个测试结束时会调用TearDown来清理现场。

  • 案例:

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    19
    20
    21
    22
    23
    24
    25
    26
    27
    28
    29
    30
    31
    32
    33
    34
    35
    36
    37
    38
    39
    40
    41
    42
    43
    #include <iostream>
    #include <gtest/gtest.h>
    #include <gmock/gmock.h>

    class Foo {
    public:
    Foo (int a = 0) {}
    virtual ~Foo () {}
    virtual int getData() { return data_; }
    virtual void setData(int data) { data_ = data; }
    private:
    int data_;
    };

    class FooTest : public ::testing::Test {
    public:
    FooTest () { }
    virtual ~FooTest () { }
    void SetUp() {
    std::cout << "Before testing" << std::endl;
    foo_.setData(1);
    }
    void TearDown() {
    std::cout << "After the test" << std::endl;
    }
    protected:
    Foo foo_;
    };

    TEST_F(FooTest, TestSetOne) {
    std::cout << "TestSetOne start" << std::endl;
    EXPECT_EQ(foo_.getData(), 1) << "foo_ data is not equal to " << 1;
    int data = 10;
    foo_.setData(data);
    EXPECT_EQ(foo_.getData(), data) << "foo_ data is not equal to " << data;
    std::cout << "TestSetOne end" << std::endl;
    }

    TEST_F(FooTest, TestSetTwo) {
    std::cout << "TestSetOne start" << std::endl;
    EXPECT_EQ(foo_.getData(), 1) << "foo_ data is not equal to " << 1;
    std::cout << "TestSetOne end" << std::endl;
    }

结果:

1573959294258

如案例中所示, 在夹具中定义的资源可以在测试中直接使用,所以当多个测试使用的资源相同的时候,还有复杂的初始化工作的时候,就可以使用夹具,减少重复代码。

  • SetUpCase 与 TearDownCase

    TODO:后续补充

GMock

简介:

​ 模拟类, 当我们写一个模块时,我们要依赖或使用另一个模块的接口或资源,例如数据库操作等。这时候另一个模块还没有写,我们需要测试一下现在写的模块功能是否正确,这就用到gmock了, 它可以模拟一个类, 可以指定一个行为,通过返回我们指定的数据来欺骗过我们写的模块。 例如我们需要数据库查询一个用户的信息,这时候可以模拟这个方法,直接返回一个固定的信息。

Mock Class

​ 创建一个Mock类需要继承需要模拟的类或者接口。 使用MOCKMETHOD 模拟方法。

  • MOCKMETHOD

    MOCKMETHOD(返回值类型, 方法名, (参数), (override等))

​ 写完一个模块需要单元测试时,依赖的另一个模块还没有做完,那么可以mock这个模块。例如 数据库模块。





参考连接: