Junit5 - 1

junit5 官方文档: https://junit.org/junit5/docs/current/user-guide/
https://www.ibm.com/developerworks/cn/java/j-introducing-junit5-part1-jupiter-api/index.html

What is JUnit 5?

与之前的Junit版本不同,Junit5由三部分组成:
JUnit 5 = JUnit Platform + JUnit Jupiter + JUnit Vintage

  • JUnit Platform: 它是在JVM上启动测试框架的基础。 统一命令行、Gradle和Maven执行测试的入口。
  • JUnit Jupiter: 包含了 JUnit 5 最新的编程模型和扩展机制。
  • JUnit Vintage: 允许在平台上运行 JUnit 3 和 JUnit 4 的测试用例。

它的架构图如下:
在这里插入图片描述

JUnit 5 对 Java 运行环境的最低要求是Java 8

JUnit 5 注解

Junit 4 vs Junit 5

JUnit 5JUnit 4说明
@Test@Test被注解的方法是一个测试方法。与 JUnit 4 相同。
@BeforeAll@BeforeClass被注解的(静态)方法将在当前类中的所有 @Test 方法前执行一次。
@BeforeEach@Before被注解的方法将在当前类中的每个 @Test 方法前执行。
@AfterEach@After被注解的方法将在当前类中的每个 @Test 方法后执行。
@AfterAll@AfterClass被注解的(静态)方法将在当前类中的所有 @Test 方法后执行一次。
@Disabled@Ignore被注解的方法不会执行(将被跳过),但会报告为已执行。

注解
JUnit Jupiter支持以下注释,用于配置测试和扩展框架。
所有核心注释都位于junit-jupiter-api模块的org.junit.jupiter.api包中。

