关闭

轻松编写c++单元测试

发表于:2013-8-28 11:06

字体: | 上一篇 | 下一篇 | 我要投稿

 作者:RickyChan0623    来源:51Testing软件测试网采编

  应用 googlemock 编写 Mock Objects

  很多 C++ 程序员对于 Mock Objects (模拟对象)可能比较陌生,模拟对象主要用于模拟整个应用程序的一部分。在单元测试用例编写过程中,常常需要编写模拟对象来隔离被测试单元的“下游”或“上游”程序逻辑或环境,从而达到对需要测试的部分进行隔离测试的目的。

  例如,要对一个使用数据库的对象进行单元测试,安装、配置、启动数据库、运行测试,然后再卸装数据库的方式,不但很麻烦,过于耗时,而且容易由于环境因素造成测试失败,达不到单元测试的目的。模仿对象提供了解决这一问题的方法:模仿对象符合实际对象的接口,但只包含用来“欺骗”测试对象并跟踪其行为的必要代码。因此,其实现往往比实际实现类简单很多。

  为了配合单元测试中对 Mocking Framework 的需要, Google 开发并于 2008 年底开放了: googlemock 。与 googletest 一样, googlemock 也是遵循 New BSD License (可用作商业用途)的开源项目,并且 googlemock 也可以支持绝大多数大家所熟知的平台。

  注 1:在 Windows 平台上编译 googlemock

  对于 Linux 平台开发者而言,编译 googlemock 可能不会遇到什么麻烦;但是对于 Windows 平台的开发者,由于 Visual Studio 还没有提供 tuple ( C++0x TR1 中新增的数据类型)的实现,编译 googlemock 需要为其指定一个 tuple 类型的实现。著名的开源 C++ 程序库 boost 已经提供了 tr1 的实现,因此,在 Windows 平台下可以使用 boost 来编译 googlemock 。为此,需要修改 %GMOCK_DIR%/msvc/gmock_config.vsprops ,设定其中 BoostDir 到 boost 所在的目录,如:

<UserMacro
Name="BoostDir"
Value="$(BOOST_DIR)"
/>

  其中 BOOST_DIR 是一个环境变量,其值为 boost 库解压后所在目录。

  对于不希望在自己的开发环境上解包 boost 库的开发者,在 googlemock 的网站上还提供了一个从 boost 库中单独提取出来的 tr1 的实现,可将其下载后将解压目录下的 boost 目录拷贝到 %GMOCK_DIR% 下(这种情况下,请勿修改上面的配置项;建议对 boost 不甚了解的开发者采用后面这种方式)。

  在应用 googlemock 来编写 Mock 类辅助单元测试时,需要:

  编写一个 Mock Class (如 class MockTurtle ),派生自待 Mock 的抽象类(如 class Turtle );

  对于原抽象类中各待 Mock 的 virtual 方法,计算出其参数个数 n ;

  在 Mock Class 类中,使用 MOCK_METHODn() (对于 const 方法则需用 MOCK_CONST_METHODn() )宏来声明相应的 Mock 方法,其中第一个参数为待 Mock 方法的方法名,第二个参数为待 Mock 方法的类型。如下:

  清单 4. 使用 MOCK_METHODn 声明 Mock 方法

#include <gmock/gmock.h>  // Brings in Google Mock.
class MockTurtle : public Turtle {
MOCK_METHOD0(PenUp, void());
MOCK_METHOD0(PenDown, void());
MOCK_METHOD1(Forward, void(int distance));
MOCK_METHOD1(Turn, void(int degrees));
MOCK_METHOD2(GoTo, void(int x, int y));
MOCK_CONST_METHOD0(GetX, int());
MOCK_CONST_METHOD0(GetY, int());
};

  在完成上述工作后,就可以开始编写相应的单元测试用例了。在编写单元测试时,可通过 ON_CALL 宏来指定 Mock 方法被调用时的行为,或 EXPECT_CALL 宏来指定 Mock 方法被调用的次数、被调用时需执行的操作等,并对执行结果进行检查。如下:

  清单 5. 使用 ON_CALL 及 EXPECT_CALL 宏

using testing::Return;                              // #1,必要的声明
TEST(BarTest, DoesThis) {
MockFoo foo;                                    // #2,创建 Mock 对象
ON_CALL(foo, GetSize())                         // #3,设定 Mock 对象默认的行为(可选)
.WillByDefault(Return(1));
// ... other default actions ...
EXPECT_CALL(foo, Describe(5))                   // #4,设定期望对象被访问的方式及其响应
.Times(3)
.WillRepeatedly(Return("Category 5"));
// ... other expectations ...
EXPECT_EQ("good", MyProductionFunction(&foo));
// #5,操作 Mock 对象并使用 googletest 提供的断言验证处理结果
}
// #6,当 Mock 对象被析构时, googlemock 会对结果进行验证以判断其行为是否与所有设定的预期一致

  其中, WillByDefault 用于指定 Mock 方法被调用时的默认行为; Return 用于指定方法被调用时的返回值; Times 用于指定方法被调用的次数; WillRepeatedly 用于指定方法被调用时重复的行为。

  对于未通过 EXPECT_CALL 声明而被调用的方法,或不满足 EXPECT_CALL 设定条件的 Mock 方法调用, googlemock 会输出警告信息。对于前一种情况下的警告信息,如果开发者并不关心这些信息,可以使用 Adapter 类模板 NiceMock 避免收到这一类警告信息。如下:

  清单 6. 使用 NiceMock 模板

  testing::NiceMock<MockFoo> nice_foo;

  在笔者开发的应用中,被测试单元会通过初始化时传入的上层应用的接口指针,产生大量的处理成功或者失败的消息给上层应用,而开发者在编写单元测试时并不关心这些消息的内容,通过使用 NiceMock 可以避免为不关心的方法编写 Mock 代码(注意:这些方法仍需在 Mock 类中声明,否则 Mock 类会被当作 abstract class 而无法实例化)。

  与 googletest 一样,在编写完单元测试后,也需要编写一个如下的入口函数来执行所有的测试:

  清单 7. 初始化 googlemock 并运行所有测试

#include <gtest/gtest.h>
#include <gmock/gmock.h>
int main(int argc, char** argv) {
testing::InitGoogleMock(&argc, argv);
// Runs all tests using Google Test.
return RUN_ALL_TESTS();
}

  下面的代码演示了如何使用 googlemock 来创建 Mock Objects 并设定其行为,从而达到对核心类 AccountService 的 transfer (转账)方法进行单元测试的目的。由于 AccountManager 类的具体实现涉及数据库等复杂的外部环境,不便直接使用,因此,在编写单元测试时,我们用 MockAccountManager 替换了具体的 AccountManager 实现。

53/5<12345>
《2023软件测试行业现状调查报告》独家发布~

关注51Testing

联系我们

快捷面板 站点地图 联系我们 广告服务 关于我们 站长统计 发展历程

法律顾问:上海兰迪律师事务所 项棋律师
版权所有 上海博为峰软件技术股份有限公司 Copyright©51testing.com 2003-2024
投诉及意见反馈:webmaster@51testing.com; 业务联系:service@51testing.com 021-64471599-8017

沪ICP备05003035号

沪公网安备 31010102002173号