这与如何使用gmock测试一类称之为基类方法的方法非常密切
我正在使用gtest和gmock测试新功能,所以我有一个基类...
class SimpleObject
{
public:
explicit SimpleObject() {}
virtual void moveX(int dX)
{
// Do important stuff like updating position, bounding box etc.
}
// ...
};
基于其他TDD我有一个派生类,新功能是在我在派生对象上调用movex时,它将做特定的事情,但是它也需要在simpleObject中执行重要的事情:: movex。p>我已经具有与SimpleObject :: Movex函数相关的测试驱动的单元测试,因此我不想重复它们的派生类。只要我知道simpleobject :: movex被称为hovex,那么一切都是笨拙的。
无论如何,基于上面的链接,然后遵循TDD,我最终得到了以下内容。
派生类:
class ComplexObject : public SimpleObject
{
public:
virtual void moveX(int dX)
{
// Do something specific
}
};
"可测试"类:
class TestableComplexObject : public ComplexObject
{
public:
MOCK_METHOD1(moveX, void(int dX));
void doMoveX(int dX)
{
SimpleObject::moveX(dX);
}
};
测试:
TEST_F(ATestableComplexObject, CallsBaseClassMoveXWhenMoveXIsCalled)
{
int dX(8);
TestableComplexObject obj;
EXPECT_CALL(obj, moveX(dX))
.Times(1)
.WillRepeatedly(testing::Invoke(&obj, &TestableComplexObject::doMoveX));
obj.moveX(dX);
}
如果我运行测试,那么一切都会通过。这是不正确的,因为您可以看到ComplexObject :: Movex无需做任何事情。
也,无论我在Domovex中放置什么(我认为是为了设置我的期望),测试仍然会通过。
我显然在这里缺少一些简单的东西,所以有什么想法吗?
感谢您的评论,对设计进行了调整,我可以测试我想要的内容。
首先,为SimpleObject创建一个接口:
class ISimpleObject
{
public:
virtual void moveX(int dX) = 0;
};
我的简单对象类,然后实现此处:
class SimpleObject : public ISimpleObject
{
public:
explicit SimpleObject() {}
virtual void moveX(int dX)
{
(void) dX;
// Do important stuff like updating position, bounding box etc.
}
};
它不是从SimpleObject继承而来的复杂对象,而是从界面继承并拥有一个简单对象(即它具有'而不是"是")。构造函数确保我们通过一个简单的对象,而这使模拟更容易。
class ComplexObject : public ISimpleObject
{
public:
ComplexObject(SimpleObject *obj)
{
_obj = obj;
}
virtual void moveX(int dX)
{
_obj->moveX(dX);
}
private:
SimpleObject *_obj;
};
现在,我只是模拟我从SimpleObject感兴趣的电话
class SimpleObjectMock : public SimpleObject
{
public:
MOCK_METHOD1(moveX, void(int dX));
// Do important stuff like updating position, bounding box etc.
};
测试也简化了
TEST_F(AComplexObject, CallsBaseClassMoveXWhenMoveX)
{
int dX(8);
SimpleObjectMock mock;
ComplexObject obj(&mock);
EXPECT_CALL(mock, moveX(dX)).Times(1);
obj.moveX(dX);
}
行为是预期的。如果ConspectObject :: Movex函数为空(就像开始时一样),则测试失败。它只有在您调用SimpleObject :: Movex。
您需要以检查是否可能调用SimpleObject::moveX
的方式来设计ComplexObject
。做到这一点的一种方法:用一些可以模拟的其他功能封装此基本调用:
class ComplexObject : public SimpleObject
{
public:
virtual void moveX(int dX)
{
// Call base function
baseMoveX(dx);
// Do something specific
}
protected:
virtual void baseMoveX(int dX)
{
SimpleObject::moveX(dx);
}
};
然后在您的Testable
类中,只需模拟此基本功能:
class TestableComplexObject : public ComplexObject
{
public:
MOCK_METHOD1(baseMoveX, void(int dX));
};
您不能仅模拟moveX
,因为 - 无法区分基础和在这种情况下派生。
所以 - 您的测试看起来像这样:
TEST_F(ATestableComplexObject, CallsBaseClassMoveXWhenMoveXIsCalled)
{
int dX(8);
TestableComplexObject obj;
EXPECT_CALL(obj, baseMoveX(dX))
.WillOnce(testing::Invoke([&obj] (auto dx) {obj.SimpleObject::moveX(dx); }));
obj.moveX();
}
[update]
在注释中发现 - 仍然存在一个问题,如何确保ConspectObject :: BaseMovex()调用SimpleObject :: Movex。
可能的解决方案是在复杂对象和SimpleObject之间再将一个类放置。
template <typename BaseObject>
class IntermediateObject : public BaseObject
{
public:
virtual void baseMoveX(int dX)
{
BaseObject::moveX(dx);
}
};
通过测试确保这确实发生了:
class TestableBaseMock
{
public:
MOCK_METHOD1(moveX, void(int dX));
};
TEST(IntermediateObject Test, shallCallBaseMoveX)
{
const int dX = 8;
IntermediateObject<TestableBaseMock> objectUnderTest;
TestableBaseMock& baseMock = objectUnderTest;
EXPECT_CALL(baseMock, moveX(dX));
objectUnderTest.baseMoveX(dx);
}
然后 - 将其放在简单和复杂的类之间:
class ComplexObject : public IntermediateObject<SimpleObject>
{
public:
virtual void moveX(int dX)
{
// Call base function
baseMoveX(dx);
// Do something specific
}
};
最后 - 我只想强调改变您的原始设计 - 使用汇总而不是继承(又称装饰器设计模式)是最好的方法。首先 - 正如您在我的回答中看到的 - 尝试使用继承使设计更糟,如果我们想测试它。第二 - 装饰器设计的测试要简单得多,就像答案之一中所示...
这里的主要问题是,在方法TestableComplexObject::doMoveX
中,您正在调用方法SimpleObject::moveX
。这就是为什么一切都过去了。您应该调用属于ComplexObject
类的方法moveX
。因此,将方法doMoveX
更改为:
void doMoveX(int dX)
{
ComplexObject::moveX(dX);
}
将解决您的问题。
您发布的代码还有一个问题。测试体中的最后一个说法应为:
obj.moveX(dX);
,但我想这只是编写问题时犯的错误?
希望这会有所帮助!