基于JUnit5框架的Java单元测试流程

发表于:2020-8-25 11:10

字体: | 上一篇 | 下一篇 | 我要投稿

 作者:顾翔    来源:51Testing软件测试网原创

  1.原理图
  2. 基本架构
  被测代码
  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!!")
          }
  }
  }
   最基本的测试代码
  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的修饰符
  描述装饰符的程序
  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");
      }
  }
  运行结果(缩进为了看起来方便,我自己设置的)
  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 新加断言
  5. 异常断言
  被测的程序
  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!!");
      }
      }
  测试程序
  @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. 超时断言
  @Test
      public void squareRoot()  {
         Assertions.assertTimeoutPreemptively(Duration.of(2, ChronoUnit.SECONDS), () -> calculator.squareRoot(4))
  }
  判断程序是否应该在两秒钟执行完毕。类似于JUnit4中的(timeout=XXX)。
  7. 参数化测试
  7.1单参数
  @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参数
  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参数化测试——方法参数(多参数)
  注意:这个方法返回必须是个流
      @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文件参数化
      @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为:
  42
  93
  164
  8.内嵌测试类
  一般一个产品类对应一个测试类,但是使用JUnit,可以实现类的嵌套。
  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("第三层-内嵌单元测试");
              }
          }
      }
  }
  结果输出:
  第一层--内嵌单元测试
  Nested2_init
  第二层-内嵌单元测试
  Nested2_init
  Nested3_init
  第三层-内嵌单元测试
  9. 重复测试
  @RepeatedTest(5) //表示重复执行5次
  @DisplayName("重复减法")
  public void testSubstractManyTimes() {
      calculator.add(5);
      calculator.substract(3);
      Assertions.assertEquals(2, calculator.getResult());
  }
  这个测试用例将被执行5次,为什么设计这个方法,我个人没有理解。
  10. 动态测试
  @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
  @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 错误进行抛出提示。另外可以看出,使用分组断言也可以实现参数化

  本文出自51Testing会员投稿,51Testing软件测试网及相关内容提供者拥有内容的全部版权,未经明确的书面许可,任何人或单位不得对本网站内容复制、转载或进行镜像,否则将追究法律责任。
《2023软件测试行业现状调查报告》独家发布~

关注51Testing

联系我们

快捷面板 站点地图 联系我们 广告服务 关于我们 站长统计 发展历程

法律顾问:上海兰迪律师事务所 项棋律师
版权所有 上海博为峰软件技术股份有限公司 Copyright©51testing.com 2003-2024
投诉及意见反馈:webmaster@51testing.com; 业务联系:service@51testing.com 021-64471599-8017

沪ICP备05003035号

沪公网安备 31010102002173号