使用GTest/GMock进行C++单元测试(二)

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

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

  • 2
    点赞
  • 10
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值