JUnit5 初体验

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
注意:

  1. 参数源方法必须是static;
  2. 如果@MethodSource没有明确提供参数源方法名称,则JUnit Jupiter将按照约定去搜索与当前@ParameterizedTest方法名称相同的工厂方法
  3. 参数源方法不仅支持Stream,还支持DoubleStream,LongStream,IntStream,Collection,Iterator,Iterable,对象数组或基元数组;
  4. 使用@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> parameter() {
        Map map1 = new HashMap<>();
        map1.put("map1","map1");
        Map map2 = new HashMap<>();
        map2.put("map2","map2");
        return Stream.of(map1, map2);
    }

当参数有多个,参数不是基本类型,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的精髓在于条件测试,嵌套测试,重复测试,测试模版,动态测试,并行执行等。欲知后事如何,且听下回分解。啾咪~

你可能感兴趣的:(JUnit5 初体验)