单元测试:DRY与可预测性

时间:2009-10-28 06:03:54

标签: unit-testing

我们是否应该针对DRY,在某种意义上,功能的变化会影响尽可能少的代码,在编写单元测试时,我们的可预测性,即代码的操作是微不足道的?基本上我要问的是创建辅助方法之间的权衡,这些方法非常通用,可以由多个单元测试使用,而不是只将测试代码限制在单个单元测试中。举个例子来看一个具有以下方法签名的工厂:

public Node buildNode(String path, String name, Map<String, Object> attributes);

根据提供的参数,生成的Node对象将不同,因此我们需要测试不同的可能性。如果我们的目标是可预测性,我们可能会编写两个独立的单元测试,如第一个示例中所示,但如果我们的目标是DRY,我们宁愿添加一个常见的辅助方法,如第二个示例中所示:

EXAMPLE1:
@Test
public void testBuildBasicNode() {
  Node node = testee.buildNode("/home/user", "Node", null);
  assertEquals("/home/user/Node", node.getAbsolutePath());
  assertEquals(false, node.isFolder());
}

@Test
public void testBuildAdvancedNode() {
  Map<String, Object> attributes = new HashMap<String, Object>();
  attributes.put("type", NodeType.FOLDER);
  Node node = testee.buildNode("/home/user", "Node", attributes);
  assertEquals("/home/user/Node", node.getAbsolutePath());
  assertEquals(true, node.isFolder());
}

EXAMPLE2:
@Test
public void testBuildBasicNode() {
  Node node = testee.buildNode("/home/user", "Node", null);
  Node comparisonNode = buildComparisonNode("/home/user", "Node", null);
  assertEquals(comparisonNode, node);
}

@Test
public void testBuildAdvancedNode() {
  Map<String, Object> attributes = new HashMap<String, Object>();
  attributes.put("type", NodeType.FOLDER);
  Node node = testee.buildNode("/home/user", "Node", attributes);
  Node comparisonNode = buildComparisonNode("/home/user", "Node", attributes);
  assertEquals(comparisonNode, node);
}

private Node buildComparisonNode(String path, String name, Map<String, Object> attributes) {
  // Not just a duplicate of the buildNode method,
  // can be more trivial if we only limit it to unit tests that share some common attributes
  ...
}

我对第一个例子(可预测性)的问题是,如果任何功能发生变化(比如说应该如何格式化AbsolutePath),它需要在我的所有单元测试中进行更改。我的第二个例子的问题是buildComparisonNode感觉就像应该测试的东西,我当然不想开始为测试编写测试。

另外,作为一个结束的想法,你会为示例单元测试中使用的文字字符串声明最终变量,还是它们没有问题?

3 个答案:

答案 0 :(得分:7)

  1. 好问题。我之前听说过“单元测试可以湿润,但不能浸湿......”为了测试的可维护性,重点(或可预测性)是关键。你的团队越大,我认为对你来说就越重要。另一件需要考虑的事情是,如果有特定的测试帮助程序代码,它本身就可以成为一个API,所以如果每个人都知道如何利用它可能并不是一个坏主意。我的经验法则是,如果我可以在自动重构中使用IDE来删除重复,我可以给它一个好名字。

  2. 建议......查看Nat Pryce关于更易于维护/可扩展的方法的Test Data Builder模式说明。

答案 1 :(得分:4)

虽然DRY适用于生产代码,但它并不总是适用于单元测试。你真的希望每个测试彼此独立,这通常意味着重复自己。另一方面,我发现将某些事物分组为所有测试使用的辅助方法是有用的,只要它不将测试耦合在一起就应该没问题。我通常减少重复的一个地方是使用测试数据构建器来构建在我的测试中以特定状态存在的对象。

我的经验法则是让我的测试尽可能小和可读。如果使用DRY可以帮助实现这一点,那么我就使用它。如果没有,那我就没有。 : - )

希望有所帮助。我不是单元测试的世界级专家,所以我可能非常错。 : - )

答案 2 :(得分:0)

要考虑的另一件事 - 通常当您在测试中删除重复时,它会告诉您生产代码或设计正在等待合法的原因(在生产代码库中使用)将其转移到生产代码中。我不记得我到底在哪里啊哈哈......但我觉得它与TDD as if you mean it有关。