JUnit5 初体验

JDK都更新换代到12了,不来尝试一下JUnit5么?

JUnit5 Maven依赖

<dependency>
    <groupId>org.junit.platform</groupId>
    <artifactId>junit-platform-commons</artifactId>
    <version>1.4.1</version>
    </dependency>

<dependency>
    <groupId>org.junit.jupiter</groupId>
    <artifactId>junit-jupiter</artifactId>
    <version>5.4.0</version>
</dependency>

<dependency>
    <groupId>org.junit.vintage</groupId>
    <artifactId>junit-vintage-engine</artifactId>
    <version>5.4.0</version>
</dependency>

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。

<dependency>
    <groupId>org.springframework</groupId>
    <artifactId>spring-test</artifactId>
    <version>5.1.5.RELEASE</version>
</dependency>

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<SeriesDTO> 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<DictionaryVO> 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<String,Object> map1 = new HashMap<>();
        map1.put("map1","map1");
        Map<String,Object> 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<String,Object> map ){
        Integer num = carSoucrceServiceSPI.getCarSourceListNum(map);
        assertAll(
            () -> assertTrue(num >= 1)
        );
    }

    /**
     * 参数源
     */
    static Stream<Map<String,Object>> parameter() {
        Map<String,Object> map1 = new HashMap<>();
        map1.put("map1","map1");
        Map<String,Object> 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<DictionaryVO> 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<String> codes){
        List<DictionaryVO> list = dicSPI.getDictionaryListByCodes(type, codes);
        assertAll(
            () -> assertTrue(list.size() > 0)
        );
    }


    static Stream<Arguments> 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的精髓在于条件测试,嵌套测试,重复测试,测试模版,动态测试,并行执行等。欲知后事如何,且听下回分解。啾咪💕💕~

©著作权归作者所有,转载或内容合作请联系作者
  • 序言:七十年代末,一起剥皮案震惊了整个滨河市,随后出现的几起案子,更是在滨河造成了极大的恐慌,老刑警刘岩,带你破解...
    沈念sama阅读 200,392评论 5 470
  • 序言:滨河连续发生了三起死亡事件,死亡现场离奇诡异,居然都是意外死亡,警方通过查阅死者的电脑和手机,发现死者居然都...
    沈念sama阅读 84,258评论 2 377
  • 文/潘晓璐 我一进店门,熙熙楼的掌柜王于贵愁眉苦脸地迎上来,“玉大人,你说我怎么就摊上这事。” “怎么了?”我有些...
    开封第一讲书人阅读 147,417评论 0 332
  • 文/不坏的土叔 我叫张陵,是天一观的道长。 经常有香客问我,道长,这世上最难降的妖魔是什么? 我笑而不...
    开封第一讲书人阅读 53,992评论 1 272
  • 正文 为了忘掉前任,我火速办了婚礼,结果婚礼上,老公的妹妹穿的比我还像新娘。我一直安慰自己,他们只是感情好,可当我...
    茶点故事阅读 62,930评论 5 360
  • 文/花漫 我一把揭开白布。 她就那样静静地躺着,像睡着了一般。 火红的嫁衣衬着肌肤如雪。 梳的纹丝不乱的头发上,一...
    开封第一讲书人阅读 48,199评论 1 277
  • 那天,我揣着相机与录音,去河边找鬼。 笑死,一个胖子当着我的面吹牛,可吹牛的内容都是我干的。 我是一名探鬼主播,决...
    沈念sama阅读 37,652评论 3 390
  • 文/苍兰香墨 我猛地睁开眼,长吁一口气:“原来是场噩梦啊……” “哼!你这毒妇竟也来了?” 一声冷哼从身侧响起,我...
    开封第一讲书人阅读 36,327评论 0 254
  • 序言:老挝万荣一对情侣失踪,失踪者是张志新(化名)和其女友刘颖,没想到半个月后,有当地人在树林里发现了一具尸体,经...
    沈念sama阅读 40,463评论 1 294
  • 正文 独居荒郊野岭守林人离奇死亡,尸身上长有42处带血的脓包…… 初始之章·张勋 以下内容为张勋视角 年9月15日...
    茶点故事阅读 35,382评论 2 317
  • 正文 我和宋清朗相恋三年,在试婚纱的时候发现自己被绿了。 大学时的朋友给我发了我未婚夫和他白月光在一起吃饭的照片。...
    茶点故事阅读 37,432评论 1 329
  • 序言:一个原本活蹦乱跳的男人离奇死亡,死状恐怖,灵堂内的尸体忽然破棺而出,到底是诈尸还是另有隐情,我是刑警宁泽,带...
    沈念sama阅读 33,118评论 3 315
  • 正文 年R本政府宣布,位于F岛的核电站,受9级特大地震影响,放射性物质发生泄漏。R本人自食恶果不足惜,却给世界环境...
    茶点故事阅读 38,704评论 3 303
  • 文/蒙蒙 一、第九天 我趴在偏房一处隐蔽的房顶上张望。 院中可真热闹,春花似锦、人声如沸。这庄子的主人今日做“春日...
    开封第一讲书人阅读 29,787评论 0 19
  • 文/苍兰香墨 我抬头看了看天上的太阳。三九已至,却和暖如春,着一层夹袄步出监牢的瞬间,已是汗流浃背。 一阵脚步声响...
    开封第一讲书人阅读 30,999评论 1 255
  • 我被黑心中介骗来泰国打工, 没想到刚下飞机就差点儿被人妖公主榨干…… 1. 我叫王不留,地道东北人。 一个月前我还...
    沈念sama阅读 42,476评论 2 346
  • 正文 我出身青楼,却偏偏与公主长得像,于是被迫代替她去往敌国和亲。 传闻我的和亲对象是个残疾皇子,可洞房花烛夜当晚...
    茶点故事阅读 42,057评论 2 341

推荐阅读更多精彩内容