51Testing软件测试论坛

标题: 基于JUnit5框架的Java单元测试流程 [打印本页]

作者: lsekfe    时间: 2020-12-7 11:20
标题: 基于JUnit5框架的Java单元测试流程
 1.原理图
[attach]131190[/attach]
2. 基本架构
  被测代码
  1. package com.jerry;

  2.   public class Calculator{

  3.       private static int result;

  4.       public void add(int n) {

  5.           result = result + n;

  6.       }

  7.       public void substract(int n) {

  8.           result = result - n;  

  9.       }

  10.       public void multiply(int n) {

  11.           result = result * n;

  12.       }

  13.       public void divide(int n){

  14.           try {

  15.               result = result / n;

  16.           }catch(ArithmeticException ex){

  17.               System.out.println(ex);

  18.               throw new ArithmeticException("The n not allowed to 0!!")

  19.           }

  20.   }

  21.   }
复制代码
最基本的测试代码
  1. public class CalculatorTest {

  2.       private static Calculator calculator = new Calculator();

  3.       @BeforeEach

  4.       public void setUp() throws Exception {

  5.           calculator.clear();

  6.       }

  7.       @Test

  8.       @DisplayName("测试加法")

  9.       public void testAdd() {

  10.           calculator.add(2);

  11.           calculator.add(3);

  12.           assertEquals(5, calculator.getResult());

  13.       }

  14.       @Test

  15.       @DisplayName("测试减法")

  16.       public void testSubstract() {

  17.           calculator.add(5);

  18.   calculator.substract(3);

  19.           Assertions.assertEquals(2, calculator.getResult());

  20.       }

  21.       @Test

  22.       @DisplayName("测试乘法")

  23.       public void testMultiply() {

  24.           calculator.add(3);

  25.           calculator.multiply(2);

  26.           Assertions.assertEquals(6, calculator.getResult());

  27.       }

  28.       @Test

  29.       @DisplayName("测试除法")

  30.       public void testDivide() {

  31.           calculator.add(9);

  32.           calculator.divide(3);

  33.           Assertions.assertEquals(3, calculator.getResult());

  34.       }
复制代码
标签@DisplayName可以在测试结果的时候显示其内容。
[attach]131191[/attach]
3.JUnit5的修饰符
[attach]131192[/attach]
描述装饰符的程序
  1.  package com.jerry;

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

  3.   import org.junit.jupiter.api.*;

  4.   public class MyFirstJunit5Test {

  5.   @BeforeAll

  6.       @DisplayName("每条用例开始时执行")

  7.   public static void startALL(){

  8.   System.out.println("BeforeAll");

  9.       }

  10.   

  11.   @AfterAll

  12.       @DisplayName("每条用例开始时执行")

  13.   public static void endAll(){

  14.   System.out.println("AfterAll");

  15.       }

  16.   

  17.   @BeforeEach

  18.       @DisplayName("每条用例开始时执行")

  19.       void start(){

  20.   System.out.println("BeforeEach");

  21.       }

  22.       @AfterEach

  23.       @DisplayName("每条用例结束时执行")

  24.       void end(){

  25.       System.out.println("AfterEach");

  26.       }

  27.       @Test

  28.       void myFirstTest() {

  29.       System.out.println("myFirstTest");

  30.       Assertions.assertEquals(2, 1 + 1);

  31.       }

  32.       @Test

  33.       @DisplayName("描述测试用例")

  34.       void testWithDisplayName() {

  35.       System.out.println("testWithDisplayName");

  36.       }

  37.       @Test

  38.       @Disabled("这条用例暂时跑不过,忽略!")

  39.       void myFailTest(){

  40.       System.out.println("Disabled Testcase");

  41.       Assertions.assertEquals(1,2);

  42.       }

  43.       @Test

  44.       @DisplayName("运行一组断言")

  45.       public void assertAllCase() {

  46.       System.out.println("运行一组断言");

  47.           assertAll("groupAssert",

  48.                   () -> Assertions.assertEquals(2, 1 + 1),

  49.                   () -> Assertions.assertTrue(1 > 0)

  50.           );

  51.       }

  52.       @Test

  53.       @DisplayName("依赖注入1")

  54.       public void testInfo(final TestInfo testInfo) {

  55.           System.out.println(testInfo.getDisplayName());

  56.       }

  57.       @Test

  58.       @DisplayName("依赖注入2")

  59.       public void testReporter(final TestReporter testReporter) {

  60.           testReporter.publishEntry("name", "Alex");

  61.       }

  62.   }
复制代码
运行结果(缩进为了看起来方便,我自己设置的)
  1.  BeforeAll

  2.        BeforeEach

  3.           依赖注入1

  4.       AfterEach

  5.       BeforeEach

  6.           TestIdentifier [依赖注入2]

  7.            ReportEntry [timestamp = 2020-08-21T11:38:49.361976500, name = 'Alex’]

  8.        AfterEach

  9.       BeforeEach

  10.           myFirstTest

  11.       AfterEach

  12.       BeforeEach

  13.            testWithDisplayName

  14.       AfterEach

  15.       BeforeEach

  16.          运行一组断言

  17.       AfterEach

  18.   AfterAll
复制代码
 注意:@BeforeAll、 @AfterALL注解方法必须是静态方法,否则会抛出运行时错误。  4. JUnit5 新加断言
[attach]131193[/attach]
5. 异常断言  被测的程序
  1. public void divide(int n){

  2.       try {

  3.       result = result / n;

  4.       }catch(ArithmeticException ex){

  5.       System.out.println(ex);

  6.       throw new ArithmeticException("The n not allowed to 0!!");

  7.       }

  8.       }
复制代码
测试程序
  1.  @Test

  2.       @DisplayName("测试除0异常")

  3.       public void testDivideByZero() {

  4.           calculator.add(9);

  5.           Throwable exception = Assertions.assertThrows(ArithmeticException.class, () -> calculator.divide(0));

  6.           Assertions.assertEquals("The n not allowed to 0!!",exception.getMessage());

  7.   }
复制代码
6. 超时断言
  1. @Test
  2.       public void squareRoot()  {

  3.          Assertions.assertTimeoutPreemptively(Duration.of(2, ChronoUnit.SECONDS), () -> calculator.squareRoot(4))

  4.   }
复制代码


判断程序是否应该在两秒钟执行完毕。类似于JUnit4中的(timeout=XXX)。  7. 参数化测试
  7.1单参数
  1.  @ParameterizedTest

  2.   @ValueSource(ints = {1,2,3,4})

  3.   @DisplayName("参数化测试_单参数")

  4.   public void parameterizedTest(int param) {

  5.       calculator.add(param);

  6.       calculator.add(-1 * param);

  7.       Assertions.assertEquals(0, calculator.getResult());

  8.   }
复制代码
标签:  ·@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参数
  1. import java.util.concurrent.TimeUnit;

  2.   …

  3.   @ParameterizedTest

  4.   @EnumSource(value = TimeUnit.class, names = {"SECONDS", "MINUTES"})

  5.   @DisplayName("参数化测试_Enum参数")

  6.   void testTimeUnitJustSecondsAndMinutes(TimeUnit unit) {

  7.   Assertions.assertTrue(EnumSet.of(TimeUnit.SECONDS, TimeUnit.MINUTES).contains(unit));

  8.   Assertions.assertFalse(EnumSet

  9.             .of(TimeUnit.DAYS, TimeUnit.HOURS, TimeUnit.MILLISECONDS, TimeUnit.NANOSECONDS,

  10.                   TimeUnit.MICROSECONDS).contains(unit));

  11.   }
复制代码
7.3参数化测试——方法参数(多参数)  注意:这个方法返回必须是个流
  1.    @ParameterizedTest

  2.       @MethodSource("paramGenerator")

  3.       @DisplayName("参数化测试_ 方法参数(多参数)")

  4.       void MethodParameForSquareRoot(int param, int result){  

  5.           calculator.squareRoot(param);

  6.           Assertions.assertEquals(result, calculator.getResult());

  7.       }

  8.   

  9.       static Stream<Arguments> paramGenerator(){

  10.           return Stream.of(Arguments.of(4,2), Arguments.of(9,3), Arguments.of(16,4));

  11.       }
复制代码
7.4.参数化测试——CVS文件参数化

  1. @ParameterizedTest

  2.       @CsvFileSource(resources = "data.csv")  //指定csv文件位置

  3.       @DisplayName("参数化测试-csv文件")

  4.       public void parameterizedCVSFile(int param, int result) {

  5.           calculator.squareRoot(param);

  6.           Assertions.assertEquals(result, calculator.getResult());

  7.       }
复制代码
其中,data.csv为:
  1. 42

  2. 93

  3. 164
复制代码
8.内嵌测试类  一般一个产品类对应一个测试类,但是使用JUnit,可以实现类的嵌套。
  1. package com.jerry;

  2.   import org.junit.jupiter.api.BeforeEach;

  3.   import org.junit.jupiter.api.DisplayName;

  4.   import org.junit.jupiter.api.Nested;

  5.   import org.junit.jupiter.api.Test;

  6.   public class NestedTestDemo {

  7.       @Test

  8.       @DisplayName("Nested")

  9.       void isInstantiatedWithNew() {

  10.           System.out.println("最一层--内嵌单元测试");

  11.       }

  12.       @Nested

  13.       @DisplayName("Nested2")

  14.       class Nested2 {

  15.           @BeforeEach

  16.           void Nested2_init() {

  17.               System.out.println("Nested2_init");

  18.           }

  19.           @Test

  20.           void Nested2_test() {

  21.               System.out.println("第二层-内嵌单元测试");

  22.           }

  23.           @Nested

  24.           @DisplayName("Nested3")

  25.           class Nested3 {

  26.               @BeforeEach

  27.               void Nested3_init() {

  28.                   System.out.println("Nested3_init");

  29.               }

  30.               @Test

  31.               void Nested3_test() {

  32.                   System.out.println("第三层-内嵌单元测试");

  33.               }

  34.           }

  35.       }

  36.   }
复制代码
结果输出:  第一层--内嵌单元测试
  Nested2_init
  第二层-内嵌单元测试
  Nested2_init
  Nested3_init
  第三层-内嵌单元测试
[attach]131194[/attach]
9. 重复测试
  1.  @RepeatedTest(5) //表示重复执行5次

  2.   @DisplayName("重复减法")

  3.   public void testSubstractManyTimes() {

  4.       calculator.add(5);

  5.       calculator.substract(3);

  6.       Assertions.assertEquals(2, calculator.getResult());

  7.   }
复制代码
这个测试用例将被执行5次,为什么设计这个方法,我个人没有理解。  10. 动态测试
  1. <strong> </strong>@TestFactory

  2.   @DisplayName("动态测试")

  3.   Iterator<DynamicTest> dynamicTests() {

  4.       return Arrays.asList(

  5.           dynamicTest("第1个动态测试", () ->{calculator.squareRoot(4);Assertions.assertEquals(2, calculator.getResult());}),

  6.           dynamicTest("第2个动态测试", () ->{calculator.squareRoot(9);Assertions.assertEquals(3, calculator.getResult());}),

  7.           dynamicTest("第3个动态测试", () ->{calculator.squareRoot(16);Assertions.assertEquals(4, calculator.getResult());}),

  8.           dynamicTest("第4个动态测试", () ->{calculator.squareRoot(25);Assertions.assertEquals(5, calculator.getResult());})

  9.       ).iterator();

  10.    }
复制代码
动态测试用得比较少,这个功能的原因我个人也不太理解。  11. 分组断言assertAll
  1.  @Test

  2.   @DisplayName("开根号分组断言")

  3.   public void testGroup() {

  4.       int[] parem = {4, 9, 16, 25, 36};

  5.       int[] result ={2, 3, 4, 5, 6};

  6.       Assertions.assertAll("parem,result",

  7.           () -> {calculator.squareRoot(parem[0]);Assertions.assertEquals(result[0], calculator.getResult());},

  8.           () -> {calculator.squareRoot(parem[1]);Assertions.assertEquals(result[1], calculator.getResult());},

  9.           () -> {calculator.squareRoot(parem[2]);Assertions.assertEquals(result[2], calculator.getResult());},

  10.           () -> {calculator.squareRoot(parem[3]);Assertions.assertEquals(result[3], calculator.getResult());},

  11.           () -> {calculator.squareRoot(parem[4]);Assertions.assertEquals(result[4], calculator.getResult());}

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
























欢迎光临 51Testing软件测试论坛 (http://bbs.51testing.com/) Powered by Discuz! X3.2