专栏首页我要变牛五年了,你还在用junit4吗?

五年了,你还在用junit4吗?

junit5

JUnit5在2017年就发布了,你还在用junit4吗?

什么是junit5

与以前的JUnit版本不同,JUnit 5由三个不同子项目的多个不同模块组成。

JUnit 5 = JUnit Platform + JUnit Jupiter + JUnit Vintage

JUnit Platform为在JVM上启动测试框架提供基础。它还定义了TestEngine API, 用来开发在平台上运行的测试框架。此外,平台提供了一个控制台启动器],用于从命令行启动平台,并为Gradle和Maven提供构建插件以[基于JUnit 4的Runner,用于在平台上运行任意TestEngine

JUnit Jupiter是在JUnit 5中编写测试和扩展的新型编程模型和[扩展模型][]的组合.Jupiter子项目提供了TestEngine,用于在平台上运行基于Jupiter的测试。

JUnit Vintage提供TestEngine,用于在平台上运行基于JUnit 3和JUnit 4的测试。

为什么需要 JUnit 5

自从有了类似 JUnit 之类的测试框架,Java 单元测试领域逐渐成熟,开发人员对单元测试框架也有了更高的要求:更多的测试方式,更少的其他库的依赖。

因此,大家期待着一个更强大的测试框架诞生,JUnit 作为Java测试领域的领头羊,推出了 JUnit 5 这个版本,主要特性:

  • 提供全新的断言和测试注解,支持测试类内嵌
  • 更丰富的测试方式:支持动态测试,重复测试,参数化测试等
  • 实现了模块化,让测试执行和测试发现等不同模块解耦,减少依赖
  • 提供对 Java 8 的支持,如 Lambda 表达式,Sream API等。

基本注解

@Test: 表示方法是测试方法。但是与JUnit4的@Test不同,他的职责非常单一不能声明任何属性,拓展的测试将会由Jupiter提供额外测试

@ParameterizedTest: 表示方法是参数化测试

@RepeatedTest: 表示方法可重复执行

@DisplayName: 为测试类或者测试方法设置展示名称

@BeforeEach: 表示在每个单元测试之前执行

@AfterEach: 表示在每个单元测试之后执行

@BeforeAll: 表示在所有单元测试之前执行

@AfterAll: 表示在所有单元测试之后执行

@Tag: 表示单元测试类别,类似于JUnit4中的@Categories

@Disabled: 表示测试类或测试方法不执行,类似于JUnit4中的@Ignore

@Timeout: 表示测试方法运行如果超过了指定时间将会返回错误

@ExtendWith: 为测试类或测试方法提供扩展类引用

常用注解格式:

class StandardTests {

    //与junit4的@beforeClass类似,每个测试类运行一次
    @BeforeAll
    static void initAll() {
    }

    //与junit4中@before类似,每个测试用例都运行一次
    @BeforeEach
    void init() {
    }

    @Test
    @DisplayName("成功测试")
    void succeedingTest() {
    }

    @Test
    @DisplayName("失败测试")
    void failingTest() {
        fail("a failing test");
    }

    //禁用测试用例
    @Test
    @Disabled("for demonstration purposes")
    void skippedTest() {
        // not executed
    }

    @Test
    void abortedTest() {
        assumeTrue("abc".contains("Z"));
        fail("test should have been aborted");
    }


    //与@BeforeEach对应,每个测试类执行一次,一般用于恢复环境
    @AfterEach
    void tearDown() {
    }

    //与@BeforeAll对应,每个测试类执行一次,一般用于恢复环境
    @AfterAll
    static void tearDownAll() {
    }
}

新特性

显示名称

@DisplayName("显示名称测试")
class DisplayNameDemo {

    @Test
    @DisplayName("我的 第一个 测试 用例")
    void testWithDisplayNameContainingSpaces() {
    }

    @Test
    @DisplayName("╯°□°)╯")
    void testWithDisplayNameContainingSpecialCharacters() {
    }

    @Test
    @DisplayName("?")
    void testWithDisplayNameContainingEmoji() {
    }
}

IDE运行测试结果显示:

image-20210416232329161

**优点:**通过这种方式,可以在方法名是英文特别长或者很难用英文描述清楚的场景下,增加中文解释

更强大的断言

JUnit Jupiter提供了许多JUnit4已有的断言方法,并增加了一些适合与Java 8 lambda一起使用的断言方法。所有JUnit Jupiter断言都是[org.junit.jupiter.Assertions]类中的静态方法。

分组断言:

多个条件同时满足时才断言成功

@Test
void groupedAssertions() {
    Person person = new Person();

    Assertions.assertAll("person",
                         () -> assertEquals("niu", person.getName()),
                         () -> assertEquals(18, person.getAge())
                        );
}

异常断言:

Junit4时需要使用rule方式,junit5提供了assertThrows更优雅的异常断言

@Test
void exceptionTesting() {
    Throwable exception = assertThrows(IllegalArgumentException.class, () -> {
        throw new IllegalArgumentException("a message");
    });
    assertEquals("a message", exception.getMessage());
}

超时断言:

@Test
@DisplayName("超时测试")
public void timeoutTest() {
    Assertions.assertTimeout(Duration.ofMillis(100), () -> Thread.sleep(50));
}

标签和过滤

通过标签把测试分组,在不同阶段执行不同的逻辑测试,比如划分为快速冒烟测试和执行慢但也重要的测试

@Test
@Tag("fast")
 void testing_faster() {
}

@Test
@Tag("slow")
 void testing_slow() {
}

然后通过配置maven-surefire-plugin插件

<plugin>
    <artifactId>maven-surefire-plugin</artifactId>
    <version>2.22.0</version>
    <configuration>
        <properties>
            <includeTags>fast</includeTags>
            <excludeTages>slow</excludeTages>
        </properties>
    </configuration>
</plugin>

嵌套测试

当我们编写的类和代码逐渐增多,随之而来的需要测试的对应测试类也会越来越多。

为了解决测试类数量爆炸的问题,JUnit 5提供了@Nested 注解,能够以静态内部成员类的形式对测试用例类进行逻辑分组。

并且每个静态内部类都可以有自己的生命周期方法, 这些方法将按从外到内层次顺序执行。

此外,嵌套的类也可以用@DisplayName 标记,这样我们就可以使用正确的测试名称。下面看下简单的用法:

@DisplayName("A stack")
class TestingAStackDemo {

    Stack<Object> stack;

    @Test
    @DisplayName("is instantiated with new Stack()")
    void isInstantiatedWithNew() {
        new Stack<>();
    }

    @Nested
    @DisplayName("when new")
    class WhenNew {

        @BeforeEach
        void createNewStack() {
            stack = new Stack<>();
        }

        @Test
        @DisplayName("is empty")
        void isEmpty() {
            assertTrue(stack.isEmpty());
        }


        @Nested
        @DisplayName("after pushing an element")
        class AfterPushing {

            String anElement = "an element";

            @BeforeEach
            void pushAnElement() {
                stack.push(anElement);
            }

            @Test
            @DisplayName("it is no longer empty")
            void isNotEmpty() {
                assertFalse(stack.isEmpty());
            }
        }
    }
}

junit没有限制嵌套层数,除非必要一般不建议使用超过3层,过于复杂的层次结构会增加开发者理解用例关系的难度

构造函数和方法的依赖注入

在之前的所有JUnit版本中,测试构造函数或方法都不允许有参数(至少不能使用标准的Runner实现)。作为JUnit Jupiter的主要变化之一,测试构造函数和方法现在都允许有参数。这带来了更大的灵活性,并为构造函数和方法启用依赖注入

  • TestInfo可获取测试信息
  • TestReporter可以向控制台输出信息
@Test
@DisplayName("test-first")
@Tag("my-tag")
void test1(TestInfo testInfo) {
    assertEquals("test-first", testInfo.getDisplayName());
    assertTrue(testInfo.getTags().contains("my-tag"));
}

@Test
@DisplayName("test-second")
@Tag("my-tag")
void test2(TestReporter testReporter) {
    testReporter.publishEntry("a key", "a value");
}

重复测试

多次调用同一个测试用例

@RepeatedTest(10)
@DisplayName("重复测试")
public void testRepeated() {
    //...
}

image-20210416232512919

动态测试

动态测试只需要编写一处代码,就能一次性对各种类型的输入和输出结果进行验证

@TestFactory
@DisplayName("动态测试")
Stream<DynamicTest> dynamicTests() {
    List<Person> persons = getAllPerson();

    return persons.stream()
        .map(person -> DynamicTest.dynamicTest(person.getName() + "-test", () -> assertTrue(person.getName().contains("niu"))));
}

超时测试

通过时间来验证用例是否超时,一般要求单个单元测试不应该超过1秒

class TimeoutDemo {
    @BeforeEach
    @Timeout(5)
    void setUp() {
        // fails if execution time exceeds 5 seconds
    }

    @Test
    @Timeout(value = 1000, unit = TimeUnit.MILLISECONDS)
    void failsIfExecutionTimeExceeds1000Milliseconds() {
        // fails if execution time exceeds 1000 milliseconds
        //也可用这种方式 Assertions.assertTimeout(Duration.ofMillis(1000), () -> Thread.sleep(1500));
    }
}

参数测试

参数测试我觉得是最好用的特性,可以大量减少重复模板式代码,也是junit5最惊艳的提升,强烈推荐使用

@ValueSource: 为参数化测试指定入参来源,支持八大基础类以及String类型,Class类型 @NullSource: 表示为参数化测试提供一个null的入参 @EnumSource: 表示为参数化测试提供一个枚举入参 @CsvSource:表示读取CSV格式内容作为参数化测试入参 @CsvFileSource:表示读取指定CSV文件内容作为参数化测试入参 @MethodSource:表示读取指定方法的返回值作为参数化测试入参(注意方法返回需要是一个流) @ArgumentsSource:指定一个自定义的,可重用的ArgumentsProvider

看完用法描述,简直太喜欢了

一个顶三个基础测试用例

@ParameterizedTest
@ValueSource(strings = {"one", "two", "three"})
@DisplayName("参数化测试1")
public void parameterizedTest1(String string) {
    assertTrue(StringUtils.isNotBlank(string));
}

image-20210416233807174

如果不是基础的类型,可以使用方法构造,只要返回值为Stream类型就可以,多个参数使用Arguments实例流

@ParameterizedTest
@MethodSource("method")
@DisplayName("方法来源参数")
public void testWithExplicitLocalMethodSource(String name) {
    Assertions.assertNotNull(name);
}

private static Stream<String> method() {
    return Stream.of("apple", "banana");
}

@CsvSource允许您将参数列表表示为以逗号分隔的值(例如,字符串文字)

@ParameterizedTest
@CsvSource({"steven,18", "jack,24"})
@DisplayName("参数化测试-csv格式")
public void parameterizedTest3(String name, Integer age) {
    System.out.println("name:" + name + ",age:" + age);
    Assertions.assertNotNull(name);
    Assertions.assertTrue(age > 0);
}

image-20210416232702304

@CsvFileSource使用classpath中的CSV文件,CSV文件中的每一行都会导致参数化测试的一次调用

这种就完全把测试数据与测试方法隔离,达到更好解耦效果

@ParameterizedTest
@CsvFileSource(resources = "/persons.csv")  //指定csv文件位置
@DisplayName("参数化测试-csv文件")
public void parameterizedTest2(String name, Integer age) {
    System.out.println("name:" + name + ",age:" + age);
    Assertions.assertNotNull(name);
    Assertions.assertTrue(age > 0);
}

其他方式不在赘述,如果还是满足不了需求,可以通过@ArgumentsSource自定义自己的数据来源,必须封装成去取JSON或者XMl等数据

AssertJ

当定义好需要运行的测试方法后,下一步则是需要关注测试方法的细节,这就离不开断言和假设

断言:封装好了常用判断逻辑,当不满足条件时,该测试用例会被认为测试失败 假设:与断言类似,当条件不满足时,测试会直接退出而不是判定为失败

因为不会影响到后续的测试用例,最常用的还是断言

除了Junit5自带的断言,AssertJ是非常好用的一个断言工具,最大特点是提供了流式断言,与Java8使用方法非常类似

@Test
void testString() {
    // 断言null或为空字符串
    assertThat("").isNullOrEmpty();
    // 断言空字符串
    assertThat("").isEmpty();
    // 断言字符串相等 断言忽略大小写判断字符串相等
    assertThat("niu").isEqualTo("niu").isEqualToIgnoringCase("NIu");
    // 断言开始字符串 结束字符穿 字符串长度
    assertThat("niu").startsWith("ni").endsWith("u").hasSize(3);
    // 断言包含字符串 不包含字符串
    assertThat("niu").contains("iu").doesNotContain("love");
    // 断言字符串只出现过一次
    assertThat("niu").containsOnlyOnce("iu");
}

@Test
void testNumber() {
    // 断言相等
    assertThat(42).isEqualTo(42);
    // 断言大于 大于等于
    assertThat(42).isGreaterThan(38).isGreaterThanOrEqualTo(38);
    // 断言小于 小于等于
    assertThat(42).isLessThan(58).isLessThanOrEqualTo(58);
    // 断言0
    assertThat(0).isZero();
    // 断言正数 非负数
    assertThat(1).isPositive().isNotNegative();
    // 断言负数 非正数
    assertThat(-1).isNegative().isNotPositive();
}

@Test
void testCollection() {
    // 断言 列表是空的
    assertThat(newArrayList()).isEmpty();
    // 断言 列表的开始 结束元素
    assertThat(newArrayList(1, 2, 3)).startsWith(1).endsWith(3);
    // 断言 列表包含元素 并且是排序的
    assertThat(newArrayList(1, 2, 3)).contains(1, atIndex(0)).contains(2, atIndex(1)).contains(3)
        .isSorted();
    // 断言 被包含与给定列表
    assertThat(newArrayList(3, 1, 2)).isSubsetOf(newArrayList(1, 2, 3, 4));
    // 断言 存在唯一元素
    assertThat(newArrayList("a", "b", "c")).containsOnlyOnce("a");
}

@Test
void testMap() {
    Map<String, Object> foo = ImmutableMap.of("A", 1, "B", 2, "C", 3);

    // 断言 map 不为空 size
    assertThat(foo).isNotEmpty().hasSize(3);
    // 断言 map 包含元素
    assertThat(foo).contains(entry("A", 1), entry("B", 2));
    // 断言 map 包含key
    assertThat(foo).containsKeys("A", "B", "C");
    // 断言 map 包含value
    assertThat(foo).containsValue(3);
}
// 其他断言,请自行探索......

想想如果没有使用AssertJ时我们是如何写断言的,是不是需要多个assert,很繁琐

AssertJ的断言代码清爽很多,流式断言充分利用了java8之后的匿名方法和stream类型的特点,很好的对Junit断言方法做了补充。

参考 https://junit.org/junit5/docs/current/user-guide/#overview https://assertj.github.io/doc/

本文分享自微信公众号 - 你呀不牛(notNiu),作者:不牛

原文出处及转载信息见文内详细说明,如有侵权,请联系 yunjia_community@tencent.com 删除。

原始发表时间:2021-04-17

本文参与腾讯云自媒体分享计划,欢迎正在阅读的你也加入,一起分享。

我来说两句

0 条评论
登录 后参与评论

相关文章

  • 都2020年了,你还在使用.XLS格式的文件吗?

    比如,我们从本地获取xls格式的文档,建模、可视化、刷新一切ok,发布到云端,使用gateway配置和刷新也都没问题:

    陈学谦
  • 你还在用CMD吗?

    就在前两天微软的大会上,微软发布了一款划时代的小软件。Windows Terminal 是一个全新的、流行的、功能强大的命令行终端工具。包含很多来社区呼声很高的...

    剑指工控
  • 都什么年代了,你还在手动配置吗

    随着程序功能的日益复杂,程序的配置日益增多:各种功能的开关、参数的配置、服务器的地址等等,对程序配置的期望值也越来越高:配置修改后实时生效,灰度发布,分环境、分...

    yuanyi928
  • 你还在用 MyBatis 吗,Ktorm 了解一下?

    Google 宣布 Kotlin 成为 Android 的官方语言,Kotlin 可以说是突然火了一波。其实不仅仅是 Android,在服务端开发的领域,Kot...

    bennyhuo
  • 什么!都已经9102年了,你还在用pubmed搜文献吗?

    说到搜索英文文献,最常用的应该就是Pubmed了,但是Pubmed远非十全十美,比如说Pubmed就有以下缺点:

    百味科研芝士
  • 五四灵魂拷问:你还是青年吗?

    ? 面对即将到来的五四青年节,有些90后在感叹青春已逝,有些则坚称自己还是个宝宝?? ? 然鹅,根据联合国规定 90后的你可能已经不再属于青年范畴了! ? ...

    鹅老师
  • 都2020年了你还在用tophat吗(RNA-seq数据免费分析)

    如果你现在(2020)做人类数据分析,比如lncRNA的鉴定啥的,当然是走hisat2+stringTie流程啦,取代已经十多年了的tophat+Cufflin...

    生信技能树
  • 你还在用if else吗?

    面向过程设计和面向对象设计的主要区别是:是否在业务逻辑层使用冗长的if else判断。如果你还在大量使用if else,当然,界面表现层除外,即使你使用Java...

    lyb-geek
  • 你还在用 REST API 吗?

    通过 HTTP 发送数据,许多开发人员已经在用 REST 了,而 GraphQL 通常被认为是一种代替遗留 REST API 的技术。本文将对比两者各自的优势、...

    深度学习与Python
  • 2018 年了,你还是只会 npm install 吗?

    你真的了解 npm 吗 ?重新介绍 npm 。

    Techeek
  • 0202年了, Chrome DevTools 你还只会console.log吗 ?

    在元素面板选择一个元素,然后在控制台输入 $0,就会在控制台中得到刚才选中的元素。如果页面中已经包含了 jQuery,你也可以使用 $($0)来进行选择。

    madneal
  • flutter_bloc使用解析---骚年,你还在手搭bloc吗!

    来看下这三个生成的bloc文件:main_bloc,main_event,main_state

    小呆呆666
  • 你还在用轮播图吗

    腾讯ISUX
  • 腾讯TMQ在线沙龙|Robotium自动化测试介绍

    Robotium自动化测试介绍 活动时间:2016年9月29日 QQ群视频交流 活动介绍:TMQ在线沙龙第九期分享 本次分享的主题是Robotium自动化测试介...

    腾讯移动品质中心TMQ
  • UT之最后一测

    经过前面几次文章的分享的UT的相关知识,今天接着分享UT相关最后一测文章,希望对大家在UT的学习中有一点点的帮助。

    你呀不牛
  • UT之最后一测

    有时候我们需要在跑起来的Spring环境中验证,Spring 框架提供了一个专门的测试模块(spring-test),用于应用程序的集成测试。

    你呀不牛
  • Eclipse中使用JUnit4进行单元测试(整合篇)

    我们在编写大型程序的时候,需要写成千上万个方法或函数,这些函数的功能可能很强大,但我们在程序中只用到该函数的一小部分功能,并且经过调试可以确定,这一小部分功能是...

    流川枫
  • 你还在用2345的软件吗?

    12月1日,“火绒安全实验室”发出警报,一款名为“云计算”的软件,正通过各种流氓渠道大肆推广,该软件除了把用户电脑当“肉鸡“进行挖矿外,没有任何其他功能...

    课代表
  • 万字干货分享 | 2020年了,你还在用注意力作可解释吗?

    本文主要基于2020 EMNLP Workshop上的一篇综述文章,介绍了NLP可解释领域的重大争议——注意力机制是否能作为解释?而什么方法才是真正符合解释逻辑...

    TechFlow-承志

扫码关注云+社区

领取腾讯云代金券