
在软件开发中,单元测试是确保代码质量的关键环节。当我们的方法在特定条件下预期会抛出异常时,如何有效地测试这些异常行为成为了一个重要课题。assertj作为一个功能强大的断言库,提供了多种机制来处理这种情况。
许多开发者在初次尝试测试异常时,可能会直观地尝试对方法调用的结果进行断言,以检查是否抛出了特定类型的异常。以下是一个常见的错误示例:
假设我们有一个方法enterTheAmount,它接受用户输入的金额,并检查该金额是否为指定价格的倍数。如果不是,则抛出IllegalArgumentException。
public class Application {
public static int enterTheAmount(){
final int LOTTO_PRICE = 1000;
// 模拟从控制台读取输入,这里简化为直接解析字符串
// 在实际测试中,通常会通过System.setIn()重定向输入流
int amount = Integer.parseInt(Console.readLine()); // 假设Console.readLine()已实现
if(amount % LOTTO_PRICE != 0) {
throw new IllegalArgumentException("金额必须是" + LOTTO_PRICE + "的倍数。");
}
return amount / LOTTO_PRICE;
}
}为了测试当输入无效金额时是否抛出IllegalArgumentException,可能会有如下错误的测试尝试:
import org.junit.jupiter.api.Test;
import static org.assertj.core.api.Assertions.assertThat;
import java.io.ByteArrayInputStream;
import java.io.InputStream;
class ApplicationTest {
// 模拟Console.readLine()所需的输入流重定向
private void setSystemIn(String input) {
InputStream in = new ByteArrayInputStream(input.getBytes());
System.setIn(in);
}
@Test
void validateTheEnteredAmount_incorrectly() {
final String INVALID_NUMBER = "1234";
setSystemIn(INVALID_NUMBER); // 设置模拟输入
// 错误的断言方式:试图对方法的返回值进行isInstanceOf判断
// 这段代码会失败,因为异常会在assertThat()被调用之前抛出
// 导致测试框架捕获到一个未处理的异常,而不是AssertJ的断言失败
assertThat(Application.enterTheAmount())
.isInstanceOf(IllegalArgumentException.class);
}
}问题分析:
上述测试代码的根本问题在于,Application.enterTheAmount()方法在遇到无效输入(如"1234")时会立即抛出IllegalArgumentException。这意味着assertThat()方法根本无法接收到任何返回值来执行isInstanceOf()断言。异常会在方法调用时直接中断程序的正常流程,并被JUnit测试框架捕获为未处理的异常,从而导致测试失败,而不是我们期望的AssertJ断言失败。
AssertJ以及JUnit 5(通过assertThrows)提供了专门用于测试异常的方法,能够优雅且准确地验证预期异常。AssertJ的assertThrows方法(实际上是Assertions.assertThatThrownBy或JUnit 5的assertThrows)是解决此类问题的正确途径。这里我们主要介绍JUnit 5的assertThrows,因为它在现代Java测试中更为常用且语义清晰。
assertThrows方法接受两个参数:预期的异常类型和一段可执行的代码(通常是一个lambda表达式),这段代码在执行时应该抛出预期的异常。
以下是使用assertThrows改进后的正确测试代码:
import org.junit.jupiter.api.Test;
import static org.junit.jupiter.api.Assertions.assertThrows; // 导入JUnit 5的assertThrows
import java.io.ByteArrayInputStream;
import java.io.InputStream;
class ApplicationTest {
// 模拟Console.readLine()所需的输入流重定向
private void setSystemIn(String input) {
InputStream in = new ByteArrayInputStream(input.getBytes());
System.setIn(in);
}
@Test
void validateTheEnteredAmount_correctly() {
final String INVALID_NUMBER = "1234";
setSystemIn(INVALID_NUMBER); // 设置模拟输入
// 正确的断言方式:使用assertThrows验证异常
assertThrows(IllegalArgumentException.class, () -> Application.enterTheAmount());
}
}在这个修正后的测试中:
assertThrows是JUnit 5提供的一个强大工具,用于验证方法是否抛出了特定的异常。
示例:进一步断言异常信息
import org.junit.jupiter.api.Test;
import static org.junit.jupiter.api.Assertions.assertThrows;
import static org.assertj.core.api.Assertions.assertThat; // 结合AssertJ进行更丰富的断言
import java.io.ByteArrayInputStream;
import java.io.InputStream;
class ApplicationTest {
private void setSystemIn(String input) {
InputStream in = new ByteArrayInputStream(input.getBytes());
System.setIn(in);
}
@Test
void validateTheEnteredAmount_withExceptionMessageCheck() {
final String INVALID_NUMBER = "1234";
setSystemIn(INVALID_NUMBER);
// 捕获异常对象并进行进一步断言
IllegalArgumentException thrown = assertThrows(
IllegalArgumentException.class,
() -> Application.enterTheAmount(),
"当输入无效金额时,应该抛出IllegalArgumentException" // 失败时的可选消息
);
// 使用AssertJ断言异常消息
assertThat(thrown.getMessage())
.isNotNull()
.contains("金额必须是")
.contains("1000的倍数");
}
}在这个例子中,我们不仅验证了异常类型,还通过返回的thrown对象,使用AssertJ的assertThat对异常消息进行了更详细的断言,确保异常信息符合预期,这大大增加了测试的健壮性。
在单元测试中验证预期异常是确保代码行为正确性的重要一环。避免直接对可能抛出异常的方法返回值进行assertThat().isInstanceOf()断言的常见误区。相反,应采用JUnit 5提供的assertThrows方法,它能够清晰、准确地捕获并验证特定类型的异常。通过将可能抛出异常的代码封装在lambda表达式中,并利用assertThrows返回的异常对象进行进一步的AssertJ断言,我们可以编写出既健壮又富有表达力的异常测试用例。
以上就是AssertJ异常处理:正确测试预期异常的方法的详细内容,更多请关注php中文网其它相关文章!
每个人都需要一台速度更快、更稳定的 PC。随着时间的推移,垃圾文件、旧注册表数据和不必要的后台进程会占用资源并降低性能。幸运的是,许多工具可以让 Windows 保持平稳运行。
Copyright 2014-2025 https://www.php.cn/ All Rights Reserved | php.cn | 湘ICP备2023035733号