要测试 Public APIs,而不是实现细节

乔梁 | 2021-02-12

公有的 API 可能被任何调用者所使用,而调用者可能会将从任意组合的参数引入到该方法中。

我们当然希望,所有的可能性都能被测试用例覆盖。 这样的话,我们就会确信:调用者使用这个 API 时,不会发生任何错误。

公共 API 包括两类:

  1. 在代码仓库中其它地方的代码调用的类 (例如,一个服务类,会被客户端代码所调用)
  2. 公共的工具类,可能被整个代码仓库所用。

一个被称为“实现细节”的类,其存在的意义就是为了支持某个公共 API,且它只有有限个数的调用者(通常只有一个)。 有时候,这些类可能通过测试那些调用它们的 API 而被间接测试到。

下面的这个类需要测试么?

class UserInfoValidator {
  public void validate(UserInfo info) {
    if (info.getDateOfBirth().isInFuture()) { throw new ValidationException(); }
  }
}

它的Validate()方法是包含一些业务逻辑的,所以,也许应该对它进行测试。

但是, 如果它的调用者是以下面的方式来使用它的呢?

public class UserInfoService {
  private UserInfoValidator validator;
  public void save(UserInfo info) {
    validator.validate(info); // Throw an exception if the value is invalid.
    writeToDatabase(info);
  }
}

答案可能是:它也许不需要测试 ,因为所有的路径都可以通过 UserInfoService被测试到。关键点在于:这个类是一种实现细节,而不是 Public API.

在某些情况下,测试这种细节实现类也是有用的。

如果这个细节实现类非常复杂,或者对公共 API 的一些测试用例非常难写。此时怎么办呢?

若想对它进行测试 ,**并不需要象测试公共 API 那样全面且深入,**因为有一些看上去可能出现的输入参数根本不可能被传到这个方法中来。

例如,在上面的代码样例中,如果UserInfoService已经确保 UserInfo 不可能为 null,那么就没有必要测试当把null作为参数传入 UserInfoValidator.validate时会发生什么,因为这根本不可能发生。

细节实现类有时可能被认为是将私有方法( private methods )被放到了另外的一个类中了,因为你想遵守"不应该测试私有方法"的原则。**当然,你同样应该严格限制这种细节实现类的可见性,**例如,在Java中可能应该是在包范围可见即可( package-private )。

测试细节实现类常常导致一些耦合问题:

  • 由于你经常需要更新测试,所以代码很难维护,例如,当修改一个细节实现类的方法签名时,或者在进行一个重构操作时。如果只通过公共API来进行测试,那么这类变更就根本不会影响测试代码。

  • 假如只能通过细节实现类,才能测试某个行为,很有可能会出现的状况是:你会对你的代码产生虚假的信心。因为当使用公共API时,同样的代码路径也许并不能按预期方式工作。所以,你在重构时必须更加小心

原文链接

谷歌ToTT:测试公有API,而不是实现细节