不要将cpputest和cppunit混淆。后者已经停止维护,使用起来也不方便。
cpputest支持Google mock。
cpputest的简单用法
TEST_GROUP定义一个测试套件
TEST定义一个测试用例
IGNORE_TEST不执行一个测试用例
断言
o CHECK(boolean condition) - 检查boolean条件
o CHECK_TEXT(boolean condition, text) - 检查boolean条件,并在失败时输出text
o CHECK_EQUAL(expected, actual) - 用==检查是否相等。如果你自定义从类支持==()重载,需要提供一个StringFrom()函数来帮助断言失败时输出字符串
o CHECK_THROWS(expected_exception, expression) - 检查异常是否匹配
o STRCMP_EQUAL(expected, actual) - 用 strcmp()函数匹配const char* strings是否相等
o LONGS_EQUAL(expected, actual) - 比较两个数字
o BYTES_EQUAL(expected, actual) -比较两个8位数字
o POINTERS_EQUAL(expected, actual) - 比较两个指针
o DOUBLES_EQUAL(expected, actual, tolerance) - 用指定误差比较两个双精度数
o FAIL(text) - 一直失败
cpputest的mock支持
设计之初,mock的支持是并没有的。后面加入的时候考虑到:
和cpputest一样的设计目标 - 使用有限的C++功能集合,便于兼容嵌入式开发使用的各种编译器,它们对C++标准的支持比较弱。可能没有提供RTTI,没有STL。
不需要生成代码(注:TDD的工具不应该增加负担,尽可能地简单和快速,不要有很多流程和步骤,最后只需要按个键,比如F5)
没有或者使用很少的宏(注:当然不可能做到完全没有)
使用简单
开发者拥有控制权
mock是以扩展的形式加入cpputest的。使用的使用需要#include "CppUTestExt/MockSupport.h"
使用mock检查函数是否被调用
应该鼓励检查测试的输出。因为如果测试检查了太多内部实现的细节,那么实现的变化会导致对应的测试也需要变化,增加了维护的代价。但是UT作为一种白盒测试,对关键函数调用的检查,关键内部数据的检查,可以帮助我们定位问题,避免代码破坏。写测试的时候需要自己把握好这种平衡。
#include "CppUTest/TestHarness.h"
#include "CppUTestExt/MockSupport.h"
TEST_GROUP(MockDocumentation)
{
void teardown()
{
mock().clear();
}
};
void productionCode()
{
mock().actualCall("productionCode");
}
TEST(MockDocumentation, SimpleScenario)
{
mock().expectOneCall("productionCode");
productionCode();
mock().checkExpectations();
}
mock()返回一个全局的MockSupport对象。它记录我们对函数调用的期望。mock().expectOneCall("productionCode")就是期望expectOneCall函数被调用一次。mock().checkExpectations()在产品代码执行完之后,检查我们的期望是否都得到满足。
如果我们去掉productionCode()的调用,或者productionCode()里面mock().actualCall("productionCode")调用,执行就会报错:
error: Failure in TEST(MockDocumentation, SimpleScenario)
Mock Failure: Expected call did not happen.
EXPECTED calls that did NOT happen:
productionCode -> no parameters
ACTUAL calls that did happen (in call order):
<none>
如果使用MockSupportPlugin,mock().checkExpectations()和mock().clear()都是不需要的。
使用mock检查对象成员函数否被调用
跟函数的调用检查类似,只是需要创建一个mock对象了。
class ClassFromProductionCode
{
public:
virtual void importantFunction(){};
virtual ~ClassFromProductionCode(){};
};
class ClassFromProductionCodeMock : public ClassFromProductionCode
{
public:
virtual void importantFunction()
{
//mock().actualCall("importantFunction");
mock().actualCall("importantFunction").onObject(this);
};
virtual ~ClassFromProductionCodeMock(){};
};
TEST(MockDocumentation, SimpleScenarioObject)
{
//mock().expectOneCall("importantFunction");
ClassFromProductionCode* object = new ClassFromProductionCodeMock; /* create mock instead of real thing */
mock().expectOneCall("importantFunction").onObject(object);
object->importantFunction();
mock().checkExpectations();
delete object;
}
如果检查失败,同样会报错。
error: Failure in TEST(MockDocumentation, SimpleScenarioObject)
Mock Failure: Expected call did not happen.
EXPECTED calls that did NOT happen:
(object address: 0x23af500)::importantFunction -> no parameters
ACTUAL calls that did happen (in call order):
<none>
检查函数调用的参数
只是检查一个函数是否被调用过可能意义不大,我们更关心函数调用的时候传入的参数是否正确。对于int, double, const char*和void*类型的参数,我们直接可以设置期望调用和实际调用:
mock().expectOneCall("function").onObject(object).withParameter("p1", 2).withParameter("p2", "hah");
mock().actualCall("function").onObject(this).withParameter("p1", p1).withParameter("p2", p2);
如果函数使用的参数是其他类型,就需要指定调用的参数类型了。
mock().expectOneCall("function").withParameterOfType("myType", "parameterName", object);
Mock怎么识别期望调用的函数参数和实际调用的函数参数类型是一致的呢?cpputest需要我们定义并安装一个比较器。
class MyTypeComparator : public MockNamedValueComparator
{
public:
virtual bool isEqual(void* object1, void* object2)
{
return object1 == object2;
}
virtual SimpleString valueToString(void* object)
{
return StringFrom(object);
}
};
MyTypeComparator comparator;
mock().installComparator("myType", comparator);
isEqual用来比较参数。valueToString用来在测试失败时输出一个表示期望值和实际值的字符串。
输出参数
函数调用的时候,有些参数是输入参数,有些参数是输出参数。输出参数传递一个给函数内部修改的引用。
int outputValue = 4;
mock().expectOneCall("Foo").withOutputParameterReturning("bar", &outputValue, sizeof(outputValue));
void Foo(int *bar)
{
mock().actualCall("foo").withOutputParameter("bar", bar);
}
函数返回值
产品代码依赖Mock函数的返回值的时候使用。
mock().expectOneCall("function").andReturnValue(10);
int function () {
return mock().actualCall("function").returnIntValue();
}
传递其它数据
除了上面的方式,我们可以通过Mock传递数据。
ClassFromProductionCode object;
mock().setData("importantValue", 10);
mock().setDataObject("importantObject", "ClassFromProductionCode", &object);
ClassFromProductionCode * pobject;
int value = mock().getData("importantValue").getIntValue();
pobject = (ClassFromProductionCode*) mock().getData("importantObject").getObjectPointer();
MockSupport的其他功能
1. 如果你只想检查一个函数是否被调用过一次,忽略其他函数调用的检查:
mock().expectOneCall("foo");
mock().ignoreOtherCalls();
只是检查foo被调用过一次,而且仅有的一次,其他函数调用检查将会被忽略(不同名的函数调用检查也被忽略)。
2. 暂时关闭和开启Mock
mock().disable();
doSomethingThatWouldOtherwiseBlowUpTheMockingFramework();
mock().enable();
关闭Mock的这段时间,如果产品代码依赖于mock函数怎么办呢?
int function () {
return mock().actualCall("function").returnIntValueOrDefault(5);
}
3. mock().clear()清除所有的期望、设置、比较器。
4. mock().crashOnFailure()可以帮助调试Mock的调用栈。
MockSupport的作用空间
我们可以给mock()指定一个命名的作用空间。这样便于理解和管理。
mock("xmlparser").expectOneCall("open");
mock("xmlparser").actualCall("open");
MockPlugin
MockPlugin帮助我们简化Mock的使用:
在每个测试结束时,调用checkExpectations
在每个测试结束时,清除所有的期望设置
安装所有的比较器
每个测试结束时,移除所有的比较器
示例代码:
#include "CppUTest/TestRegistry.h"
#include "CppUTestExt/MockSupportPlugin.h"
MyDummyComparator dummyComparator;
MockSupportPlugin mockPlugin;
mockPlugin.installComparator("MyDummyType", dummyComparator);
TestRegistry::getCurrentRegistry()->installPlugin(&mockPlugin);