JUnit测试中如何断言抛出特定异常
技术背景
在Java开发中,单元测试是确保代码质量和稳定性的重要手段。JUnit是Java开发中广泛使用的单元测试框架,在编写单元测试时,经常需要验证代码是否会抛出预期的异常。不同版本的JUnit以及搭配不同的断言库,有多种方式可以实现异常断言。
实现步骤
JUnit 5 和 JUnit 4.13+
可以使用 Assertions.assertThrows()
(JUnit 5)和 Assert.assertThrows()
(JUnit 4.13+):
1 2 3 4 5 6 7 8 9 10 11 12
| import static org.junit.jupiter.api.Assertions.assertThrows; import org.junit.jupiter.api.Test;
public class ExceptionTestingExample { @Test void exceptionTesting() { ArithmeticException exception = assertThrows(ArithmeticException.class, () -> calculator.divide(1, 0));
assertEquals("/ by zero", exception.getMessage()); } }
|
JUnit 4.7+
可以使用 ExpectedException
规则:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16
| import org.junit.Rule; import org.junit.Test; import org.junit.rules.ExpectedException;
public class FooTest { @Rule public final ExpectedException exception = ExpectedException.none();
@Test public void doStuffThrowsIndexOutOfBoundsException() { Foo foo = new Foo();
exception.expect(IndexOutOfBoundsException.class); foo.doStuff(); } }
|
经典的 try/catch 方式
1 2 3 4 5 6 7 8 9 10 11 12 13
| import org.junit.Test; import static org.junit.Assert.fail;
public class ExceptionTestingExample { @Test public void testFooThrowsIndexOutOfBoundsException() { try { foo.doStuff(); fail("My method didn't throw when I expected it to"); } catch (IndexOutOfBoundsException expectedException) { } } }
|
使用 AssertJ 断言库
1 2 3 4 5 6 7 8 9 10 11 12
| import static org.assertj.core.api.Assertions.*; import org.junit.Test;
public class ExceptionTestingExample { @Test public void testFooThrowsIndexOutOfBoundsException() { Foo foo = new Foo();
assertThatThrownBy(() -> foo.doStuff()) .isInstanceOf(IndexOutOfBoundsException.class); } }
|
核心代码
以下是不同方式的核心代码片段:
JUnit 5 异常断言
1 2 3 4 5 6 7 8 9 10 11 12
| import static org.junit.jupiter.api.Assertions.assertThrows; import org.junit.jupiter.api.Test;
public class JUnit5ExceptionTest { @Test void testException() { IllegalArgumentException exception = assertThrows(IllegalArgumentException.class, () -> { throw new IllegalArgumentException("a message"); }); assertEquals("a message", exception.getMessage()); } }
|
JUnit 4 ExpectedException
规则
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15
| import org.junit.Rule; import org.junit.Test; import org.junit.rules.ExpectedException;
public class JUnit4ExpectedExceptionTest { @Rule public ExpectedException thrown = ExpectedException.none();
@Test public void testReadFile() throws FileNotFoundException { thrown.expect(FileNotFoundException.class); thrown.expectMessage(startsWith("The file test.txt")); myClass.readFile("test.txt"); } }
|
AssertJ 异常断言
1 2 3 4 5 6 7 8 9 10 11 12 13
| import static org.assertj.core.api.Assertions.*; import org.junit.Test;
public class AssertJExceptionTest { @Test public void testException() { assertThatThrownBy(() -> { throw new Exception("boom!"); }) .isInstanceOf(Exception.class) .hasMessageContaining("boom"); } }
|
最佳实践
- JUnit 版本选择:如果使用 JUnit 5 或 JUnit 4.13+,优先使用
assertThrows
方法,它简洁且功能强大。 - 异常信息验证:除了验证异常类型,还可以验证异常的消息、原因等信息,确保异常的准确性。
- 代码可读性:选择合适的断言方式,使测试代码具有良好的可读性和可维护性。
常见问题
1. 使用 @Test(expected = ...)
的问题
这种方式只能断言方法抛出了指定异常,但不能精确到特定代码行。如果测试代码中多个地方可能抛出相同异常,可能会导致误判。
2. ExpectedException
规则的使用问题
ExpectedException
实例必须是 public
的。- 不能在
@Before
方法中实例化 ExpectedException
。
3. 名称冲突问题
使用 AssertJ 时,assertThat
可能会与 JUnit 的 assertThat
产生名称冲突,需要注意导入正确的包。