AnnotationDescription
@Test被注解的方法是一个测试方法。
@ParameterizedTest表示方法是参数化测试。
@RepeatedTest方法是重复测试的测试模板。
@TestFactory动态测试的测试工厂。
@TestTemplate设计为多次调用的测试用例的模板,具体取决于注册提供程序返回的调用上下文的数量。
@TestMethodOrder用于配置带注释的测试类的测试方法执行顺序;类似于JUnit 4 的@FixMethodOrder
@TestInstance带注释的测试类配置测试实例生命周期。
@DisplayName类或测试方法的自定义显示名称。
@DisplayNameGeneratio声明测试类的自定义显示名称生成器。
@BeforeEach当前类中的每个@Test,@RequestTest,@ParameterizedTest或@TestFactory方法之前执行带注释的方法
@AfterEach同上相反
@BeforeAll被注解的(静态)方法将在当前类中的所有 @Test 方法前`执行一次。
@AfterAll同上相反
@Nested嵌套测试类,该类类是非静态嵌套测试类。 外部类中@BeforeAll,@AfterAll不会起作用。
@Tag在类或方法级别声明用于过滤测试的标记。
@Disabled被注解的方法不会执行(将被跳过),但会报告为已执行。
@Timeout执行超过给定持续时间时使其失败。
@ExtendWith用于以声明方式为测试类注册扩展
@RegisterExtension用于通过字段以编程方式注册扩展。 这些字段是可继承的。
@TempDir用于通过生命周期方法 or 字段注入 or 参数注入来提供临时目录;

编写测试

maven依赖

  <dependency>
    <groupId>org.junit.jupiter</groupId>
      <artifactId>junit-jupiter</artifactId>
      <version>5.5.1</version>
      <scope>test</scope>
  </dependency>

标准测试

public class StandardTests {
    @BeforeAll
    static void initAll() {
    }

    @BeforeEach
    void init() {
    }

    @Test
    void succeedingTest() {
    }

    @Test
    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");
    }

    @AfterEach
    void tearDown() {
    }

    @AfterAll
    static void tearDownAll() {
    }

}

Display Names

@DisplayName("A special test case")
public class DisplayNameDemo {
    @Test
    @DisplayName("Custom test name containing spaces")
    void testWithDisplayNameContainingSpaces() {
    }

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

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

执行结果:
在这里插入图片描述
Display Name Generators
自定义generator:

 class IndicativeSentences extends DisplayNameGenerator.ReplaceUnderscores {

        @Override
        public String generateDisplayNameForClass(Class<?> testClass) {
            return "顶级class:" + testClass.getSimpleName();
        }

        @Override
        public String generateDisplayNameForNestedClass(Class<?> nestedClass) {
            return "嵌套class:" + nestedClass.getSimpleName();
        }

        @Override
        public String generateDisplayNameForMethod(Class<?> testClass, Method testMethod) {
            String name = testClass.getSimpleName() + ' ' + testMethod.getName();
            return "方法method:"+name.replace('_', ' ') + '.';
        }

    }

使用generator:

@DisplayNameGeneration(IndicativeSentences.class)
public class DisplayNameGeneratorDemo {
    @Test
    void method0() {
    }

    @Nested
    @DisplayNameGeneration(IndicativeSentences.class)
    class ChildTestCase {

        @Test
        void method1() {
        }

        @Test
        void method2() {
        }
    }
}

运行结果:
在这里插入图片描述

设置默认的Display Name Generator
src/test/resources/junit-platform.properties下添加如下配置:

junit.jupiter.displayname.generator.default = IndicativeSentences

断言(Assertions)

如果断言失败,测试会在断言所在的代码行上停止,并生成断言失败报告。如果断言成功,测试会继续执行下一行代码。
工具类

class Calculator  {
    int add(int a, int b){
        return a + b;
    }

    int multiply(int a, int b){
        return a * b;
    }

    int divide(int a, int b){
        return a / b;
    }
}

@Data
@AllArgsConstructor
class Person {
    private String firstName;
    private String lastName;
}

标准断言

    @Test
   public void standardAssertions() {
        assertEquals(2, calculator.add(1, 1));
        assertEquals(4, calculator.multiply(2, 2),"不相等");

        //避免不必要地构建复杂的消息, 只有才失败时,才构建语句
        assertTrue('a' < 'b', () -> "不相等2");
    }

分组
在分组断言中,所有断言都被执行,所有断言都被执行,将一起报告失败。

  @Test
    void groupedAssertions() {
        assertAll("person",
                () -> assertEquals("Jane", person.getFirstName()),
                () -> assertEquals("Doe", person.getLastName())
        );
    }

分组嵌套
在代码块中,如果断言失败了,将跳过同一块中的后续代码。

 @Test
  void dependentAssertions() {
      assertAll("properties",
              () -> {
                  String firstName = person.getFirstName();
                  assertNotNull(firstName);

                  //只有在上面的assertNotNull() 执行通过后方可执行
                  assertAll("first name",
                          () -> assertTrue(firstName.startsWith("J")),
                          () -> assertTrue(firstName.endsWith("e"))
                  );
              },
              () -> {
                  // 分组断言,独立处理
                  // 即使上面的断言失败,此断言仍然执行
                  String lastName = person.getLastName();
                  assertNotNull(lastName);

                  //只有在上面的assertNotNull() 执行通过后方可执行
                  assertAll("last name",
                          () -> assertTrue(lastName.startsWith("D")),
                          () -> assertTrue(lastName.endsWith("e"))
                  );
              }
      );
  }

其他APIs


/**
 * 如果异常类型不匹配,则抛出异常
 * 如果异常类型匹配,则获取返回值,继续执行
 * @param expectedType   期待的异常类型
 * @param executable 执行方法
 */
public static <T extends Throwable> T assertThrows(Class<T> expectedType, Executable executable,
			Supplier<String> messageSupplier);			


/**
 * 执行超时抛出异常
 * @param timeout    超时时间
 * @param executable 无返回值的方法
 */
public static void assertTimeout(Duration timeout, Executable executable);

/**
 * 执行成功,返回返回值; 超时则抛出异常;
 * @param  timeout   超时时间
 * @param  supplier 带返回值的方法
 * @return          [description]
 */
public static <T> T assertTimeout(Duration timeout, ThrowingSupplier<T> supplier);


// executable 使用另外的线程执行
public static void assertTimeoutPreemptively(Duration timeout, Executable executable) ;

前置条件 (Assumption)

前置条件 (Assumption) 与断言类似,但前置条件必须为 true,否则测试将中止。
assumeTrue

   @Test
    void testOnlyOnCiServer() {
        assumeTrue("CI".equals(System.getenv("ENV")));

        // 只有上面的假设成立,方可执行后续的代码
    }

    @Test
    void testOnlyOnDeveloperWorkstation() {
        //假设不成立,抛出异常,并打印后续messageSupplier的异常提示信息
        assumeTrue("DEV".equals(System.getenv("ENV")),
                () -> "不是DEV环境");
    }

assumingThat
当条件满足时执行后续executable,不满足直接跳过(不抛出异常)

 @Test
  void testInAllEnvironments() {
       //assumingThat: 当条件满足时执行后续executable,不满足直接跳过(不抛出异常)
       assumingThat("CI".equals(System.getenv("ENV")),
               () -> {
                   //仅在 CI 环境运行
                   assertEquals(2, calculator.divide(4, 2));
               });
   }

Disabled

@Disabled可以使用在类上,也可以在方法上使用,强烈建议在使用@Disable时,增加一段说明文字

@Disabled("Disabled until bug #99 has been fixed")
class DisabledClassDemo {
    @Test
    void testWillBeSkipped() {
    }
}

class DisabledTestsDemo {
    @Disabled("Disabled until bug #42 has been resolved")
    @Test
    void testWillBeSkipped() {
    }
    @Test
    void testWillBeExecuted() {
    }
}

Conditional

System Conditions
@EnabledOnOs@EnabledOnOs,

 @Test
@EnabledOnOs(MAC)
  void onlyOnMacOs() {
  }


  //enable 多个参数
  @Test
  @EnabledOnOs({ LINUX, MAC })
  void onLinuxOrMac() {
  }

  //disable
  @Test
  @DisabledOnOs(WINDOWS)
  void notOnWindows() {
  }

自定义注解:

//自定义注解类型
@Target(ElementType.METHOD)
@Retention(RetentionPolicy.RUNTIME)
@Test
@EnabledOnOs(MAC)
public @interface TestOnMac {
}

 //使用自定义注解类型
 @TestOnMac
 void testOnMac() {
 }

Jre Conditions

 @Test
  @EnabledOnJre(JAVA_8)
  void onlyOnJava8() {
  }
  
  @Test
  @EnabledOnJre({ JAVA_9, JAVA_10 })
  void onJava9Or10() {
  }
  
  @Test
  @DisabledOnJre(JAVA_9)
  void notOnJava9() {
  }

SystemProperty Conditions
虚拟机的变量形,如: -Djavaxxxx。

  @Test
   @EnabledIfSystemProperty(named = "os.arch", matches = ".*64.*")
   void onlyOn64BitArchitectures() {
   }
   
   @Test
   @DisabledIfSystemProperty(named = "ci-server", matches = "true")
   void notOnCiServer() {
   }

Environment conditions
操作系统的环境变量。

 @Test
  @EnabledIfEnvironmentVariable(named = "ENV", matches = "staging-server")
   void onlyOnStagingServer() {
   }
   @Test
   @DisabledIfEnvironmentVariable(named = "ENV", matches = ".*development.*")
   void notOnDeveloperWorkstation() {
   }

script conditions(实验阶段 ,不建议使用)

 	@Test // Static JavaScript expression.
    @EnabledIf("2 * 3 == 6")
    void willBeExecuted() {
    }

    @Test
    @EnabledIf("Math.random() < 0.314159")
    void mightNotBeExecuted() {
    }
    
    @Test // Regular expression testing bound system property.
    @DisabledIf("/32/.test(systemProperty.get('os.arch'))")
    public void disabledOn32BitArchitectures() {
        assertFalse(System.getProperty("os.arch").contains("32"));
    }
    @Test
    @EnabledIf("'CI' == systemEnvironment.get('ENV')")
    void onlyOnCiServer() {
        assertTrue("CI".equals(System.getenv("ENV")));
    }

Tagging and Filtering

@Tag("fast")
@Tag("model")
public class TaggingDemo {
    @Test
    @Tag("taxes")
    void testingTaxCalculation() {
    }
    
    //自定义注解
    @Retention(RUNTIME)
    @Target({ TYPE, METHOD })
    @Tag("fast")
    static @interface Fast{
    }

    //使用自定义注解
    @Test
    @Fast
    void testingFast() {
    }
}

过滤tag — maven配置

 <build>
        <plugins>
            <plugin>
                <artifactId>maven-surefire-plugin</artifactId>
                <version>3.0.0-M3</version>

                <!-- groups: 表示需要包含的tag,或表达式
                 excludedGroups: 表示需要排除的tag,或表达式
                 -->
     			<!--          
      			<configuration>
                    <groups>acceptance | !feature-a</groups>
                    <excludedGroups>integration, regression</excludedGroups>
                </configuration>
                -->
            </plugin>
        </plugins>
    </build>

经过测试,未起作用, 待后续完善。

Test Order

排序支持三种方式:

  • OrderAnnotation: - 按照注解顺序排序
  • Alphanumeric: 按照方法名称 和 参数个数排序
  • Random: 随机排序
@TestMethodOrder(MethodOrderer.OrderAnnotation.class)
public class OrderedTestsDemo {

    @Test
    @Order(1)
    void a() {
    }

    @Test
    @Order(2)
    void c() {    }

    @Test
    @Order(3)
    void b() {    }
}

Nested Tests

package junit5;

import org.junit.jupiter.api.BeforeEach;
import org.junit.jupiter.api.DisplayName;
import org.junit.jupiter.api.Nested;
import org.junit.jupiter.api.Test;

import java.util.EmptyStackException;
import java.util.Stack;

import static org.junit.jupiter.api.Assertions.*;

/**
 * @author Trump
 * @version 1.0.0
 * @Description
 * @createTime 2019-09-04 14:31
 */
@DisplayName("class_1")
public class TestingAStackDemo {

    @BeforeEach
    @DisplayName("method_1")
    void method_1() {
        System.out.println("method_1()");
    }

    @Nested
    @DisplayName("class_1_1")
    class WhenNew {
        @BeforeEach
        @DisplayName("method_1_1")
        void method_1_1() {
            System.out.println("method_1_1()");
        }

        @Test
        @DisplayName("method_1_2")
        void method_1_2() {
            /**
             * 执行结果:
             * method_1()
             * method_1_1()
             */
        }

        @Nested
        @DisplayName("class_1_1_1")
        class AfterPushing {
             @BeforeEach
             @DisplayName("method_1_1_1")
            void method_1_1_1() {
                 System.out.println("method_1_1_1()");
            }

            @Test
            @DisplayName("method_1_1_2")
            void method_1_1_2() {
                /**
                 * 执行结果:
                 * method_1()
                 * method_1_1()
                 * method_1_1_1()
                 */
            }

        }
    }
}

@Nested 允许多层嵌套(non-static类)。 内部类测试方法执行时,会执行所有外部类({outer,outer.outer....})的@BeforeEach,@AfterEach方法。
嵌套类无法定义自身的@BeforeAll@AfterAll方法,是因为内部类是不允许定义静态方法的。

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值