假设我们正在使用TDD实现Stack
类,我们需要为Stack类的每个功能位添加一个新的测试来运行它:
[TestMethod] public void Should_Be_Empty_After_Instantiation()
[TestMethod] public void Should_Not_Be_Empty_After_Pushing_One_Item()
...
现在,另一方面,在进行单元测试时,应该关注我们的类应该提供的外部行为,因此单元测试集检查我的Stack接口的所有预期合同是否都已满足。
我的问题是如何调解这两个方面。
例如,假设我的Stack
在内部使用初始大小为8的数组,如果我的用户想要插入第9个项目,我会希望它增长。为了添加调整大小功能,我希望至少有一个测试可以在那个方向上驱动我的类代码(我是对的吗?)。
另一方面,那会增加一个单元测试(或者这不是真正的单元测试吗?)而不是实际的合同(我假设用户不关心Stack的内部实现)但是它的实现。
所以我们在这里有一个转折,我不知道如何解决。我在这里混淆概念吗?
由于
经过大量谷歌搜索后,我来到以下似乎处理此问题的链接: http://stephenwalther.com/blog/archive/2009/04/11/tdd-tests-are-not-unit-tests.aspx
答案 0 :(得分:4)
您可以编写一个将第九个项目推送到堆栈的测试。如果你没有任何调整大小的逻辑,这显然会失败。但是,将9硬编码到测试中似乎是一个坏主意,因为您将把Stack的内部实现细节合并到测试中。
现在,TDD测试的编写经常告诉作者他的API可能存在差距。在这种情况下,测试希望能够指定堆栈的初始预分配大小。然后它可以将它设置为8或2或其他任何东西并再推一个项目。并且,认为其他客户端也可能想要它(例如,它类似于std :: vector的reserve方法)并不是不现实的。所以,我会考虑向Stack添加一个构造函数参数来指定初始保留大小,默认为8,并添加一个Should_Not_Error_When_Pushing_More_Items_Than_Initial_Size测试。
答案 1 :(得分:1)
我会说在引用特定类的TDD和外部行为时它们是相同的。在编写TDD样式代码时,您将关注使用类的公共API时的行为。所以你的前两个测试用例是正确的,因为他们在类上测试公共的东西(堆栈的大小)。
至于内部实现以及是否使用数组,测试并不关心它是如何完成的,只是功能对给定的测试有效。您可以选择在稍后阶段以不同方式实现它,并且您的测试将验证行为保持不变(测试在重构后不会失败)
答案 2 :(得分:0)
你在这里有几个方面的行为:
如果你在测试它们时遇到问题,你总是可以把增长内部数组的行为放到另一个类中,使它与堆栈的行为分开。这是过载的单一责任原则!然后,您可以使用一个非常大的数组来测试堆栈的行为,并检查堆栈是否正确地使用模拟代表其职责(如果您没有模拟框架,则可以自行编译)。 / p>
我发现这是一种有用的计时,线程或其他任何封装内部行为的技术,不一定能从外部看到。当然,你在某种程度上牺牲了表现。如果这很重要,那么从它的调用类的角度来看,只需要担心堆栈的行为,并使用系统性能和分析测试来获取其余的。