JDK都更新换代到12了,不来尝试一下JUnit5么?
JUnit5 Maven依赖
org.junit.platform
junit-platform-commons
1.4.1
org.junit.jupiter
junit-jupiter
5.4.0
org.junit.vintage
junit-vintage-engine
5.4.0
Junit5包括三个模块:
JUnit 5 = JUnit Platform + JUnit Jupiter + JUnit Vintage
这三个东西具体是什么作用,可以去官网查阅,在这里就不赘述了,本次重点在于使用。
需要注意一点的是JUnit5需要Java8或者更高的运行环境。
增加依赖后,就使用JUnit5进行单元测试了。
import org.junit.jupiter.api.Test;
public class JUnit5Test {
@Test
protected void test() {
System.out.println("......");
}
}
但是我们一般不会单独使用JUnit5,都是与Spring整合使用。
Spring整合JUnit5同JUnit4,引入Maven依赖,需要注意的是Spring版本,需要5.0以上,本次引入的是5.1.5.RELEASE。
org.springframework
spring-test
5.1.5.RELEASE
import org.junit.jupiter.api.Test;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.test.context.junit.jupiter.web.SpringJUnitWebConfig;
import static org.junit.jupiter.api.Assertions.assertAll;
import static org.junit.jupiter.api.Assertions.assertTrue;
//@ExtendWith(SpringExtension.class)
@SpringJUnitWebConfig(locations = "classpath*:applicationContext-test.xml")
public class BaseJunit5Test {
@Autowired
TestSPI testSPI;
@Test
protected void test() {
Test test = shopSPI.loadShopByCode("");
assertAll(
() -> assertTrue(test != null)
);
}
}
JUnit5 注解
注解 | 描述 |
---|---|
@Test | 表示该方法是一个测试方法。修饰符可以是public/protected/private。 但是修饰为private时编译器会通过,测试也不会报错,但不会跑测试,相当于将这个Test关闭。所以不建议使用private |
@ParameterizedTest | 表示该方法是一个参数化测试 |
@RepeatedTest | 表示该方法是一个重复测试的测试模板 |
@TestFactory | 表示该方法是一个动态测试的测试工厂 |
@TestInstance | 用于配置所标注的测试类的 测试实例生命周期 |
@TestTemplate | 表示该方法是一个测试模板,它会依据注册的 提供者所返回的调用上下文的数量被多次调用。 |
@DisplayName | 为测试类或测试方法声明一个自定义的显示名称。该注解不能被继承 |
@BeforeEach | 表示使用了该注解的方法应该在当前类中每一个使用了@Test 、@RepeatedTest 、@ParameterizedTest 或者@TestFactory 注解的方法之前 执行;类似于JUnit 4的 @Before。这样的方法会被继承,除非它们被覆盖。 |
@AfterEach | 表示使用了该注解的方法应该在当前类中每一个使用了@Test 、@RepeatedTest 、@ParameterizedTest 或者@TestFactory 注解的方法之后 执行;类似于JUnit 4的@After 。这样的方法会被继承,除非它们被覆盖。 |
@BeforeAll | 表示使用了该注解的方法应该在当前类中所有使用了@Test 、@RepeatedTest 、@ParameterizedTest 或者@TestFactory 注解的方法之前 执行;类似于JUnit 4的 @BeforeClass 。这样的方法会被继承(除非它们被隐藏 或覆盖),并且它必须是 static 方法(除非"per-class" 测试实例生命周期) |
@AfterAll | 表示使用了该注解的方法应该在当前类中所有使用了@Test 、@RepeatedTest 、@ParameterizedTest 或者@TestFactory 注解的方法之后执行;类似于JUnit 4的 @AfterClass 。这样的方法会被继承(除非它们被隐藏 或覆盖),并且它必须是 static 方法(除非"per-class" 测试实例生命周期 被使用) |
@Nested | 表示使用了该注解的类是一个内嵌、非静态的测试类。@BeforeAll 和@AfterAll 方法不能直接在@Nested 测试类中使用,(除非"per-class" 测试实例生命周期 被使用)。该注解不能被继承 |
@Tag | 用于声明过滤测试的tags,该注解可以用在方法或类上;类似于TesgNG的测试组或JUnit 4的分类。该注解能被继承,但仅限于类级别,而非方法级别 |
@Disable | 用于禁用一个测试类或测试方法;类似于JUnit 4的@Ignore。该注解不能被继承 |
@ExtendWith | 用于注册自定义 扩展该注解不能被继承。 |
JUnit5参数化测试
想要写一个规范的单元测试,少不了参数化测试。
如果要进行参数化测试,就不可以用普通的@Test注解了,使用注解:org.junit.jupiter.params.ParameterizedTest。但如果你使用的@ParameterizedTest注解,就必须提供参数源,然后在测试方法中消费 这些参数,否则会报错。报错信息:
org.junit.platform.commons.util.PreconditionViolationException: Configuration error: You must configure at least one set of arguments for this @ParameterizedTest
当只有一个参数时,参数类型是:short,byte,int,long,float,double,char,String,Class,使用@ValueSource
import static org.junit.jupiter.api.Assertions.assertAll;
import org.junit.jupiter.params.ParameterizedTest;
import org.junit.jupiter.params.provider.ValueSource;
@ParameterizedTest
@ValueSource(strings = { "green", "red" })
public void querySeriesByBrand(String brandCode){
List seriesDTOS = carSPI.querySeriesByBrand(brandCode);
assertAll(
() -> assertTrue(seriesDTOS != null)
);
}
当多个参数,参数类型为short,byte,int,long,float,double,char,String,Class,使用@CsvSource
示例输入 | 生成的参数列表 |
---|---|
@CsvSource({ "green, red" }) | "green", "red" |
@CsvSource({ "green, 'red, yellow'" }) | "green", "red, yellow" |
@CsvSource({ "green, ''" }) | "green", "" |
@CsvSource({ "green, " }) | "green", null |
如果null引用的目标类型是基本类型,则会抛出一个ArgumentConversionException。
@ParameterizedTest
@CsvSource({"green,001", "red,002"})
public void getCustomerByCrmUserIdAndShopCodeTest(String crmUserId, String shopCode) {
Customer customer = cupidSPI.getCustomerByCrmUserIdAndShopCode(crmUserId, shopCode);
if (crmUserId.equals("green")) {
assertAll(
() -> assertTrue(customer.getId().equals("green"))
);
}
if (crmUserId.equals("red")) {
assertAll(
() -> assertTrue(customer == null)
);
}
}
当多个参数,参数类型为short,byte,int,long,float,double,char,String,Class,也可以使用@CsvFileSource
@CsvFileSource允许你使用类路径中的CSV文件。CSV文件中的每一行都会触发参数化测试的一次调用。
@ParameterizedTest
@CsvFileSource(resources = "../disSPITest.cvs")
public void queryDictionaryByTypeandLevelTest(String type, String level) {
List list = dicSPI.queryDictionaryByTypeandLevel(type, level);
if (type.equals("green")) {
assertTrue(list.size() == 1);
}
if (type.equals("yellow")) {
assertTrue(list == null);
}
if (type.equals("red")) {
assertTrue(list == null);
}
}
disSPITest.cvs
green,middle
yellow,
red,low
当只有一个参数,参数不是基本类型,String,Class,使用 @MethodSource
注意:
- 参数源方法必须是static;
- 如果@MethodSource没有明确提供参数源方法名称,则JUnit Jupiter将按照约定去搜索与当前@ParameterizedTest方法名称相同的工厂方法
- 参数源方法不仅支持Stream,还支持DoubleStream,LongStream,IntStream,Collection,Iterator,Iterable,对象数组或基元数组;
- 使用@BeforeAll,需要加类注释@TestInstance(Lifecycle.PER_CLASS)或者@ BeforeAll修饰的方法static修饰
/**
* mock数据
*/
private void initShopService(){
Map map1 = new HashMap<>();
map1.put("map1","map1");
Map map2 = new HashMap<>();
map2.put("map2","map2");
when(carSoucrceService.getCarSourceListNum(map1)).thenReturn(1);
when(carSoucrceService.getCarSourceListNum(map2)).thenReturn(2);
}
/**
* 类似于JUnit 4的 @BeforeClass
*/
@BeforeAll
public void init(){
initShopService();
}
@ParameterizedTest
@MethodSource("parameter")
public void getCarSourceListNumTest( Map map ){
Integer num = carSoucrceServiceSPI.getCarSourceListNum(map);
assertAll(
() -> assertTrue(num >= 1)
);
}
/**
* 参数源
*/
static Stream
当参数有多个,参数不是基本类型,String,Class,使用 @MethodSource,且需要Arguments配合构造参数源
private void initCupidService() {
/**
* mock数据
*/
private void initDictionaryService() {
String type = "green";
List list = new ArrayList<>();
when(dictionaryService.getDictionaryListByCodes(type, Arrays.asList("a", "b"))).thenReturn(list);
when(dictionaryService.getDictionaryListByCodes("red", Arrays.asList("x", "y"))).thenThrow(new NullPointerException());
}
/**
* 类似于JUnit 4的 @BeforeClass
*/
@BeforeAll
public void init() {
initDictionaryService();
}
@ParameterizedTest
@MethodSource("stringAndListProvider")
public void getDictionaryListByCodesTest(String type, List codes){
List list = dicSPI.getDictionaryListByCodes(type, codes);
assertAll(
() -> assertTrue(list.size() > 0)
);
}
static Stream stringAndListProvider() {
return Stream.of(
Arguments.of("green", Arrays.asList("a", "b")),
Arguments.of("red", Arrays.asList("x", "y"))
);
}
除了上诉几个常用的注解,JUnit5还提供了:
@EnumSource
@EnumSource能够很方便地提供Enum常量。该注解提供了一个可选的names参数,你可以用它来指定使用哪些常量。如果省略了,就意味着所有的常量将被使用,就像下面的例子所示。
@ArgumentsSource
@ArgumentsSource 可以用来指定一个自定义且能够复用的ArgumentsProvider。自定义一个类implements ArgumentsProvider,@ArgumentsSource制定自定义类。
等等其他构造参数源的方法。
JUnit5断言
JUnit Jupiter附带了很多JUnit 4就已经存在的断言方法,并增加了一些适合与Java8 Lambda一起使用的断言。所有的JUnit Jupiter断言都是 org.junit.jupiter.api.Assertions类中static
方法。
以上仅是JUnit5的初体验,JUnit5的精髓在于条件测试,嵌套测试,重复测试,测试模版,动态测试,并行执行等。欲知后事如何,且听下回分解。啾咪~