上一篇博客讲述了开发阶段编写单元测试的意义,对此不再做赘述,实战用法如下。
1.TEST
1.1.适用场景
- 用法简单,几乎适用于任何单一接口的测试;
- 真是一直用一直爽;
1.2.示例
TEST(Normal, NormalTest)
{
ASSERT_EQ(Setting::IsSqlConnected());
}
2.继承testing::Test
2.1.适用场景
- 当测试一系列与类型相关的重载接口或功能相近的接口时;
- 其他与类型相关的场景;
- 灵活运用,利用其部分特点创造更优效果;
2.2.示例
2.2.1.待测试接口
以下接口都是与类型相关的转换接口,将二进制数据转换为不同类型对象。
2.2.2.测试类的编写
声明类模板,继承自testing::Test,
- 该测试类可以为多个测试用例共用,即可以有多个case入口与之绑定;(下同,测试类基本都是这样,方便复用)
- 使用相关宏进行声明定义;
- line130为该case入口;
- line125定义类型参数Types,当运行时会遍历该参数列表,执行case的入口;
- 其中line127、128声明并绑定了测试用例和参数对象;
- line135注册了该测试用例;
- 注意,绑定了定义的测试类后,case入口就可以访问类的成员,比如这里是成员函数ConverterTest(),进而遍历执行case,所以运行测试用例时控制套会显示执行了5个用例;
3.继承testing::TestWithParam<T>
3.1.适用场景
- 看类型就知道和参数有关,是的,参数化测试;
- 什么?一个不够?还需要测试XX场景下的?还需要测试大数据量调用情况?还需要...,批量产生测试数据,并应用测试;
- 当然也可以灵活的使用,即可以应用其一部分长处,我曾经写过一个测试场景是不用其参数化的长处,而在其构建函数和SetUp函数中构造好一份测试对象,再用多个不同case的入口,测试该对象的不同功能接口,比如分别测增删改查相关的接口;当然这里也可以利用其参数化几何的特点,批量构造对象,批量测试...看自己测试的场景和目的了;
3.2.示例
这里展示测试Escape字符串的接口,StrCotent0为原始字符串,StrCotent0为Escape之后的字符串,当然因为特殊情况,有些字符串不需要Escape,用NeedEscape来标记;
struct EscapeData
{
EscapeData(string strContent0, string strContent1, bool needEscape)
{
StrContent0 = strContent0;
StrContent1 = strContent1;
NeedEscape = needEscape;
}
string StrContent0;
string StrContent1;
bool NeedEscape;
};
class TestEscapeString : public testing::TestWithParam<EscapeData>
{
protected:
void SetUp() override;
protected:
bool mNeedEscape;
string mStrContent0;
string mStrContent1;
};
- SetUp函数类似于初始化函数,每次用例执行(在此为遍历每一个参数执行时)首先都要执行SetUp函数 ;
- testing::TestWithParam<T>测试参数为T,可以访问T类型的成员;
- 即对测试参数的数据做进一步的处理,方便测试或有意为之;
void TestEscapeString::SetUp()
{
mStrContent0 = GetParam().StrContent0;
mStrContent1 = GetParam().StrContent1;
mNeedEscape = GetParam().NeedEscape;
}
- GenerateEscapeData函数为批量的创建测试参数;
- 上文说过SetUp函数对测试参数做了进一步解析和处理,并将结果保存在成员字段里;
- TEST_P为入口函数,此处可以访问测试类的成员,即可以访问测试数据(为方便测试而处理后的);
- 进而测试相关接口;
INSTANTIATE_TEST_CASE_P(ESCAPE, TestEscapeString, testing::ValuesIn(GenerateEscapeData()));
TEST_P(TestEscapeString, caseEscapeUnEscape)
{
string strConvert;
string strConvert2;
bool bNeed = JSONInterface::NeedUnEscape(mStrContent0, strConvert);
ASSERT_EQ(bNeed, mNeedEscape);
if (!bNeed)
return;
ASSERT_EQ(strConvert, mStrContent1);
// 此处省略
}
- 再结合TEST宏进一步丰富测试用例,它不香吗?
TEST(testRetoreEscapeSpecificPropertyChar1, testRetoreEscapeSpecificPropertyChar)
{
// 此处省略
}
4.Mock模拟行为
4.1适用场景
当数据格式已设计好,数据生成方和数据使用方可以同时开发,这时候数据使用方需要按照约定格式模拟相关接口;
当需要调用后端服务接口,而此接口还在开发中未开放,需要模拟接口;
其他场景。
4.2.示例
4.2.1.待测试接口
class ParseFileUtils : public ParseFileBase
{
public:
ParseFileUtils();
virtual ~ParseFileUtils();
shared_ptr<char> ParseBinaryFile(wstring filePath) override;
vector<vector<string>> ParseTextFileToGetComponents(wstring textFilePath, string flag) override;
vector<vector<string>> ParseTextFileToGetComponents(wstring textFilePath, string beginFlag, string endFlag) override;
};
4.2.2.测试代码编写
- 需要编写测试类,继承被测试类;
- 并声明要Mock哪些接口;
- 在测试时使用测试类代替被测试类,并预先定义调用接口时有什么行为(支持多种类型的行为,在不同测试用例下模拟不同行为);
class MockParseFileUtils : public ParseFileUtils
{
public:
MockParseFileUtils()
{
Init();
}
MOCK_METHOD1(ParseBinaryFile, shared_ptr<char>(wstring filePath));
MOCK_METHOD2(ParseTextFileToGetComponents, vector<vector<string>>(wstring textFilePath, string flag));
MOCK_METHOD3(ParseTextFileToGetComponents, vector<vector<string>>(wstring textFilePath, string flag, string endFlag));
shared_ptr<char> GetBinaryStream();
protected:
void Init();
// 此处省略
protected:
// 此处省略
};
- 该测试类可以为多个测试用例共用,即可以有多个case入口与之绑定;
- 针对文件无效场景的测试用例;
- 预先定义什么条件下条用接口会有什么行为,这里是模拟无效文件,所以返回nullptr和空数组;
using ::testing::Return;
using ::testing::_;
TEST(MockParseModelFile, MockBadModelFile)
{
// Mock解析文件接口,返回模拟数据
MockParseFileUtils obj;
EXPECT_CALL(obj, ParseBinaryFile(testing::_)).Times(1).WillOnce(Return(shared_ptr<char>(nullptr)));
EXPECT_CALL(obj, ParseTextFileToGetComponents(testing::_, testing::_)).Times(1).WillOnce(Return(vector<vector<string>>()));
EXPECT_CALL(obj, ParseTextFileToGetComponents(testing::_, testing::_, testing::_)).Times(1).WillOnce(Return(vector<vector<string>>()));
ViewDataInfo dataInfo(L"UTV", L"NAME", L"Case1", L"VIEWLOCMOCK", L"PROPLOCMOCK", L"THUMBLOCMOCK");
Exporter exporter(dataInfo, L"D:\\TestFile\\Mock", &obj);
Result* result = exporter.Run();
}
- 该测试类可以为多个测试用例共用,即可以有多个case入口与之绑定;
- 针对文件正常场景的测试用例;
- 预先定义什么条件下条用接口会有什么行为,这里是模拟正常文件,所以调用其他函数返回模拟的正常数据;
TEST(MockParseModelFile, MockNormalModelFile)
{
// Mock解析文件接口,返回模拟数据
MockParseFileUtils obj;
EXPECT_CALL(obj, ParseBinaryFile(testing::_)).Times(1).WillOnce(Return(obj.GetFileBinaryStream()));
EXPECT_CALL(obj, ParseTextFileToGetComponents(testing::_, testing::_)).Times(1).WillOnce(Return(vector<vector<string>>()));
EXPECT_CALL(obj, ParseTextFileToGetComponents(testing::_, testing::_, testing::_)).Times(1).WillOnce(Return(vector<vector<string>>()));
// 此处省略
}
- 当然可以定义多种条件下接口的行为,有兴趣的可以多多尝试;
- 别忘了测试项目(可执行程序exe)的入口为:
int _tmain(int argc, _TCHAR* argv[])
{
testing::InitGoogleTest(&argc, argv);
testing::InitGoogleMock(&argc, argv);
int nRe = RUN_ALL_TESTS();
system("pause");
return nRe;
}
5.原理及流程
- 根据自己的使用和理解,绘制了一张GTest/GMock测试原理流程图,供参考,