7

我正在尝试了解有关 JUnit 和 TDD 的更多信息,但我遇到了一些与测试用例之间的耦合有关的问题。

当我为特定数据类型的 API 编写测试用例时,比如 a Deque<T>,如何限制测试用例之间的耦合?例如,如果我正在为 method 编写一个测试用例insertFirst(T item),那么假设我应该能够在正确初始化对象上调用该方法后断言两件事:

  1. 对象的大小Deque应该增加一
  2. 如果我随后调用相应的T removeFirst()方法,它应该返回对我在初始调用中插入的对象的引用。

但是,这会在我的至少两个测试用例之间产生不希望的耦合,其中一个测试用例的通过取决于另一种 API 方法的正确实现。例如,为了让这个测试用例通过,我需要一个正确的实现来检查项目的数量Deque以及删除项目。如果我对其中任何一种方法的测试由于某种原因不正确或不完整,那么我对该insertFirst方法的测试将自动受到怀疑。

避免这种情况的最佳做法是什么?我编写测试用例的方法在某种程度上是错误的吗?

4

2 回答 2

13

在为一种方法编写测试时,您必须假设类的其余部分工作正常。如果你不做这个假设,唯一的结论就是每个班级进行一次大规模的测试。这不是我们所做的。

您可以假设该类的其他部分正常工作,因为也会对这些其他部分进行测试,以确保它们的正确性。
如果某个部分工作不正常,则测试将失败,向您显示某些地方不正确。
一旦您的测试套件的测试失败,您就必须修复一个错误。你不能再做任何假设了。

例子:

你有一个简单的列表实现,只有三种方法:

  1. 插入
  2. 消除
  3. 数数

你有三个测试:

  1. 测试insert
    • 创建列表实例(排列
    • insert项目(法案
    • 检查是否count等于 1(断言
  2. 测试remove
    • 创建列表和insert项目的实例(排列
    • remove项目(法案
    • 检查是否count等于 0(断言
  3. 测试count
    • 创建列表和insertn 个项目的实例(排列
    • 检索count行为
    • 检查是否count等于 n(断言

现在,如果上述任何测试失败,您就无法确定班级中单个成员的正确性:

  • 如果第一个测试失败,第三个测试也将失败。第二个会通过,但实际上并没有 test remove,因为没有什么要删除的。
  • 如果第二个测试失败,其他两个测试仍然会通过。尽管如此,您仍不能确定它insert是否count正常工作,因为如果三个成员中的任何一个不能正常工作,第二个测试将失败。
  • 如果第三个测试失败,另外两个很可能也会失败。

但是,失败的测试告诉你一些事情:
根据失败的测试,你通常可以推断出错误所在。
示例:如果只有第二个测试失败,而第一个或第三个测试没有失败,则错误很可能出在remove方法中。

于 2013-02-20T12:57:10.237 回答
4

将单元测试视为测试特定功能而不是特定方法通常更有效率。任何给定的测试都将检查某些方法集合是否正确地实现了作为测试主题的功能,并且设计良好的一组测试中的失败模式往往会告诉您哪个方法很快就崩溃了。

大量的测试往往会自然而然地脱离 TDD;这是使这项技术如此强大的原因之一。如果我正在写 a Deque,我写的测试往往如下,通常按此顺序呈现。

  1. empty_Deque_isEmpty-- 实现isEmpty总是返回true
  2. non_empty_Deque_isntEmpty-- 实现insertFirst使isEmpty实例变量为假
  3. re_emptied_Deque_isEmpty-- 将使用的实例变量更改isEmpty为响应insertFirst和的数字removeFirst
  4. is_empty_Deque_size_correct-- 实现size始终返回 0
  5. is_nonempty_Deque_size_correct-- 添加实例变量以跟踪大小;意识到它正在做同样的事情isEmpty;重构
  6. is_re_emptied_Deque_size_correct-- 让测试通过,因为我们做了什么来实现 5. 发生
  7. does_removing_from_empty_Deque_throw--在做任何其他事情之前removeFirst需要检查size
  8. is_inserted_item_returned--insertFirst现在removeFirst填充一个T实例变量
  9. is_inserted_item_returned_from_end-- 加上removeLast那是一个副本removeFirst;重构
  10. is_rear_inserted_item_returned-- 添加insertLast该副本insertFirst;重构
  11. are_all_inserted_items_returned——改变insertFirstremoveFirst采取行动SomeKindOfCollection<T>;不检查检索顺序
  12. does_removeFirst_retrieve_items_in_correct_order-- 插入两个东西,确保第二个由removeFirst. 可能已经是真的了。
  13. does_removeLast_retrieve_items_in_correct_order- 同上removeLast,除非很确定不会通过。

这是一大堆测试,但是当您查看它们时,您应该注意到其中的模式。这些测试都不是真正的“测试count”或“测试removeFirst”。但是当我们完成时,该类的整个接口都在运行,并且该接口所需的所有内部组件都已开发完毕。一些测试依赖于不止一种方法,如果该方法失败,它们都会中断。但是中断的模式往往会非常有助于确定错误在哪里。

同样有趣的是,我们可以通过多少这些测试而无需承诺在对象中实际拥有一个集合,这表明可以将这组测试分解成一个更通用的测试套件,这在开发PriorityQueue.

于 2013-02-20T23:20:08.417 回答