引言
程序开发的时候,往往需要编写一些测试样例来完成功能测试,以保证自己的代码在功能上符合预期,能考虑到一些异常边界问题等等。
gtest快速入门
1.引入gtest
# 使用的是1.10版本,其他版本可根据需要选择
git clone -b v1.10.x https://github.com/google/googletest.git
cd googletest
mkdir build && cd build
cmake .. && make -j4
sudo make install
sudo ldconfig
2.编写第一个单测
2.1 待测试文件
#ifndef __HELLO_H__
#define __HELLO_H__
#include <iostream>
#include <string>
class Animal {
public:
Animal(std::string name)
: _name(name)
{
}
virtual ~Animal() {}
virtual bool eat(const std::string& food) = 0;
private:
std::string _name;
};
class Tigger : public Animal {
public:
Tigger() : Animal("tigger")
{
}
bool eat(const std::string& food) override
{
if (food == "meat") {
return true;
}
return false;
}
};
class Horse : public Animal {
public:
Horse() : Animal("Horse")
{
}
bool eat(const std::string& food) override
{
if (food == "grass") {
return true;
}
return false;
}
};
#endif
2.2 单测文件
#include "hello.h"
#include "gtest/gtest.h"
using namespace ::testing;
namespace {
TEST(TestTigger, CaseEat)
{
Animal *tigger = new Tigger();
bool ret = tigger->eat("meat");
EXPECT_TRUE(ret);
ret = tigger->eat("grass");
EXPECT_FALSE(ret);
delete tigger;
}
TEST(TestHorse, CaseEat)
{
Animal *horse = new Horse();
bool ret = horse->eat("grass");
EXPECT_TRUE(ret);
ret = horse->eat("meat");
EXPECT_FALSE(ret);
delete horse;
}
}
2.3 makefile文件
CXX = g++
CXXFLAGS = -Wall
LIBES = -lgtest -lgtest_main -lpthread
LPATH = -L/tools/googletest/1.11.0/build/lib # 替换成自己lib路径
HPATH = -I/tools/googletest/1.11.0/googletest/include/ # 替换成自己的include路径
UTEST_OBJD = hello_unit_test
hello_unit_test:hello_unit_test.cpp
${CXX} -o $@ $+ -I ../ ${HPATH} ${CXXFLAGS} ${LIBES} ${LPATH}
clean:
rm -rf *_unit_test
make && ./hello_unit_test 编译并执行单测程序,执行结果如下:
gtest常用宏
1. 各种断言
1.1 Bool断言
致命断言 | 非致命断言 | 含义 |
---|---|---|
ASSERT_TRUE(val) | EXPECT_TRUE(val) | val == true |
ASSERT_FALSE(val) | EXPECT_FALSE(val) | val == false |
1.2 二元值断言(比较大小)
致命断言 | 非致命断言 | 含义 |
---|---|---|
ASSERT_EQ(a, b) | EXPECT_EQ(a, b) | a == b |
ASSERT_NE(a, b) | EXPECT_NE(a, b) | a != b |
ASSERT_LT(a, b) | EXPECT_LT(a, b) | a < b |
ASSERT_LE(a, b) | EXPECT_LE(a, b) | a <= b |
ASSERT_GT(a, b) | EXPECT_GT(a, b) | a > b |
ASSERT_GE(a, b) | EXPECT_GE(a, b) | a >= b |
1.3 字符串断言(字符串比较)
致命断言 | 非致命断言 | 含义 |
---|---|---|
ASSERT_STREQ(a, b) | EXPECT_STREQ(a, b) | a == b |
ASSERT_STRNE(a, b) | EXPECT_STRNE(a, b) | a != b |
ASSERT_STRCASEEQ(a, b) | EXPECT_STRCASEEQ(a, b) | a == b 忽略大小写 |
ASSERT_STRCASENE(a, b) | EXPECT_STRCASENE(a, b) | a != b 忽略大小写 |
2. TEST、TEST_F和TEST_P
2.1 TEST
TEST是最基本的构造测试case的宏,基本用法:
TEST(param1, prama2)
{
/*测试代码*/
}
- 参数1:用例名,一般由待测试的类名或函数名组成,如TestAnimal
- 参数2:测试名,代表测试含义,如CaseEat
- 测试结果将以"用例名.测试名"来区分不同测试case
2.2 TEST_F
TEST_F和TEST的不同之处在于,其可以使用到初始化函数(SetUp)和一个清理函数(TearDown)。基本用法如下:
class TestAnimal : public ::testing::Test {
protected:
void SetUp() override
{
// 成员变量初始化
tigger = new Tigger();
}
void TearDown() override
{
// 资源清理、释放
delete tigger;
tigger = NULL;
}
protected:
Animal *tigger;
};
TEST_F(TestAnimal, caseEatMeat)
{
// 执行之前调用SetUp进行初始化
EXPECT_TRUE(tigger->eat("meat"));
// case退出时调用TearDown进行释放
}
TEST_F(TestAnimal, caseEatGrass)
{
// 执行之前调用SetUp进行初始化
EXPECT_FALSE(tigger->eat("grass"));
// case退出时调用TearDown进行释放
}
- 创建一个继承testing::Test的测试类TestAnimal,并在该类中声明成员变量,做好初始化和清理操作
- TEST_F宏
- 参数1:同测试类名(TestAnimal )
- 参数2:测试名,代表测试含义
- 每一个测试case都是相互独立的,当每个case需要共同使用某个变量时,可以将该变量放在测试类中,每执行一个TEST_F宏构造的case,都会调用一次SetUp和TearDown,因此case之间对变量的操作不会相互影响。
2.3 TEST_P
针对某个待测试的方法,当你需要测试不同的输入,但又不想每个case都写一遍时,就可以使用到TEST_P宏,基本使用如下:
// 多个参数时,使用结构体更方便
struct MyParams {
std::string food;
// other params
};
class TestAnimal : public ::testing::Test, public ::testing::WithParamInterface<MyParams>
{
protected:
void SetUp() override
{
// 成员变量初始化
tigger = new Tigger();
}
void TearDown() override
{
// 资源清理、释放
delete tigger;
tigger = NULL;
}
protected:
Animal *tigger;
};
TEST_P(TestAnimal, caseEat)
{
std::string food = GetParam().food; // 获取参数
ASSERT_FALSE(tigger->eat(food));
}
// 构造不同的测试样例
INSTANTIATE_TEST_SUITE_P(TestCaseEatParams, TestAnimal, ::testing::Values(
MyParams{"grass"},
MyParams{"leafs"}
));
- 和TEST_F有相似的功能,使用SetUp、TearDown进行初始化和清理,创建一个继承testing::Test、testing::WithParamInterface的测试类,其中WithParamInterface是一个模板类,用来关联测试参数。
- TEST_P宏
- 参数1:测试类名
- 参数2:测试名,代表测试含义
- INSTANTIATE_TEST_SUITE_P宏
- 参数1:能表明测试含义即可
- 参数2:测试类名
- 参数3:不同测试样例集合
- 执行结果如下:
总结
- 好记性不如烂笔头,最近在写单元测试,于是就有了这篇文章。
- 通过学习gtest的基本语法,已经可以应对一部分测试场景了,然而还有一些场景只通过gtest是无法完成的,比如,在我们的代码中有许多并不是我们自己设计的接口,可能是外部依赖,也可能来自于其他模块,我们没办法设计一个合适的case来让这些接口返回给我们一个预期值,那我们该怎么办呢?于是gmock由此诞生,这个在下一篇中会进行深入学习,篇名我已经想好了(玩转单元测试之GMock)。