减少测试用例之间的耦合

时间:2013-02-20 12:54:42

标签: java unit-testing testing junit tdd

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

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

  1. Deque对象的大小应该增加一个
  2. 如果我随后调用相应的T removeFirst()方法,它应该返回对我通过初始调用插入的对象的引用。
  3. 但是,这会在我的至少两个测试用例之间产生不希望的耦合,其中一个测试用例传递依赖于另一个API方法的正确实现。例如,为了使这个测试用例通过,我需要一个正确的实现来检查Deque中的项目数以及删除项目。如果我对这些方法中的任何一个的测试由于某种原因不正确或不完整,那么我对insertFirst方法的测试将自动被怀疑。

    避免这种情况的最佳做法是什么?我的方法是否以某种方式编写测试用例错误?

2 个答案:

答案 0 :(得分:12)

为一个方法编写测试时, 认为其余的类正常工作。如果你不做这个假设,唯一的结论就是每个类的单个大量测试。这不是我们所做的。

您可以假设班级的其他部分正常工作,因为也会对其他部分进行测试,以确保其正确性。
如果一个部件工作不正常,测试将失败,表明某些事情不正确 一旦测试套件测试失败,就必须修复错误。你不再做任何假设。

示例:

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

  1. 插入
  2. 除去
  3. 计数
  4. 你有三个测试:

    1. 测试insert
      • 创建列表实例(安排
      • insert item( Act
      • 检查count是否等于1(断言
    2. 测试remove
      • 创建列表实例和insert项目(安排
      • remove item( Act
      • 检查count是否等于0(断言
    3. 测试count
      • 创建列表实例和insert n项(安排
      • 检索count行动
      • 检查count是否等于n(断言
    4. 现在,如果上述任何测试失败,您无法确定班级中单个成员的正确性:

      • 如果第一次测试失败,第三次测试也将失败。第二个会通过,但实际上没有测试remove,因为没有什么可以删除。
      • 如果第二次测试失败,其他两项测试仍然会通过。但是,您无法确定insertcount是否正常工作,因为如果三个成员中的任何一个无法正常工作,第二个测试将失败。
      • 如果第三次测试失败,另外两次最有可能失败。

      失败的测试告诉你一些事情:
      根据失败的测试,您通常可以减去错误的位置 示例:如果只有第二个测试失败但不是第一个或第三个测试失败,则错误很可能是remove方法。

答案 1 :(得分:4)

将单元测试视为测试特定功能而非特定方法通常会更高效。任何给定的测试都将检查某些方法集合是否正常工作以实现作为测试主题的特性,并且精心设计的测试集中的失败模式将倾向于告诉您哪种方法很快就会破坏。

好的测试集合往往会自然而然地脱离TDD;这是使技术如此强大的事情之一。如果我正在编写Deque,我写的测试往往是以下内容,通常按此顺序显示。

  1. empty_Deque_isEmpty - 实施isEmpty以始终返回true
  2. non_empty_Deque_isntEmpty - 实施insertFirst以使isEmpty实例变量为false
  3. re_emptied_Deque_isEmpty - 将isEmpty使用的实例变量更改为响应insertFirstremoveFirst
  4. 的数字
  5. is_empty_Deque_size_correct - 实施size始终返回0
  6. is_nonempty_Deque_size_correct - 将实例变量添加到曲目大小;意识到它正在做isEmpty所需的同样的事情;重构
  7. is_re_emptied_Deque_size_correct - 由于我们所做的事情而使测试刚刚过去了5.发生
  8. does_removing_from_empty_Deque_throw - removeFirst需要在执行任何其他操作之前检查size
  9. is_inserted_item_returned - insertFirstremoveFirst现在填充T个实例变量
  10. is_inserted_item_returned_from_end - 添加removeLast的副本removeFirst;重构
  11. is_rear_inserted_item_returned - 添加复制insertLast的{​​{1}};重构
  12. insertFirst - 更改are_all_inserted_items_returnedinsertFirst以对removeFirst采取行动;指出不检查检索顺序
  13. SomeKindOfCollection<T> - 插入两件事,确保does_removeFirst_retrieve_items_in_correct_order返回第二件事。可能已经成真。
  14. removeFirst - 与does_removeLast_retrieve_items_in_correct_order同上,但非常确定不会通过。
  15. 这是一大堆测试,但是当你仔细研究它们时,你应该注意到这种模式。这些测试都不是“removeLast”的测试或“count的测试”。但是到了我们的时候,整个类的接口都在运行,并且开发了该接口所需的所有内部构件。一些测试依赖于多种方法,如果该方法失败,它们都会破坏。但是,断点模式在确定错误的位置方面往往非常有用。

    同样有趣的是我们可以通过多少这些测试来传递,而不需要提交实际在对象中有一个集合,这表明这组测试可以被分解为更通用的测试套件,这将是有用的在开发removeFirst