前往小程序,Get更优阅读体验!
立即前往
首页
学习
活动
专区
工具
TVP
发布
社区首页 >专栏 >Java单元测试框架(二)——JUnit5

Java单元测试框架(二)——JUnit5

作者头像
顾翔
发布2020-08-28 03:04:56
9570
发布2020-08-28 03:04:56
举报

1.原理图

2. 基本架构

被测代码

代码语言:javascript
复制
package com.jerry;
public class Calculator{
    private static int result; 
    public void add(int n) {
        result = result + n;
    }
    public void substract(int n) {
        result = result - n;  
    }
    public void multiply(int n) {
        result = result * n;
    } 
    public void divide(int n){
        try {
            result = result / n;
        }catch(ArithmeticException ex){
            System.out.println(ex);
            throw new ArithmeticException("The n not allowed to 0!!")
        }
}
}

最基本的测试代码

代码语言:javascript
复制
public class CalculatorTest {
    private static Calculator calculator = new Calculator();
    @BeforeEach
    public void setUp() throws Exception {
        calculator.clear();
    }
    @Test
    @DisplayName("测试加法")
    public void testAdd() {
        calculator.add(2);
        calculator.add(3);
        assertEquals(5, calculator.getResult());
    }
    @Test
    @DisplayName("测试减法")
    public void testSubstract() {
        calculator.add(5);
calculator.substract(3);
        Assertions.assertEquals(2, calculator.getResult());
    }
    @Test
    @DisplayName("测试乘法")
    public void testMultiply() {
        calculator.add(3);
        calculator.multiply(2);
        Assertions.assertEquals(6, calculator.getResult());
    }
    @Test
    @DisplayName("测试除法")
    public void testDivide() {
        calculator.add(9);
        calculator.divide(3);
        Assertions.assertEquals(3, calculator.getResult());
    }

标签@DisplayName可以在测试结果的时候显示其内容。

3.JUnit5的修饰符

修饰符

含义

@DisplayName

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

@BeforeAll

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

@AfterAll

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

@BeforeEach

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

@AfterEach

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

@Disabled

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

@Timeout

表示测试方法运行如果超过了指定时间将会返回错误,类似于JUnit4中的(timeout=XXX) JUnit 5.5.2以后

@RepeatedTest

表示方法可重复执行

@ParameterizedTest

表示方法是参数化测试,类似于JUnit4中的@RunWith(Parameterized.class)

@Tag

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

@ExtendWith

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

描述装饰符的程序

代码语言:javascript
复制
package com.jerry;


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


public class MyFirstJunit5Test {
  @BeforeAll
    @DisplayName("每条用例开始时执行")
  public static void startALL(){
    System.out.println("BeforeAll");
    }
  
  @AfterAll
    @DisplayName("每条用例开始时执行")
  public static void endAll(){
    System.out.println("AfterAll");
    }
  
  @BeforeEach
    @DisplayName("每条用例开始时执行")
    void start(){
    System.out.println("BeforeEach");
    }


    @AfterEach
    @DisplayName("每条用例结束时执行")
    void end(){
      System.out.println("AfterEach");
    }


    @Test
    void myFirstTest() {
      System.out.println("myFirstTest");
      Assertions.assertEquals(2, 1 + 1);
    }


    @Test
    @DisplayName("描述测试用例")
    void testWithDisplayName() {
      System.out.println("testWithDisplayName");
    }


    @Test
    @Disabled("这条用例暂时跑不过,忽略!")
    void myFailTest(){
      System.out.println("Disabled Testcase");
      Assertions.assertEquals(1,2);
    }


    @Test
    @DisplayName("运行一组断言")
    public void assertAllCase() {
      System.out.println("运行一组断言");
        assertAll("groupAssert",
                () -> Assertions.assertEquals(2, 1 + 1),
                () -> Assertions.assertTrue(1 > 0)
        );
    }


    @Test
    @DisplayName("依赖注入1")
    public void testInfo(final TestInfo testInfo) {
        System.out.println(testInfo.getDisplayName());
    }


