作为一个例子,我在游戏引擎中有一个LockedCrateObject
。它可以使用密钥锁定,这是我想编写单元测试的功能。尽管该功能永远不会用于单元测试,但它也负责绘制自己。以下是我如何在没有单元测试的情况下实现它:
class LockedCrateObject : IWorldObject
{
IGraphics m_Graphics;
public Inventory Inventory;
public KeySlot KeySlot;
public LockedCrateObject(IGraphics graphics)
{
m_Graphics = graphics;
/* Add some data to Inventory and KeySlot and
bind OnValidKey and OnEndValidKey to KeySlot's Events */
}
void OnValidKey()
{
Inventory.Locked = false;
}
void OnEndValidKey()
{
Inventory.Locked = true;
}
public void Draw() { /* Use m_Graphics to render the crate */ }
}
这将要求单元测试提供IGraphics
的实现,即使它在单元测试期间永远不会被使用。我可以直接将它传递给Draw,但这意味着每个需要在整个引擎中呈现内容的类需要开始遍历每个帧的引用而不是引用传递一次。我担心这会对性能产生影响。我也可以允许将null传递给构造函数,但这意味着有人可能会错误地认为没有它就能正常工作,导致空指针异常。
我应该如何重新设计这个类,以便对crate锁定进行简单的单元测试,而无需创建实现IGraphics
的整个模拟对象?
答案 0 :(得分:2)
一个论点是:关注点的分离表明你的渲染和行为代码应该存在于不同的类中。但是,我知道在一些游戏引擎中实现起来有多难。
因此,作为不必“创建实现IGraphics
的整个模拟对象”的替代方法,“如何轻松创建该模拟?”
例如,使用Moq,可能就是这么简单:
var mockGraphics = new Mock<IGraphics>();
var target = new LockedCrateObject(mockGraphics.Object);
由于您没有对需要IGraphics
对象的方法进行单元测试,因此模拟行为不会充实并不重要。
答案 1 :(得分:0)
我不知道您的IGraphics界面需要做的一切;所以,假设它有一个“绘制”操作,你可以像这样创建一个间谍实现:
public class GraphicsSpy : IGraphics
{
public void Draw()
{
DrawCalled = true;
}
public bool DrawCalled{get;private set;}
}
然后,在单元测试中使用它(取决于您的测试框架):
[Test]
public void LockedCrateObjectIsDrawnCorrectly()
{
var graphicsSpy = new GraphicsSpy();
var lockedCrateObject = new LockedCrateObject(graphicsSpy);
lockedCrateObject.Draw();
Assert.IsTrue(graphicsSpy.DrawCalled);
}