目录
1 Junit5
Junit5由下面几个子模块组成:
JUnit 5 = JUnit Platform + JUnit Jupiter + JUnit Vintage
Junit5需要在Java8或者更高版本运行。一般springboot项目已经包含了Junit的依赖。
2 各种测试使用
待测试的demo-1:
@Service
public class Calculator {
@Autowired
private BeanOne beanOne;
public int add(int a, int b) {
return a+b;
}
public void exception() throws Exception {
throw new Exception();
}
public int getOne() {
return beanOne.get();
}
}
2.1 注释
Juit5常用的注解如下表(详细内容参考官网:Junit5):
注解 | 描述 |
---|---|
@Test | 表示是测试方法。与 JUnit 4 的注释不同,此注释不声明任何属性。 |
@RepeatedTest | 表示方法是重复测试的测试模板,一般需要传入value值,表示重复次数。 |
@BeforeEach | 表示方法应该在每个@Test方法之前执行,常用于实例化对象属性。 |
@AfterEach | 表示方法应该在每个@Test方法之后执行,常用于实例化对象的一些清理工作。 |
@BeforeAll | 表示该方法在所有方法之前执行一次,一般用于初始化静态属性资源(如数据库连接),修饰static方法。 |
@AfterAll | 表示该方法在所有方法之后执行一次,用于静态资源的清理和释放。修饰static方法。 |
@Disabled | 表示该方法或者测试类将被禁用。 |
@Timeout | 表示该方法执行超时之后,将会失败。 |
上表的@BeforeAll@AfterAll@BeforeEach@AfterEach属于测试中的生命周期函数。
2.2 测试类和测试方法
测试类和测试方法不要使用private或public修饰符,最好省略。
一个标准的测试类如下demo-2:
public class StandardTests {
@BeforeAll
static void initAll() {
}
@BeforeEach
void init() {
}
@Test
void succeedingTest() {
}
@Test
void failingTest() {
Fail.fail("a failing test");
}
@Test
@Disabled("for demonstration purposes")
void skippedTest() {
// not executed
}
@Test
void abortedTest() {
Assertions.assertTrue("abc".contains("Z"));
Fail.fail("test should have been aborted");
}
@AfterEach
void tearDown() {
}
@AfterAll
static void tearDownAll() {
}
}
2.3 断言
Junit5继承了Junit4的许多assertion方法,同时增加了少量用于和Java8 lambda表达式配合的断言。
所有断言位于:org.junit.jupiter.api.Assertions。
如assertAll,断言一组lambda表达式:
//源码定义
public static void assertAll(String heading, Executable... executables) throws MultipleFailuresError {
AssertAll.assertAll(heading, executables);
}
@Test
void groupedAssertions() {
// In a grouped assertion all assertions are executed, and all
// failures will be reported together.
Assertions.assertAll("person",
() -> Assertions.assertEquals("Jane", person.getFirstName()),
() -> Assertions.assertEquals("Doe", person.getLastName())
);
}
assertAll可以进行依赖断言,也就是在assertAll可以嵌套其它断言。
异常断言assertThrows:
@Test
public void testCalculatorException() {
Assertions.assertThrows(Exception.class, () -> {
calculator.exception();
});
}
传入异常类类型和一个函数式方法,方法中放入你测试的抛出异常的待测试方法。
Assertions.assertThrows(Class<T> expectedType, Executable executable)
超时断言assertTimeout:
@Test
void timeoutNotExceeded() {
// The following assertion succeeds.
Assertions.assertTimeout(Duration.ofSeconds(5L), () -> {
Thread.sleep(10000L);
}) ;
}
源码是通过判断执行任务前后的时间,决定是否测试成功,会捕获异常。
该断言如果成功会返回任务的执行结果,可以根据该结果进行下一步的判断。
assertTimeoutPreemptively超时断言:
@Test
void timeoutExceededWithPreemptiveTermination() {
// The following assertion fails with an error message similar to:
// execution timed out after 10 ms
Assertions.assertTimeoutPreemptively(Duration.ofMillis(10), () -> {
// Simulate task that takes more than 10 ms.
new CountDownLatch(1).await();
});
}
该断言利用线程池和具有生命周期的Future来实现任务超时判断,通过Future.get方法等待指定的timeout时间,然后根据该返回结果判断是否任务正常结束还是抛出异常or超时,从而判断是否测试成功。
与assertTimeout区别:assertTimeoutPreemptively超时会停止测试方法,并返回结果,而assertTimeout会等待任务执行结束再进行处理,也就是说如果任务有死循环,测试也会死循环。
2.4 假言测试
Junit Assumption一般位于包:org.junit.jupiter.api.Assumptions;
assumeTrue测试:
@Test
void testOnlyOnAMDServer() {
Assumptions.assumeTrue("AMD64".equals(System.getenv().get("PROCESSOR_ARCHITECTURE")));
// remainder of test
}
判断条件为true测试成功。
assumingTrue重载测试:
@Test
void testAssumptionTrue() {
Assumptions.assumeTrue("AMD64".equals(System.getenv().get("PROCESSOR_ARCHITECTURE")), "Aborting test if not AMD64");
}
测试失败会抛出信息:"Aborting test if not AMD64"
。
assumingThat测试:
@Test
void testThatOnlyAMD() {
Assumptions.assumingThat("AMD64".equals(System.getenv().get("PROCESSOR_ARCHITECTURE")), () -> {
Assertions.assertEquals(3, calculator.add(1, 2));
});
}
执行提供的lambda,但前提是提供的假设有效。
2.5 禁用测试
可以通过提供的@Disabled
来禁用测试类或测试方法。
禁用测试类:
@Disabled("Disable test class until bug fixed")
class DisabledClassDemo {
@Test
void skipTest() {
}
}
禁用测试方法:
@Disabled("disable test function until bug fixed")
@Test
void skipTest() {
}
@Test
void normalTest() {
}
上述skipTest
将会被跳过。
建议:JUnit团队建议开发人员提供一个简短的解释,说明为什么一个测试类或测试方法被禁用了。
2.6 测试顺序
Junit提供了方法测试执行顺序和测试类执行顺序(参考官网:Junit5.Test Execution Order),这里以方法测试为例:
@TestMethodOrder(MethodOrderer.OrderAnnotation.class)
public class OrderedTestDemo {
@Test
@Order(1)
void nullValues() {
// perform assertions against null values
}
@Test
@Order(2)
void emptyValues() {
// perform assertions against empty values
}
@Test
@Order(3)
void validValues() {
// perform assertions against valid values
}
}
测试执行结果:
通过注解@TestMethodOrder(MethodOrderer.OrderAnnotation.class)
来说明测试类按照顺序进行测试,@Order
进行测试排序,需要传入参数value。
2.7 重复测试
注释@RepeatedTest()
进行重复测试,需要传入重复次数,示例demo:
@RepeatedTest(5)
void repeatedTest() {
Assertions.assertEquals(4, calculator.add(1, 3));
}
执行结果:
查看注解源码,该注解支持自定义name属性。
2.8 参数化类型测试
Junit支持通过@ParameterizedTest和@ValueSource
进行指定参数列表测试。支持的参数类型如下:
- short
- byte
- int
- long
- float
- double
- char
- boolean
- java.lang.String
- java.lang.Class
测试demo如下:
@ParameterizedTest
@ValueSource(ints = { 1, 2, 3 })
void testWithValueSource(int argument) {
Assertions.assertTrue(argument > 0 && argument < 4);
}
执行结果:
对传入的参数数组进行逐一判断。
2.9 动态测试
这种新类型的测试是一个动态测试,它是在运行时由带有@TestFactory
注释的工厂方法生成的。
动态测试demo:
// error-This will result in a JUnitException!
@TestFactory
List<String> dynamicTestsWithInvalidReturnType() {
return Arrays.asList("Hello");
}
@TestFactory
Collection<DynamicTest> dynamicTestsFromCollection() {
return Arrays.asList(
DynamicTest.dynamicTest("1st dynamic test", () -> Assertions.assertEquals(5, calculator.add(1, 4))),
DynamicTest.dynamicTest("2nd dynamic test", () -> Assertions.assertEquals(4, calculator.add(2, 2)))
);
}
上述中dynamicTestsWithInvalidReturnType
将会返回错误,因为不是动态测试支持的返回类型,通常我们有以下几种支持的返回类型:
- Collection<DynamicTest>。
- Iterable<DynamicTest>。
- Iterator<DynamicTest>。
- DynamicTest[]。
- Stream<DynamicTest>。
上述的demo其实不算真正的动态测试,因为相当于把静态测试封装起来动态生成,每次执行结果是一样的。下述demo是真实动态的测试:
@TestFactory
Stream<DynamicTest> generateRandomNumberOfTestsFromIterator() {
// Generates random positive integers between 0 and 100 until
// a number evenly divisible by 7 is encountered.
Iterator<Integer> inputGenerator = new Iterator<Integer>() {
Random random = new Random();
int current;
@Override
public boolean hasNext() {
current = random.nextInt(100);
return current % 3 == 0;
}
@Override
public Integer next() {
return current;
}
};
// Generates display names like: input:5, input:37, input:85, etc.
Function<Integer, String> displayNameGenerator = (input) -> "input:" + input;
// Executes tests based on the current input value.
ThrowingConsumer<Integer> testExecutor = (input) -> assertTrue(input % 3 != 0);
// Returns a stream of dynamic tests.
return DynamicTest.stream(inputGenerator, displayNameGenerator, testExecutor);
}
这个demo测试是动态生成的,而且流的大小每次会不同,因为hasNext每次不同,这个测试更符合我们动态测试的概念。
我们可以利用inputGenerator生成我们测试的测试参数,动态变化,记得hasNext设置随机跳出条件。
然后利用testExecutor设置我们的测试条件。
2.10 超时测试
注释@Timeout
运行终止一个测试,当达到超时时间设置。
测试demo如下:
@Test
@Timeout(value = 5, unit = TimeUnit.SECONDS)
void testTimeout() {
try {
Thread.sleep(10000L);
} catch (InterruptedException e) {
e.printStackTrace();
}
}
运行结果:
这里可以看出,@Timeout
只是在任务结束后,判断执行时间是否测试成功,不会主动停止任务。
参考文献
[1] Junit5官网