其他回答已经涉及了如何编写检查异常是否被抛出的单元测试的一般问题。但我认为你的问题实际上是关于如何在代码中首先引发异常。
以你的代码为例。在简单的单元测试上下文中,很难导致你的getServerName()
内部引发异常。问题在于为了发生异常,代码(通常)需要在网络有问题的计算机上运行。在单元测试中安排这种情况可能是不可能的...你需要在运行测试之前故意配置错误的机器。
那么答案是什么呢?
在某些情况下,简单的解决方案就是做出实用的决定,不要追求完全的测试覆盖率。你的方法就是一个很好的例子。从代码检查中应该清楚方法实际上是做什么的。测试它并不能证明任何东西(除了参见下面的**
)。你所做的只是提高你的测试计数和测试覆盖率数字,这两者都不应该是项目的目标。
在其他情况下,将生成异常的低级代码分离出来并将其变成独立的类可能是明智的。然后,为了测试高级代码对异常的处理,您可以使用模拟类替换该类,以引发所需的异常。
这是你的例子经过这种“处理”的结果。(这有点牵强...)
public interface ILocalDetails {
InetAddress getLocalHost() throws UnknownHostException;
...
}
public class LocalDetails implements ILocalDetails {
public InetAddress getLocalHost() throws UnknownHostException {
return InetAddress.getLocalHost();
}
}
public class SomeClass {
private ILocalDetails local = new LocalDetails();
...
public String getServerName() {
try {
InetAddress addr = local.getLocalHost();
return addr.getHostName();
}
catch (Exception e) {
e.printStackTrace();
return "";
}
}
}
现在要进行单元测试,需要创建一个“模拟”实现ILocalDetails接口的对象,使其getLocalHost()方法在适当的条件下抛出所需的异常。然后,为SomeClass.getServerName()创建一个单元测试,让SomeClass的实例使用您的“模拟”类的实例而不是正常的类实例。(最后一步可以使用模拟框架通过暴露local属性的setter或使用反射API来完成)。
显然,您需要修改代码以使其可测试。还有一些限制...例如,现在您无法创建一个单元测试来使真正的LocalDetails.getLocalHost()方法抛出异常。您需要逐个判断是否值得这样做;即单元测试的好处是否超过了以这种方式使类可测试所需的工作量(和额外的代码复杂性)。(这种情况下静态方法的存在是问题的重要部分。)
**
这种测试确实有一个假设的点。在您的示例中,原始代码捕获异常并返回空字符串可能是一个错误,具体取决于方法的API如何规定,而假设单元测试将发现它。但是,在这种情况下,这个错误非常明显,您在编写单元测试时就会发现它!并且假设您在发现错误时修复它,那么该单元测试将变得有些多余。(您不会期望有人恢复这个特定的错误...)