3

假设我们正在使用 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 个项目,我会希望它增长。为了添加调整大小的功能,我希望至少有一个测试可以推动我的类代码朝那个方向发展(对吗?)。

另一方面,这将添加一个单元测试(或者这不是一个真正的单元测试吗?),它不执行类的实际合同(我假设用户不关心内部实现堆栈)但它的实现。

所以我们这里有一个转折点,我不知道如何解决。我在这里混淆概念了吗?

谢谢

编辑

经过大量谷歌搜索后,我来到了似乎可以解决此问题的以下链接:http: //stephenwalther.com/blog/archive/2009/04/11/tdd-tests-are-not-unit-tests.aspx

4

3 回答 3

4

您可以编写一个将第九个项目推入堆栈的测试。如果您没有任何调整大小的逻辑,那显然会失败。但是,将 9 硬编码到测试中似乎是个坏主意,因为您会将堆栈的内部实现细节合并到测试中。

现在,编写 TDD 测试经常会告知作者他的 API 中可能存在的漏洞。在这种情况下,测试希望能够指定 Stack 的初始预分配大小。然后它可以将其设置为 8 或 2 或其他任何值,然后再推送一个项目。而且,认为其他客户端也可能想要这样并不是不现实的(例如,它类似于 std::vector 的保留方法)。因此,我会考虑向 Stack 添加一个构造函数参数以指定初始保留大小,默认为 8,并添加一个 Should_Not_Error_When_Pushing_More_Items_Than_Initial_Size 测试。

于 2010-08-30T22:10:32.503 回答
1

当提到 TDD 和特定类的外部行为时,我会说它们是同一回事。在编写 TDD 样式代码时,您关注的是使用类的公共 api 时的行为。因此,您的前两个测试用例是正确的,因为它们测试了类上的公共内容(堆栈大小)。

至于内部实现以及是否使用数组,测试并不关心它是如何完成的,只是功能对给定的测试起作用。您可以选择在稍后阶段以不同的方式实现它,并且您的测试将验证行为保持不变(重构后测试不会失败)

于 2010-08-30T21:41:59.217 回答
0

您在这里有几个方面的行为:

  • 允许推送和弹出项目的堆栈
  • 一个存储元素,当添加另一个项目时,它的内部数组会增长。

如果您在测试它们时遇到问题,您总是可以将增长内部数组的行为放到另一个类中,使其与堆栈的行为分开。这是 overdrive 中的单一责任原则!然后,您可以只使用一个非常大的数组来测试堆栈的行为,并使用模拟检查堆栈是否在涉及增长的情况下正确委派了其职责(如果您没有模拟框架,您可以自己滚动)。

我发现这是一种用于计时、线程或任何其他封装内部行为的有用技术,这些行为不一定能从外部看到。当然,你在某种程度上牺牲了性能。如果这很重要,那么只需从调用类的角度担心堆栈的行为,并使用系统性能和分析测试来获取其余部分。

于 2010-09-07T17:28:59.923 回答