通过把这一小段条件逻辑提取到简洁的新方法中(如清单 2 所示),做到了两件事:一,把 updateContent() 方法的整体复杂性降低了 5;二,逻辑的隔离很完整,可以容易地对它进行测试。

清单 2. 提取方法产生 getStatus


private String getStatus(IStatus status, List lastChangedStatus) {
  String retstatus = null;
  if ( lastChangedStatus != null && lastChangedStatus.size() > 0 ){
    if ( status.getId() == ((IStatus)lastChangedStatus.get(0)).getId() ){
      retstatus = "Change in Current status";
    }else{
      retstatus = "Account Previously Changed in: " +
     ((IStatus)lastChangedStatus.get(0)).getStatusIdentification();
    }
  }else{
    retstatus = "No Changes Since Creation";
  }
  return retstatus;
}


现在可以把 updateContent() 方法体中的一部分替换成对新创建的 getStatus() 方法的调用,如清单 3 所示:

清单 3. 调用 getStatus


//...other code above

String iStatus = getStatus(status, lastChangedStatus);

//...more code below


请记住运行现有的测试,以验证什么都没被破坏!

测试私有方法

您将注意到在 清单 2 中定义的新 getStatus() 方法被声明为 private。这在想验证隔离的 方法的行为的时候形成了一个有趣的挑战。有许多方法可以解决这个问题:

    把方法声明成 public。
    把方法声明成 protected,并把测试用例放在同一个包中。
    在父类中建立一个内部类,这个内部类是个测试用例。

还有另一个选择:保留方法现有的声明不变(即 private),并采用的 JUnit 插件项目来测试它。

PrivateAccessor 类

JUnit 插件项目有一些方便的工具,可以帮助 JUnit 进行测试。其中有用的一个是 PrivateAccessor 类,它把对 private 方法的测试变成小菜一碟,无论选择的测试框架是什么。PrivateAccessor 类对 JUnit 没有显式的依赖,所以可以把它用于任何测试框架,例如 TestNG。

PrivateAccessor 的 API 很简单 —— 向 invoke() 方法提供方法的名称(作为 String)和方法对应的参数类型和相关的值(分别在 Class 和 Object 数组中),会返回被调用方法的值。在幕后,PrivateAccessor 类实际上利用 Java 的反射 API 关闭了对象的可访问性。但是请记住,如果虚拟机有定制的安全性设置,那么这个工具可能无法正确工作。

在清单 4 中,调用 getStatus() 方法时两个参数值都设置为 null。invoke() 方法返回一个 Object,所以要转换成 String。还请注意 invoke() 方法声明它要 throws Throwable,必须捕获异常或者让测试框架处理它,像我做的那样。

清单 4. 测试私有方法


public void testGetStatus() throws Throwable{
  AccountAction action = new AccountAction();
 
  String value = (String)PrivateAccessor.invoke(action,
      "getStatus", new Class[]{IStatus.class, List.class},
       new Object[]{null, null});
 
  assertEquals("should be No Changes Since Creation",
    "No Changes Since Creation", value);
}


请注意 invoke() 方法被覆盖成可以接受一个 Object 实例(如清单 4 所示)或一个 Class(这时期望的 private 方法也是 static 的)。

还请记住,使用反射调用 private 方法会对生成的结果带来一定程度的脆弱性。如果有人改变了 getStatus() 方法的名字,以上测试会失败;但是,如果经常测试,可以迅速地进行适当的修正。

结束语

在抗击圈复杂度时,请记住大部分编写到应用程序中的路径是应用程序的整体行为所固有的。也是说,很难显著地减少路径的整体数量。重构只是把这些路径放在更小的代码段中,从而更容易测试。这些小的代码段也更容易维护。