Unit testing 单元测试:干燥与可预测性

Unit testing 单元测试:干燥与可预测性,unit-testing,Unit Testing,在编写单元测试时,我们是否应该以干涸为目标,即对功能的更改对代码的影响尽可能小,我们的可预测性,即代码的操作微不足道?基本上,我要问的是创建非常通用且可由多个单元测试使用的辅助方法与仅将测试代码约束为单个单元测试之间的权衡。以具有以下方法签名的工厂为例: public Node buildNode(String path, String name, Map<String, Object> attributes); 公共节点buildNode(字符串路径、字符串名称、映射属性);

在编写单元测试时,我们是否应该以干涸为目标,即对功能的更改对代码的影响尽可能小,我们的可预测性,即代码的操作微不足道?基本上,我要问的是创建非常通用且可由多个单元测试使用的辅助方法与仅将测试代码约束为单个单元测试之间的权衡。以具有以下方法签名的工厂为例:

public Node buildNode(String path, String name, Map<String, Object> attributes);
公共节点buildNode(字符串路径、字符串名称、映射属性);
根据提供的参数,结果节点对象将不同,因此我们需要测试不同的可能性。如果我们的目标是可预测性,我们可能会编写两个独立的单元测试,如第一个示例中所示,但如果我们的目标是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
  ...
}
示例1:
@试验
公共void testBuildBasicNode(){
Node Node=testee.buildNode(“/home/user”,“Node”,null);
assertEquals(“/home/user/Node”,Node.getAbsolutePath());
assertEquals(false,node.isFolder());
}
@试验
public void testBuildAdvancedNode(){
Map attributes=newhashmap();
attributes.put(“type”,NodeType.FOLDER);
Node Node=testee.buildNode(“/home/user”,“Node”,属性);
assertEquals(“/home/user/Node”,Node.getAbsolutePath());
assertEquals(true,node.isFolder());
}
例2:
@试验
公共void testBuildBasicNode(){
Node Node=testee.buildNode(“/home/user”,“Node”,null);
Node comparisonNode=buildComparisonNode(“/home/user”,“Node”,null);
assertEquals(比较节点、节点);
}
@试验
public void testBuildAdvancedNode(){
Map attributes=newhashmap();
attributes.put(“type”,NodeType.FOLDER);
Node Node=testee.buildNode(“/home/user”,“Node”,属性);
Node comparisonNode=buildComparisonNode(“/home/user”,“节点”,属性);
assertEquals(比较节点、节点);
}
私有节点buildComparisonNode(字符串路径、字符串名称、映射属性){
//不仅仅是buildNode方法的副本,
//如果我们只将它限制在共享一些公共属性的单元测试中,那么它可能会变得更简单
...
}
我对第一个示例(可预测性)的问题是,如果任何功能发生更改(比如说AbsolutePath应该如何格式化),它需要在我的所有单元测试中进行更改。我对第二个示例的问题是buildComparisonNode感觉也应该进行测试,我当然不想开始为测试编写测试


另外,作为结束语,您会为示例单元测试中使用的文本字符串声明最终变量吗?还是说它们现在还可以?

虽然DRY适用于生产代码,但并不总是适用于单元测试。你真的希望每个测试彼此独立,这通常意味着重复你自己。另一方面,我发现将某些东西分组到所有测试都使用的助手方法中是很有用的,只要它不将测试耦合在一起,那么就可以了。我通常减少重复的一个地方是使用测试数据构建器来构造在测试中处于特定状态的对象

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

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

  • 好问题。我以前听过“单元测试可以是湿的,但不能是湿的……”对于测试的可维护性,重点(或可预测性)是关键。你的团队越大,我认为这对你越重要。另一个要考虑的是,如果有特定的测试帮助代码,那么它本身就可以成为一个API,所以如果每个人都知道如何利用它,这可能不是一个坏主意。我的经验法则是,如果我能在自动重构中用IDE实现,我会消除重复,并且我能给它起个好名字

  • 一个建议。。。查看Nat Pryce编写的模式writeup,了解一种更易于维护/扩展的方法


  • 另一件需要考虑的事情——通常当您在测试中删除重复时,它会告诉您某个产品代码或设计正在等待一个合法的理由(在生产代码库中的使用)将其移动到生产代码中。我记不清我是从哪里得到这个啊哈时刻的。。。但我认为这与。

    一个单元案例=一次测试有关。我看不出DRY=)谢谢,测试数据生成器模式非常巧妙+1:不要沉迷于干单元测试,那会破坏目的。测试必须是超级清晰的(值得信赖的),并且根本不必是高效的。任何合适的单元测试库都允许继承,因此您可以轻松地将一些公共代码放入超类中。