    @Test
    @DisplayName("依赖注入2")
    public void testReporter(final TestReporter testReporter) {
        testReporter.publishEntry("name", "Alex");
    }
}

运行结果(缩进为了看起来方便,我自己设置的)

代码语言:javascript
复制
BeforeAll
     BeforeEach
        依赖注入1
    AfterEach
    BeforeEach
        TestIdentifier [依赖注入2]
         ReportEntry [timestamp = 2020-08-21T11:38:49.361976500, name = 'Alex’]
     AfterEach
    BeforeEach
        myFirstTest
    AfterEach
    BeforeEach
         testWithDisplayName
    AfterEach
    BeforeEach
       运行一组断言
    AfterEach
AfterAll

注意:@BeforeAll、 @AfterALL注解方法必须是静态方法,否则会抛出运行时错误。

4. JUnit5 新加断言

断言方法

断言描述

assertTimeoutPreemptively

超时断言

assertThrows

异常断言

5. 异常断言

被测的程序

代码语言:javascript
复制
public void divide(int n){
      try {
        result = result / n;
      }catch(ArithmeticException ex){
        System.out.println(ex);
        throw new ArithmeticException("The n not allowed to 0!!");
      }
    }

测试程序

代码语言:javascript
复制
@Test
    @DisplayName("测试除0异常")
    public void testDivideByZero() {
        calculator.add(9);
        Throwable exception = Assertions.assertThrows(ArithmeticException.class, () -> calculator.divide(0));
        Assertions.assertEquals("The n not allowed to 0!!",exception.getMessage());
  }

6. 超时断言

代码语言:javascript
复制
@Test
    public void squareRoot()  {
       Assertions.assertTimeoutPreemptively(Duration.of(2, ChronoUnit.SECONDS), () -> calculator.squareRoot(4))
}

判断程序是否应该在两秒钟执行完毕。类似于JUnit4中的(timeout=XXX)。

7. 参数化测试

7.1单参数

代码语言:javascript
复制
@ParameterizedTest
@ValueSource(ints = {1,2,3,4})
@DisplayName("参数化测试_单参数")
public void parameterizedTest(int param) {
    calculator.add(param);
    calculator.add(-1 * param);
    Assertions.assertEquals(0, calculator.getResult());
}

标签:

  • @ValueSource: 为参数化测试指定入参来源,支持八大基础类以及String类型,Class类型
  • @NullSource: 表示为参数化测试提供一个null的入参
  • @EnumSource: 表示为参数化测试提供一个枚举入参

ValueSource类型:

  • String values: @ValueSource(strings = {"foo", "bar", "baz"})
  • Double values: @ValueSource(doubles = {1.5D, 2.2D, 3.0D})
  • Long values: @ValueSource(longs = {2L, 4L, 8L})
  • Integer values: @ValueSource(ints = {2, 4, 8})

7.2 Enum参数

代码语言:javascript
复制
import java.util.concurrent.TimeUnit;
…


@ParameterizedTest
@EnumSource(value = TimeUnit.class, names = {"SECONDS", "MINUTES"})
@DisplayName("参数化测试_Enum参数")
void testTimeUnitJustSecondsAndMinutes(TimeUnit unit) {
Assertions.assertTrue(EnumSet.of(TimeUnit.SECONDS, TimeUnit.MINUTES).contains(unit));
Assertions.assertFalse(EnumSet
          .of(TimeUnit.DAYS, TimeUnit.HOURS, TimeUnit.MILLISECONDS, TimeUnit.NANOSECONDS,
                TimeUnit.MICROSECONDS).contains(unit));
}

7.3参数化测试——方法参数(多参数)

代码语言:javascript
复制
注意:这个方法返回必须是个流
    @ParameterizedTest
    @MethodSource("paramGenerator")
    @DisplayName("参数化测试_ 方法参数(多参数)")
    void MethodParameForSquareRoot(int param, int result){    
        calculator.squareRoot(param);
        Assertions.assertEquals(result, calculator.getResult());
    }
  
    static Stream<Arguments> paramGenerator(){
        return Stream.of(Arguments.of(4,2), Arguments.of(9,3), Arguments.of(16,4));
    }

7.4.参数化测试——CVS文件参数

代码语言:javascript
复制
@ParameterizedTest
    @CsvFileSource(resources = "data.csv")  //指定csv文件位置
    @DisplayName("参数化测试-csv文件")
    public void parameterizedCVSFile(int param, int result) {
        calculator.squareRoot(param);
        Assertions.assertEquals(result, calculator.getResult());
    }

其中,data.csv为:

代码语言:javascript
复制
4  2
9  3
16  4

8.内嵌测试类

一般一个产品类对应一个测试类,但是使用JUnit,可以实现类的嵌套。

代码语言:javascript
复制
package com.jerry;


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


public class NestedTestDemo {


    @Test
    @DisplayName("Nested")
    void isInstantiatedWithNew() {
        System.out.println("最一层--内嵌单元测试");
    }


    @Nested
    @DisplayName("Nested2")
    class Nested2 {


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


        @Test
        void Nested2_test() {
            System.out.println("第二层-内嵌单元测试");
        }




        @Nested
        @DisplayName("Nested3")
        class Nested3 {


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


            @Test
            void Nested3_test() {
                System.out.println("第三层-内嵌单元测试");
            }
        }
    }


}

结果输出:

代码语言:javascript
复制
第一层--内嵌单元测试
Nested2_init
第二层-内嵌单元测试
Nested2_init
Nested3_init
第三层-内嵌单元测试

9. 重复测试

代码语言:javascript
复制
@RepeatedTest(5) //表示重复执行5次
@DisplayName("重复减法")
public void testSubstractManyTimes() {
    calculator.add(5);
    calculator.substract(3);
    Assertions.assertEquals(2, calculator.getResult());
}

这个测试用例将被执行5次,为什么设计这个方法,我个人没有理解。

10. 动态测试

代码语言:javascript
复制
@TestFactory
@DisplayName("动态测试")
Iterator<DynamicTest> dynamicTests() {
    return Arrays.asList(
        dynamicTest("第1个动态测试", () ->{calculator.squareRoot(4);Assertions.assertEquals(2, calculator.getResult());}),
        dynamicTest("第2个动态测试", () ->{calculator.squareRoot(9);Assertions.assertEquals(3, calculator.getResult());}),
        dynamicTest("第3个动态测试", () ->{calculator.squareRoot(16);Assertions.assertEquals(4, calculator.getResult());}),
        dynamicTest("第4个动态测试", () ->{calculator.squareRoot(25);Assertions.assertEquals(5, calculator.getResult());})
    ).iterator();
 }

动态测试用得比较少,这个功能的原因我个人也不太理解。

11分组断言assertAll

代码语言:javascript
复制
@Test
@DisplayName("开根号分组断言")
public void testGroup() {
    int[] parem = {4, 9, 16, 25, 36};
    int[] result ={2, 3, 4, 5, 6};
    Assertions.assertAll("parem,result",
        () -> {calculator.squareRoot(parem[0]);Assertions.assertEquals(result[0], calculator.getResult());},
        () -> {calculator.squareRoot(parem[1]);Assertions.assertEquals(result[1], calculator.getResult());},
        () -> {calculator.squareRoot(parem[2]);Assertions.assertEquals(result[2], calculator.getResult());},
        () -> {calculator.squareRoot(parem[3]);Assertions.assertEquals(result[3], calculator.getResult());},
        () -> {calculator.squareRoot(parem[4]);Assertions.assertEquals(result[4], calculator.getResult());}
   );

分组断言中任一个断言的失败,都会将以 MultipleFailuresError 错误进行抛出提示。另外可以看出,使用分组断言也可以实现参数化

本文参与 腾讯云自媒体分享计划,分享自微信公众号。
原始发表:2020-08-22,如有侵权请联系 cloudcommunity@tencent.com 删除

本文分享自 软件测试培训 微信公众号,前往查看

如有侵权,请联系 cloudcommunity@tencent.com 删除。

本文参与 腾讯云自媒体分享计划  ,欢迎热爱写作的你一起参与!

评论
登录后参与评论
0 条评论
热度
最新
推荐阅读
领券
问题归档专栏文章快讯文章归档关键词归档开发者手册归档开发者手册 Section 归档