如您所知,在异常情况下会抛出异常。那么如何模拟这些异常呢?我觉得这是挑战。对于此类代码段:
public String getServerName() {
try {
InetAddress addr = InetAddress.getLocalHost();
String hostname = addr.getHostName();
return hostname;
}
catch (Exception e) {
e.printStackTrace();
return "";
}
}
有人有好主意吗?
答案 0 :(得分:65)
你可以告诉junit正确的行为是获得异常。
在JUnit 4中,它类似于:
@Test(expected = MyExceptionClass.class)
public void functionUnderTest() {
…
}
答案 1 :(得分:31)
其他答案解决了如何编写单元测试以检查是否抛出异常的一般问题。但我认为你的问题实际上是询问如何让代码首先抛出异常。
以您的代码为例。在单个单元测试的上下文中使getServerName()
内部抛出异常非常困难。问题是,为了使异常发生,代码(通常)需要在网络被破坏的机器上运行。安排在单元测试中发生这种情况可能是不可能的......在运行测试之前,您需要故意错误配置机器。
那么答案是什么?
在某些情况下,简单的答案只是采取务实的决定,而不是全面的测试覆盖。你的方法就是一个很好的例子。从代码检查中应该清楚该方法实际上做了什么。测试它不会证明什么(除了**
下面)。您所做的只是改善测试计数和测试覆盖率,这两者都不应该是项目目标。
在其他情况下,分离出生成异常的低级代码并将其作为单独的类可能是明智的。然后,要测试异常的更高级代码的处理,可以使用将抛出所需异常的mock类替换该类。
这是“治疗”的例子。 (这有点人为......)
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(); // or something ...
...
public String getServerName() {
try {
InetAddress addr = local.getLocalHost();
return addr.getHostName();
}
catch (Exception e) {
e.printStackTrace();
return "";
}
}
}
现在要对其进行单元测试,您可以创建ILocalDetails
接口的“模拟”实现,其getLocalHost()
方法会在适当的条件下抛出您想要的异常。然后为SomeClass.getServerName()
创建单元文本,安排SomeClass
的实例使用“mock”类的实例而不是正常类。 (最后一点可以使用模拟框架,通过为setter
属性公开local
或使用反射API来完成。)
显然,您需要修改代码以使其像这样可测试。你可以做什么是有限的...例如,你现在无法创建一个单元测试来使真正的LocalDetails.getLocalHost()
方法抛出异常。你需要逐案判断是否值得这样做的努力;即,单元测试的好处是否超过了以这种方式使类可测试的工作(以及额外的代码复杂性)。 (在这个问题的底部有一个static
方法的事实是问题的很大一部分。)
**
是这种测试的假设点。在您的示例中,原始代码捕获异常并返回空字符串的事实可能是一个错误...取决于方法的API如何指定...并且假设单元测试将选择它了。但是,在这种情况下,bug是如此明显,以至于你在编写单元测试时会发现它!假设您在找到错误时修复了错误,则单元测试变得有些多余。 (你不会指望有人重新设置这个特定的错误...)
答案 2 :(得分:13)
好的,这里有一些可能的答案。
检测异常本身很容易
import static org.hamcrest.core.Is.is;
import static org.junit.Assert.assertThat;
@Test
public void TestForException() {
try {
doSomething();
fail();
} catch (Exception e) {
assertThat(e.getMessage(), is("Something bad happened"));
}
}
或者,您可以使用异常注释来指出您希望出现异常。
现在,至于你的具体例子,当你无法与对象交互时,测试你在方法中创建的东西,无论是通过新的还是静态的,都是棘手的。您通常需要封装该特定生成器,然后使用一些模拟来覆盖行为以生成您期望的异常。
答案 3 :(得分:8)
由于这个问题在社区维基中,我会添加一个新的完整性:
您可以在JUnit 4中使用ExpectedException
@Rule
public ExpectedException thrown= ExpectedException.none();
@Test
public void TestForException(){
thrown.expect(SomeException.class);
DoSomething();
}
ExpectedException
使抛出的异常可用于所有测试方法。
也可以测试特定的错误消息:
thrown.expectMessage("Error string");
或使用匹配器
thrown.expectMessage(startsWith("Specific start"));
这比
更短更方便public void TestForException(){
try{
DoSomething();
Fail();
}catch(Exception e) {
Assert.That(e.msg, Is("Bad thing happened"))
}
}
因为如果您忘记了失败,那么测试可能会导致误报。
答案 4 :(得分:6)
许多单元测试框架允许您的测试将异常作为测试的一部分。例如,JUnit,allows for this。
@Test (expected=IndexOutOfBoundsException.class) public void elementAt() {
int[] intArray = new int[10];
int i = intArray[20]; // Should throw IndexOutOfBoundsException
}