异常和超时测试:确保代码按预期失败
好的测试不仅要验证正常路径,也要验证异常路径。
一个不抛异常的 NPE、一个应该超时的方法没超时——这些往往是生产环境的定时炸弹。
异常测试:确保该抛的时候抛了
assertThrows:断言抛出指定异常
java
@Test
void testThrowsException() {
// 断言抛出 ArithmeticException
ArithmeticException ex = assertThrows(ArithmeticException.class, () -> {
int result = 10 / 0;
});
// 可以进一步验证异常信息
assertEquals("/ by zero", ex.getMessage());
}验证异常消息
java
@Test
void testExceptionMessage() {
Exception ex = assertThrows(RuntimeException.class, () -> {
throw new RuntimeException("用户不存在: 12345");
});
assertThat(ex.getMessage())
.contains("12345")
.startsWith("用户不存在");
}断言不抛异常
java
@Test
void testDoesNotThrow() {
assertDoesNotThrow(() -> {
int result = 10 / 2; // 不会抛异常
});
}超时测试:防止死循环和性能退化
@Timeout 注解
java
import org.junit.jupiter.api.Test;
import org.junit.jupiter.api.Timeout;
import java.util.concurrent.TimeUnit;
class TimeoutDemo {
@Test
@Timeout(value = 1, unit = TimeUnit.SECONDS) // 1 秒内必须完成
void testWithTimeout() {
// 如果这个方法执行超过 1 秒,测试自动失败
doSomething();
}
}assertTimeoutPreemptively:抢占式超时
java
@Test
void testTimeoutAssertion() {
assertTimeoutPreemptively(Duration.ofMillis(100), () -> {
// 抢占式超时:超过 100ms 强制中断线程
Thread.sleep(50); // 不会中断,50ms < 100ms
});
}assertTimeoutPreemptively 和 @Timeout 都是抢占式的——超时后强制中断线程。相比之下 assertTimeout 是计时型的,只记录是否超时,不会中断。
超时陷阱
java
// ❌ 如果测试代码包含对外部服务的真实调用,100ms 肯定不够
@Test
@Timeout(1)
void testExternalCall() {
httpClient.call("http://slow-api.com"); // 假设这个 API 响应需要 3 秒
}
// ✅ Mock 外部依赖,让测试纯粹测自己的逻辑
@Test
@Timeout(1)
void testExternalCall() {
when(httpClient.call(anyString())).thenReturn("mocked");
// 测试代码逻辑
}组合场景
异常 + 超时
java
@Test
@Timeout(value = 2, unit = TimeUnit.SECONDS)
void testWithExceptionAndTimeout() {
assertThrows(IllegalArgumentException.class, () -> {
throw new IllegalArgumentException("Invalid input");
});
}常见误区
1. 只测正常路径
java
// ❌ 只测成功的情况
@Test
void testDivide() {
assertEquals(5, calculator.divide(10, 2));
}
// ✅ 还要测异常路径
@Test
void testDivideByZero() {
assertThrows(ArithmeticException.class, () -> {
calculator.divide(10, 0);
});
}2. 超时设置过长
超时太宽松就失去了意义。应该根据实际业务逻辑的最长时间来设置。
3. 假设超时是精确的
超时断言的精度受 JVM 调度影响,不建议用超时代替性能测试。
总结
异常测试和超时测试是测试体系里常被忽视的两个角落:
- 异常测试:确保代码在遇到错误时正确处理(该抛的抛,该捕获的捕获)
- 超时测试:防止代码陷入死循环或性能严重退化
好的测试套件,既要确保正常路径走通,也要确保异常路径走对。
