我最近刚开始使用TDD,在阅读了Kent Beck关于测试驱动开发的书之后,我脑子里仍然有很多关于测试设计的问题。
我目前遇到的一个问题是使用Mock对象。以下是一个非常简单的生成报告:
public string MakeFinancialReport()
{
return sys1.GetData() + sys2.GetData() + sys3.GetData();
}
报告必须包含标题,正文和页脚。因此,快速测试报告中是否存在这些标题:
public void TestReport()
{
string report = MakeFinancialReport();
Assert.IsTrue(report.Contains("[Title]") && report.Contains("[Body]") && report.Contains("[Footer]"));
}
为了隔离方法,我想我会嘲笑sys1,sys2和sys3调用。现在,如果他们都是嘲笑,我还有什么可以测试的?此外,当我模仿它们时,为什么我必须告诉模拟对象它们将被调用一次并返回X等。如果它不仅仅是一个黑盒测试而且MakeFinancialReport可以调用尽可能多的调用它想建立报告吗?
我对这个小问题感到困惑,我不确定我错过了什么。我认为Mocking会删除可测试的代码,对于大多数简单的方法,剩下要测试的内容根本没用。
答案 0 :(得分:4)
Martin,我认为你应该为sys1-3使用模拟,但它们只需要足够简单就可以返回单个字符串。
这意味着您的测试应如下所示:
public void TestReport()
{
// Setup mocks for sys1-3
string report = MakeFinancialReport();
Assert.IsTrue(report.equals("abc"));
}
这表明MakeFinancialReport
具有从sys1-3 调用GetData()
的属性,它按照此特定顺序连接结果。
答案 1 :(得分:3)
目前看来,除了与更有趣的合作者互动之外,MakeFinancialReport
几乎没有做任何事情,可能不值得进行单元测试。
如果我为该方法编写了任何单元测试,我可能只是验证该方法在合作者返回null
时是否符合我的预期,主要是为了记录预期的行为(或帮助我决定如果我提前做的预期行为)。目前该方法将失败。这可能没问题,但是值得考虑是否要将空值视为空字符串 - 并且单元测试证明您决定的行为是有意的。
“指定模拟对象行为白盒测试?”绝对 - 如果你的类有一个你正在嘲笑的依赖,你就会把你的测试绑在那个依赖上。但白盒测试有其优点。并非所有协作者互动都与您示例中的互动一样简单。
答案 2 :(得分:2)
你应该只在有用的时候使用模拟对象。如果MakeFinancialReport
,sys1
,sys2
和sys3
都包含复杂的逻辑,那么您需要独立测试每个逻辑。通过将三个sysX
对象的模拟版本提供给MakeFinancialReport
,您可以消除其复杂性,只需测试MakeFinancialReport
。
当您想要测试错误条件和异常处理时,模拟对象特别有用,因为可能很难强制从实际对象中获取异常。
当你谈到不必设置明确的期望和返回值时,这是一个称为存根的相关概念。你可能会发现Martin Fowler的“Mocks Aren't Stubs”很有帮助。
Kent Beck的书是一本很好的介绍,但如果您正在寻找更多细节,我强烈推荐xUnit Patterns book。例如,它有一个mock objects部分,以及test doubles的更一般类别。
答案 3 :(得分:0)
我认为其中一个问题是您的测试将sys1,sys2和sys3的职责与TestReport方法的职责混合在一起。在我看来,您应该将测试分为两部分:
1)MakeFinancialReport()返回sys1,sys2,sys3的串联。在那里你可以使用
的内容来存根sys1等var sys1 =MockRepository.GenerateStub<ISys>();
sys1.Expect(s=>s.GetData()).Return("Part 1");
// etc... for sys2, sys3 var
reportMaker = new ReportMaker(sys1,sys2, sys3);
Assert.AreEqual("Part 1" + "Part 2" + "Part 3", reportMaker.MakeFinancialReport();
拥有MakeFinancialReport()方法的类不应该关心或知道sys类正在做什么。他们可以返回任何类 - MakeFinancialReport()只是连接,这是你应该测试的(如果你认为值得)。
2)从接口sys1,sys2,sys3实现测试GetData()方法。这可能是您要检查您希望看到“身体”,“标题”等等的情况的地方 这里的争吵可能有点过头了,但是这会给你带来的是一个潜在的重度依赖(3个sys实例)的廉价实例化,以及sys做什么以及MakeFinancialReport做什么的明确分离。
顺便说一句,这可能是因为您使用的语言,但令人惊讶的是您的测试不是从拥有MakeFinancialReport()的类的实例化开始。