使用GTestGMock进行C++单元测试(二)

it2023-10-06  72

上一篇博客讲述了开发阶段编写单元测试的意义,对此不再做赘述,实战用法如下。

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测试原理流程图,供参考,

最新回复(0)