在编写测试用例时,验证测试结果是否符合预期是至关重要的一步。JUnit 提供了强大的 Assertions 类,帮助我们轻松实现这一目标。通过断言,我们可以确保代码在各种情况下都能正确运行,从而提升代码的可靠性和稳定性。
验证布尔值:assertTrue 和 assertFalse
在测试中,我们经常需要验证某个条件是否为 true 或 false。使用 Assertions.assertTrue 和 Assertions.assertFalse,可以轻松判断返回结果是否符合预期。如果结果不符合预期,断言会抛出异常,并附带自定义的错误信息,帮助你快速定位问题。
@ParameterizedTest
@ValueSource(booleans = {true, false})
public void testAssertTrue(boolean isTrue) {
// Assertions.assertTrue(isTrue);
Assertions.assertTrue(isTrue, "返回结果必须是True");
}
@ParameterizedTest
@ValueSource(booleans = {true, false})
public void testAssertFalse(boolean isFalse) {
// Assertions.assertFalse(isFalse);
Assertions.assertFalse(isFalse, "返回结果必须是false");
}
验证空值:assertNotNull 和 assertNull
在处理对象时,我们经常需要判断某个对象是否为 null。Assertions.assertNotNull 和 Assertions.assertNull 可以帮助我们验证对象是否为空。这在处理数据库查询、API 调用等场景时尤为有用。
@ParameterizedTest
@NullSource
@ValueSource(strings = {"A", "B", "C"})
public void testAssertNotNull(String name) {
// Assertions.assertNotNull(name);
Assertions.assertNotNull(name, "name字段必须不为null");
}
@ParameterizedTest
@NullSource
@ValueSource(strings = {"A", "B", "C"})
public void testAssertNull(String name) {
// Assertions.assertNull(name);
Assertions.assertNull(name, "name字段必须为null");
}
证相等性:assertEquals
无论是基本类型还是对象,Assertions.assertEquals 都可以帮助我们验证两个值是否相等。这个方法支持多种数据类型,包括 int、long、float、double、String 等。
@ParameterizedTest
@ValueSource(ints = {1, 127, 256, 1024})
public void testAssertEqInt(int num) {
Assertions.assertEquals(num, Integer.valueOf("1024"));
}
@ParameterizedTest
@ValueSource(longs = {1L, 127L, 256L, 1024L})
public void testAssertEqLong(long num) {
Assertions.assertEquals(num, 256);
}
@ParameterizedTest
@ValueSource(floats = {1f, 127f, 256f, 1024f})
public void testAssertEqLong(float num) {
Assertions.assertEquals(num, 127f);
}
@ParameterizedTest
@ValueSource(doubles = {1d, 127d, 256d, 1024.13d})
public void testAssertEqLong(double num) {
Assertions.assertEquals(num, 1024.13d);
}
@ParameterizedTest
@ValueSource(strings = {"a", "b", "c"})
public void testAssertEqString(String str) {
Assertions.assertEquals(str, "b");
}
@ParameterizedTest
@ValueSource(chars = {'a', 'b', 'c'})
public void testAssertEqChar(char chars) {
Assertions.assertEquals(chars, 'c');
}
@ParameterizedTest
@ValueSource(bytes = {-128, 0, 127})
public void testAssertEqBytes(Byte bytes) {
Assertions.assertEquals(bytes, (byte)127, () -> "bytes: " + bytes + " != " + (byte)127);
}
验证数组相等性:assertArrayEquals
在处理数组时,Assertions.assertArrayEquals 可以帮助我们验证两个数组是否相等。它会逐个比较数组中的元素,确保它们完全一致。
static Stream<Arguments> createIntArr() {
int[] arr1 = {1, 2, 3};
int[] arr2 = {3, 4, 5};
int[] arr3 = {5, 6, 7};
return Stream.of(Arguments.of(arr1), Arguments.of(arr2), Arguments.of(arr3));
}
@ParameterizedTest
@MethodSource("createIntArr")
public void testAssertIntArr(int[] arr) {
int[] tmp = {3, 4, 5};
Assertions.assertArrayEquals(arr, tmp);
}
验证异常:assertThrows 和 assertDoesNotThrow
在测试中,我们不仅需要验证代码的正确执行,还需要确保代码在特定情况下抛出预期的异常。Assertions.assertThrows 可以帮助我们验证代码是否抛出了指定的异常,而 Assertions.assertDoesNotThrow 则可以确保代码在预期情况下不会抛出任何异常。
@ParameterizedTest
@ValueSource(ints = {-1, 0, 1})
public void testAssertThrow(int num) {
ArithmeticException arithmeticException = Assertions.assertThrows(ArithmeticException.class, () -> {
int res = 1 / num;
});
System.out.println(arithmeticException.getMessage());
}
检测当前代码运行预期不会出现异常AssertDoesNotThrow.assertDoesNotThrow,通过测试用例,否则不通过
@ParameterizedTest
@ValueSource(strings = {"/Users/jay/Desktop/users/spring-boot-test-junit/testdb.mv.db", "/Users/jay/Desktop/users" +
"/spring-boot-test-junit/test.mv.db0", "/Users/jay/Desktop/users/spring-boot-test-junit/test.mv.db1"})
public void testAssertNotThrow(String path) {
Assertions.assertDoesNotThrow(() -> {
Path pathObj = Paths.get(path);
byte[] bytes = Files.readAllBytes(pathObj);
System.out.println(bytes.length);
});
}
验证对象引用:assertSame
在某些情况下,我们需要验证两个对象是否是同一个实例。Assertions.assertSame 可以帮助我们验证两个对象是否指向同一个内存地址,这在单例模式的测试中尤为有用。
@ParameterizedTest
@ValueSource(strings = {"abc", "dcf", "s", "b", "e", "d", "fr", "gt", "lt", "qwe"})
public void testAssertSame(String str) {
Random random = new Random();
int i = random.nextInt();
String tmp;
if (i % 2 == 0) {
tmp = str;
} else {
tmp = new String(str);
}
Assertions.assertSame(str, tmp);
}
检测是否是同一个对象实例,比如单例模式
static Stream<Arguments> createObj() {
return Stream.of(Arguments.of(Color.RED.getRed()), Arguments.of(new Red()));
}
@ParameterizedTest
@MethodSource("createObj")
public void testAssertSameObj(Red red) {
Assertions.assertSame(red, Color.RED.getRed());
}
static class Red {
}
enum Color {
RED;
private Red red;
Color() {
red = new Red();
}
public Red getRed() {
return red;
}
}
批量验证:assertAll
在复杂的测试场景中,我们可能需要对多个断言进行批量验证。Assertions.assertAll 允许我们一次性验证多个断言,即使其中某些断言失败,其他断言仍然会被执行。最终,所有失败的断言信息会一次性报告,帮助我们全面了解测试结果。
@Test
public void testAssertAll() {
HttpClient client = HttpClient.newBuilder().connectTimeout(Duration.ofSeconds(10)).build();
Assertions.assertAll("批量测试接口是否正常可访问", () -> {
HttpRequest requestV2ex =
HttpRequest.newBuilder().uri(URI.create("https://v2ex.com/")).timeout(Duration.ofSeconds(120)).GET().build();
Assertions.assertDoesNotThrow(() -> {
HttpResponse<String> v2ex = client.send(requestV2ex, HttpResponse.BodyHandlers.ofString());
System.out.println(v2ex);
Assertions.assertEquals(v2ex.statusCode(), 200);
});
}, () -> {
HttpRequest requestBaiDu = HttpRequest.newBuilder().uri(URI.create("https://www.baidu.com/")).GET().build();
HttpResponse<String> baidu = client.send(requestBaiDu, HttpResponse.BodyHandlers.ofString());
System.out.println(baidu);
Assertions.assertEquals(baidu.statusCode(), 200);
}, () -> {
HttpRequest requestAlibaba =
HttpRequest.newBuilder().uri(URI.create("https://www.alibaba.com/")).GET().build();
HttpResponse<String> alibaba = client.send(requestAlibaba, HttpResponse.BodyHandlers.ofString());
System.out.println(alibaba);
Assertions.assertEquals(alibaba.statusCode(), 200);
}, () -> {
HttpRequest requestGoogle =
HttpRequest.newBuilder().uri(URI.create("https://www.google.com/")).GET().build();
HttpResponse<String> google = client.send(requestGoogle, HttpResponse.BodyHandlers.ofString());
System.out.println(google);
Assertions.assertEquals(google.statusCode(), 200);
}, () -> {
HttpRequest requestOther = HttpRequest.newBuilder().uri(URI.create("https://www.other.com/")).GET().build();
SSLHandshakeException sslHandshakeException = Assertions.assertThrows(SSLHandshakeException.class, () -> {
HttpResponse<String> other = client.send(requestOther, HttpResponse.BodyHandlers.ofString());
System.out.println(other);
Assertions.assertEquals(other.statusCode(), 200);
});
System.out.println(sslHandshakeException.getMessage());
});
}
验证执行时间:assertTimeout 和 assertTimeoutPreemptively
在某些性能测试中,我们需要确保代码在规定的时间内完成执行。Assertions.assertTimeout 和 Assertions.assertTimeoutPreemptively 可以帮助我们验证代码的执行时间是否在预期范围内。后者在超时后会立即中断测试,避免测试无限卡住。
@ParameterizedTest
@ValueSource(strings = {"/Users/jay/Desktop/users/spring-boot-test-junit/testdb.mv.db", "/Users/jay/Downloads/hello-algo-1.0.0b6-zh-java.pdf", "/Users/jay/Downloads/Netty权威指南 第2版.pdf"})
public void testAssertTimeout(String filePath) {
Assertions.assertTimeout(Duration.of(5, ChronoUnit.MILLIS), () -> {
Path path = Paths.get(filePath);
try (AsynchronousFileChannel asyncChannel = AsynchronousFileChannel.open(path, StandardOpenOption.READ)) {
ByteBuffer buffer = ByteBuffer.allocate(1024);
// 提交读取任务
Future<Integer> result = asyncChannel.read(buffer, 0);
// 等待读取完成
while (!result.isDone()) {
System.out.println("Reading...");
}
// 读取完成
buffer.flip();
System.out.println("File content:");
while (buffer.hasRemaining()) {
System.out.print((char) buffer.get());
}
} catch (IOException e) {
e.printStackTrace();
}
});
}
AssertTimeoutPreemptively 用于在测试中限制代码执行的时间。当代码的执行时间超过指定的时间限制时,测试会立即中断并失败。这在测试需要验证性能或时间约束的场景中非常有用。
• 强制中断执行:如果执行时间超过了 timeout 参数指定的限制,测试会立即失败,并抛出 TimeoutException。
• 预防死锁:在多线程或阻塞任务的测试中非常有用,可以避免代码死锁导致测试无限卡住。
• 线程级别的中断:使用独立线程运行可执行代码,超时后终止线程。
@ParameterizedTest
@ValueSource(strings = {"/Users/jay/Desktop/users/spring-boot-test-junit/testdb.mv.db", "/Users/jay/Downloads/hello-algo-1.0.0b6-zh-java.pdf", "/Users/jay/Downloads/Netty权威指南 第2版.pdf"})
public void testAssertTimeoutPreemptively(String filePath) {
Assertions.assertTimeoutPreemptively(Duration.of(5, ChronoUnit.MILLIS), () -> {
try (FileInputStream fis = new FileInputStream(filePath);
FileChannel fileChannel = fis.getChannel()) {
// 创建缓冲区
ByteBuffer buffer = ByteBuffer.allocate(1024);
// 读取文件内容
while (fileChannel.read(buffer) > 0) {
buffer.flip(); // 切换为读取模式
// while (buffer.hasRemaining()) {
// System.out.print((char) buffer.get()); // 输出缓冲区内容
// }
buffer.clear(); // 清空缓冲区
}
} catch (IOException e) {
e.printStackTrace();
}
});
}
条件跳过测试:assumingThat
在某些情况下,我们可能希望根据特定条件跳过某些测试用例。Assumptions.assumingThat 可以帮助我们实现这一需求,只有在满足特定条件时,测试用例才会被执行。
@ParameterizedTest
@ValueSource(strings = {"dev", "pre", "sit", "prod"})
public void testSkip(String active) {
Assumptions.assumingThat(!active.equals("prod"), () -> {
System.out.println( "在非prod环境运行这个测试用例");
});
System.out.println("无论是否存在 ENV 环境变量,这段代码都会执行");
}