SpringBoot项目中单元测试与集成测试的应用

测试分类

单元测试->集成测试->系统测试->验收测试

  1. 单元测试

    单元测试(unit testing),是指对软件中的最小可测试单元进行检查和验证。一个单元可能是单个程序、类、对象、方法等。

    测试阶段:编码后或编码前(测试驱动开发TDD)

    测试对象:最小模块(Java-类)

    测试内容:模块接口测试、局部数据结构、路径测试、错误处理测试、边界测试

    模块接口测试:对通过被测模块的数据流进行测试。包括参数表、调用子模块的参数、全程数据、文件输入/输出操作等

    局部数据结构:数据类型说明、初始化、缺省值、上溢下溢的地址等

    路径测试:对每一条独立执行路径测试,保证每条语句至少执行一次

    错误处理测试:检查模块的错误处理功能是否包含有错误或缺陷。是否拒绝不合理的输入、输出的报错信息是否难以理解、是否对错误定位有误、 是否出错原因有误、报错代码处理执行之前,是否已引起系统报错

    测试人员:开发人员

    测试方法:白盒测试(基本路径测试、语句覆盖、条件覆盖、判定覆盖、判定条件覆盖、条件组合覆盖、路径覆盖等)

    单测目的:

    • 提高代码质量。之前在做测试时候,在项目启动后,通过Swagger或Postman访问接口进行测试,修改代码之后每次都要重新启动,耗时较长

    • 方便重构。重构之后如果不确定代码的正确性,可以走一遍单元测试做参考

    • 开发阶段排除Bug的例子:调度编辑功能,在ApplicationService层实现逻辑是参数校验、调度删除、调度新增;其中,在新增时候也有做校验,且校验失败时候直接抛出异常;导致我在新增正常调度成功后,编辑错误数据,删除成功了,但重新添加时候失败;详细测试数据见Spring Boot项目单元测试及集成测试的应用单元的2.2 Application-Service层 示例

  2. 集成测试

    集成测试是在单元测试的基础上,将所有已通过单元测试的模块按照概要设计的要求组装为子系统或系统,并进行测试的过程。目标是把通过了单元测试的模块拿来,构造一个在设计中所描述的程序结构,应当避免一次性的集成(除非软件规模很小),而采用增量集成。

    测试阶段:单元测试通过后

    测试对象:模块间的接口

    测试内容:模块之间数据传输、模块之间功能冲突、模块组装功能正确性、全局数据结构、单模块缺陷对系统的影响

    测试人员:开发人员

    测试方法:白盒测试+黑盒测试

  3. 系统测试

    测试阶段:集成测试通过后

    测试对象:整个系统

    测试内容:功能、界面、可靠性、易用性、性能、兼容性、安全性等

    测试人员:测试人员

    测试方法:黑盒测试

  4. 验收测试

    验收测试是部署软件之前的最后一个测试操作。它是技术测试的最后一个阶段,也称为交付测试。验收测试的目的是确保软件准备就绪,按照项目合同、任务书、双方约定的验收依据文档,向软件购买都展示该软件系统满足原始需求。

    测试阶段:系统测试通过后

    测试对象:整个系统

    测试内容:同系统测试

    测试人员:需求方或最终用户

    测试方法:黑盒测试

    测试阶段对比图如下:

    集成测试 单元测试 系统测试 验收测试
    测试阶段 单元测试通过后 编码后或编码前 集成测试通过后 系统测试通过后
    测试对象 模块间的接口 最小模块 整个系统 整个系统
    测试内容 模块之间数据传输、模块之间功能冲突、 模块组装功能正确性、全局数据结构、单模块缺陷对系统的影响 模块接口测试、局部数据结构、路径测试、错误处理测试、边界测试 功能、界面、可靠性、易用性、性能、兼容性、安全性等 同系统测试
    测试人员 开发人员 开发人员 测试人员 需求方或最终用户
    测试方法 白盒测试+黑盒测试 白盒测试 黑盒测试 黑盒测试

单元测试

  1. Junit

    手册:JUnit API

    常用注解:

    @BeforeClass: 在所有测试方法执行前执行一次,一般在其中写上整体初始化的代码

    public class Example {
        @BeforeClass 
        public static void onlyOnce() {
           ...
        }
        @Test 
        public void one() {
           ...
        }
        @Test 
        public void two() {
           ...
        }
     }

    @Before:在每个方法测试前执行,一般用来初始化方法

     public class Example {
        List empty;
        @Before 
        public void initialize() {
           empty= new ArrayList();
        }
        @Test 
        public void size() {
           ...
        }
        @Test
        public void remove() {
           ...
        }
     }

    @AfterClass:在所有测试方法后执行一次,一般在其中写上销毁和释放资源的代码

    public class Example {
        private static DatabaseConnection database;
        @BeforeClass 
        public static void login() {
              database= ...;
        }
        @Test 
        public void something() {
              ...
        }
        @Test 
        public void somethingElse() {
              ...
        }
        @AfterClass 
        public static void logout() {
              database.logout();
        }
     }

    @After:在每个测试方法执行后,在方法执行完成后要做的事情

    public class Example {
        File output;
        @Before 
        public void createOutputFile() {
              output= new File(...);
        }
        @Test 
        public void something() {
              ...
        }
        @After 
        public void deleteOutputFile() {
              output.delete();
        }
     }

    @Test:编写一般测试用例。支持两个可选参数,用于异常测试和限制测试执行时间

    public class Example {
        @Test
        public void method() {
           org.junit.Assert.assertTrue( new ArrayList().isEmpty() );
        }
     }

    @Test(expected = Exception.class) :测试方法期望得到的异常类,如果方法执行没有抛出指定的异常,则测试失败

        @Test(expected=IndexOutOfBoundsException.class)
        public void outOfBounds() {
            new ArrayList().get(1);
        } 

    @Test(timeout = m):测试方法花费的时间超过指定的时间m毫秒后,将测试失败

        @Test(timeout=100)
        public void infinity() {
            while(true);
        }

    @Ignore("not ready yet"):执行测试时将忽略掉此方法,如果用于修饰类,则忽略整个类

     @Ignore 
     public class IgnoreMe {
         @Test 
         public void test1() { ... }
         @Test 
         public void test2() { ... }
     }
  2. Mockito

    Mock测试即在测试过程中,对于一些不容易构造的,与本次单元测试关系不大但又有上下文依赖关系的对象,用一个虚拟的对象(Mock对象)来模拟,以便单元测试能够进行。Mockito是Java单元测试的mock开源框架。

    手册:mockito-core 3.11.2 javadoc (org.mockito)

    常用方法:

    mock对象

    @BeforeEach
    void init() {
        scheduler = mock(Scheduler.class);
        scheduleService = new ScheduleServiceImpl(scheduler);
    }

    参数匹配

    anyInt()、anyLong()、anyFloat()、anyDouble(),匹配任意xx类型数据

    @Test
    void testMoc(){
        ArrayList test = mock(ArrayList.class);
        when(test.get(anyInt())).thenReturn("mock argumentMatcher");
        System.out.println(test.get(0));
    }

    设置对象调用的预期返回值

    when(condition).thenReturn(value1); 当满足when中条件时候,返回value1;当不满足时候,返回null

    when(condition).thenReturn(value1).thenReturn(value2); 当满足when中条件时候,第一次返回value1,第n(n>1且∈N*)次返回value2

    when(condition).thenReturn(value1)...thenReturn(valuen); 当满足when中条件时候,第一次返回value1,第二次返回value2...第n(n>1且∈N*)次返回valuen

    doReturn().when().someMethod(); 与thenReturn()的区别是不会调用真实方法

    示例如下:

    当满足when中条件时候,返回value1:

    @Test
    void mockitoMethodWhenThen(){
        ArrayList mockList = mock(ArrayList.class);
        //参数匹配(when中条件不满足时候,renturn null)
        when(mockList.get(anyInt())).thenReturn("mock argumentMatcher");
        System.out.println(mockList.get(0));
    }

    当不满足条件时候,返回null:

    @Test
    void mockitoMethodWhenThen(){
        ArrayList mockList = mock(ArrayList.class);
        //参数匹配(when中条件不满足时候,renturn null)
        when(mockList.get(1)).thenReturn("mock argumentMatcher");
        System.out.println(mockList.get(0));
    }

    SpringBoot项目中单元测试与集成测试的应用_第1张图片

    当满足when中条件时候,第一次返回value1,第n次(n>1且∈N*)返回value2:

    @Test
        void mockitoMethodWhenThen(){
            ArrayList mockList = mock(ArrayList.class);
            //参数匹配(when中条件不满足时候,renturn null)
            when(mockList.get(anyInt())).thenReturn("First trigger").thenReturn("Second trigger").thenReturn("Third trigger");
            System.out.println(mockList.get(0));
            System.out.println(mockList.get(1));
            System.out.println(mockList.get(2));
            System.out.println(mockList.get(2));
            System.out.println(mockList.get(3));
        }

    SpringBoot项目中单元测试与集成测试的应用_第2张图片

    @Test
    void mockitoMethodDoReturn() {
        ArrayList mockList = mock(ArrayList.class);
        mockList.add(1);
        doReturn(mockList.add(2)).when(mockList).add(3);
        mockList.add(3);
        assertThat(mockList.get(2)).isEqualTo(2);
    }

    SpringBoot项目中单元测试与集成测试的应用_第3张图片

    API如下所示:

    /**
     * Enables stubbing methods. Use it when you want the mock to return particular value when particular method is called.
     * 

    * Simply put: "When the x method is called then return y". * *

    * Examples: * *

    
     * when(mock.someMethod()).thenReturn(10);
     *
     * //you can use flexible argument matchers, e.g:
     * when(mock.someMethod(anyString())).thenReturn(10);
     *
     * //setting exception to be thrown:
     * when(mock.someMethod("some arg")).thenThrow(new RuntimeException());
     *
     * //you can set different behavior for consecutive method calls.
     * //Last stubbing (e.g: thenReturn("foo")) determines the behavior of further consecutive calls.
     * when(mock.someMethod("some arg"))
     *  .thenThrow(new RuntimeException())
     *  .thenReturn("foo");
     *
     * //Alternative, shorter version for consecutive stubbing:
     * when(mock.someMethod("some arg"))
     *  .thenReturn("one", "two");
     * //is the same as:
     * when(mock.someMethod("some arg"))
     *  .thenReturn("one")
     *  .thenReturn("two");
     *
     * //shorter version for consecutive method calls throwing exceptions:
     * when(mock.someMethod("some arg"))
     *  .thenThrow(new RuntimeException(), new NullPointerException();
     *
     * 
    * * For stubbing void methods with throwables see: {@link Mockito#doThrow(Throwable...)} *

    * Stubbing can be overridden: for example common stubbing can go to fixture * setup but the test methods can override it. * Please note that overridding stubbing is a potential code smell that points out too much stubbing. *

    * Once stubbed, the method will always return stubbed value regardless * of how many times it is called. *

    * Last stubbing is more important - when you stubbed the same method with * the same arguments many times. *

    * Although it is possible to verify a stubbed invocation, usually it's just redundant. * Let's say you've stubbed foo.bar(). * If your code cares what foo.bar() returns then something else breaks(often before even verify() gets executed). * If your code doesn't care what get(0) returns then it should not be stubbed. * *

    * See examples in javadoc for {@link Mockito} class * @param methodCall method to be stubbed * @return OngoingStubbing object used to stub fluently. * Do not create a reference to this returned object. */ @CheckReturnValue public static OngoingStubbing when(T methodCall) { return MOCKITO_CORE.when(methodCall); }

    /**
     * Sets a return value to be returned when the method is called. E.g:
     * 
    
     * when(mock.someMethod()).thenReturn(10);
     * 
    * * See examples in javadoc for {@link Mockito#when} * * @param value return value * * @return object that allows stubbing consecutive calls */ OngoingStubbing thenReturn(T value);
    /**
     * Sets consecutive return values to be returned when the method is called. E.g:
     * 
    
     * when(mock.someMethod()).thenReturn(1, 2, 3);
     * 
    * * Last return value in the sequence (in example: 3) determines the behavior of further consecutive calls. *

    * See examples in javadoc for {@link Mockito#when} * * @param value first return value * @param values next return values * * @return object that allows stubbing consecutive calls */ // Additional method helps users of JDK7+ to hide heap pollution / unchecked generics array // creation warnings (on call site) @SuppressWarnings({"unchecked", "varargs"}) OngoingStubbing thenReturn(T value, T... values);

    验证调用次数

    verify(T mock);

    verify(T mock, VerificationMode mode);

    @Test
    void mockitoMethodVerify(){
        ArrayList mockList = mock(ArrayList.class);
        //验证方法是否被调用及执行次数
        mockList.add(1);
        verify(mockList).add(1);
        mockList.add(1);
        mockList.add(2);
        verify(mockList,times(2)).add(1);
        verify(mockList,never()).add(3);
        verify(mockList,atLeastOnce()).add(1);
        verify(mockList,atLeast(2)).add(1);
        verify(mockList,atMostOnce()).add(2);
        verify(mockList,atMost(2)).add(1);
    }

    SpringBoot项目中单元测试与集成测试的应用_第4张图片

    @Test
        void mockitoMethodVerify(){
            ArrayList mockList = mock(ArrayList.class);
            //验证方法是否被调用及执行次数
            mockList.add(1);
            mockList.add(1);
            verify(mockList,atLeast(3)).add(1);
        }

    SpringBoot项目中单元测试与集成测试的应用_第5张图片

    API如下所示:

    /**
     * Verifies certain behavior happened once.
     * 

    * Alias to verify(mock, times(1)) E.g: *

    
     *   verify(mock).someMethod("some arg");
     * 
    * Above is equivalent to: *
    
     *   verify(mock, times(1)).someMethod("some arg");
     * 
    *

    * Arguments passed are compared using equals() method. * Read about {@link ArgumentCaptor} or {@link ArgumentMatcher} to find out other ways of matching / asserting arguments passed. *

    * Although it is possible to verify a stubbed invocation, usually it's just redundant. * Let's say you've stubbed foo.bar(). * If your code cares what foo.bar() returns then something else breaks(often before even verify() gets executed). * If your code doesn't care what foo.bar() returns then it should not be stubbed. * *

    * See examples in javadoc for {@link Mockito} class * * @param mock to be verified * @return mock object itself */ @CheckReturnValue public static T verify(T mock) { return MOCKITO_CORE.verify(mock, times(1)); }

    /**
     * Verifies certain behavior happened at least once / exact number of times / never. E.g:
     * 
    
     *   verify(mock, times(5)).someMethod("was called five times");
     *
     *   verify(mock, atLeast(2)).someMethod("was called at least two times");
     *
     *   //you can use flexible argument matchers, e.g:
     *   verify(mock, atLeastOnce()).someMethod(anyString());
     * 
    * * times(1) is the default and can be omitted *

    * Arguments passed are compared using equals() method. * Read about {@link ArgumentCaptor} or {@link ArgumentMatcher} to find out other ways of matching / asserting arguments passed. *

    * * @param mock to be verified * @param mode times(x), atLeastOnce() or never() * * @return mock object itself */ @CheckReturnValue public static T verify(T mock, VerificationMode mode) { return MOCKITO_CORE.verify(mock, mode); }

    /**
     * Allows verifying exact number of invocations. E.g:
     * 
    
     *   verify(mock, times(2)).someMethod("some arg");
     * 
    * * See examples in javadoc for {@link Mockito} class * * @param wantedNumberOfInvocations wanted number of invocations * * @return verification mode */ @CheckReturnValue public static VerificationMode times(int wantedNumberOfInvocations) { return VerificationModeFactory.times(wantedNumberOfInvocations); }
    /**
     * Alias to times(0), see {@link Mockito#times(int)}
     * 

    * Verifies that interaction did not happen. E.g: *

    
     *   verify(mock, never()).someMethod();
     * 
    * *

    * If you want to verify there were NO interactions with the mock * check out {@link Mockito#verifyZeroInteractions(Object...)} * or {@link Mockito#verifyNoMoreInteractions(Object...)} *

    * See examples in javadoc for {@link Mockito} class * * @return verification mode */ @CheckReturnValue public static VerificationMode never() { return times(0); }

    /**
     * Allows at-least-once verification. E.g:
     * 
    
     *   verify(mock, atLeastOnce()).someMethod("some arg");
     * 
    * Alias to atLeast(1). *

    * See examples in javadoc for {@link Mockito} class * * @return verification mode */ @CheckReturnValue public static VerificationMode atLeastOnce() { return VerificationModeFactory.atLeastOnce(); }

    /**
     * Allows at-least-x verification. E.g:
     * 
    
     *   verify(mock, atLeast(3)).someMethod("some arg");
     * 
    * * See examples in javadoc for {@link Mockito} class * * @param minNumberOfInvocations minimum number of invocations * * @return verification mode */ @CheckReturnValue public static VerificationMode atLeast(int minNumberOfInvocations) { return VerificationModeFactory.atLeast(minNumberOfInvocations); }
    /**
     * Allows at-most-once verification. E.g:
     * 
    
     *   verify(mock, atMostOnce()).someMethod("some arg");
     * 
    * Alias to atMost(1). *

    * See examples in javadoc for {@link Mockito} class * * @return verification mode */ @CheckReturnValue public static VerificationMode atMostOnce() { return VerificationModeFactory.atMostOnce(); }

    /**
     * Allows at-most-x verification. E.g:
     * 
    
     *   verify(mock, atMost(3)).someMethod("some arg");
     * 
    * * See examples in javadoc for {@link Mockito} class * * @param maxNumberOfInvocations max number of invocations * * @return verification mode */ @CheckReturnValue public static VerificationMode atMost(int maxNumberOfInvocations) { return VerificationModeFactory.atMost(maxNumberOfInvocations); }

    验证失败对应的异常:

    SpringBoot项目中单元测试与集成测试的应用_第6张图片

    异常抛出

    when() ...thenThrow(); 当满足条件时候,抛出异常

    doThrow()...when(); 满足when中条件时候,抛出异常

    当when条件中函数返回值为void时候,不可用thenThrow(),用doThrow()

    @Test
    void mockitoMethodException() {
        ArrayList mockList = mock(ArrayList.class);
        mockList.add(1);
        when(mockList.add(2)).thenThrow(new Exception("异常抛出!"));
        doThrow(new Exception("异常抛出!")).when(mockList).clear();
    }

    返回回调接口生成期望值

    when(methodCall).thenAnswer(answer));

    doAnswer(answer).when(methodCall).[method];

    调用真实的方法

    doCallRealMethod().when(mock).[method];

  3. AssertJ

    AssertJ: JAVA 流式断言器,常见的断言器一条断言语句只能对实际值断言一个校验点,而流式断言器,支持一条断言语句对实际值同时断言多个校验点。是用来验证输出和期望是否一致的一个工具。

    手册:assertj-core 3.20.2 javadoc (org.assertj)

    常用方法:

    1. as(): 添加错误提示信息

    assertThat("abc").as("校验abc").isEqualTo("abcd");
    1. isEqualsTo():相等

    2. contains():包含

    assertThat("abc").as("校验abc").isEqualTo("abc").contains("d");
    1. isNull() / isNotNull():为空判断

    Object object = null;
    assertThat(object).isNotNull();
    assertThat(object).isNull();
    1. isIn() / isNotIn():范围判断

    List list = new ArrayList();
    assertThat(list).isIn(new ArrayList(), new HashMap());
    assertThat(list).isNotIn(new ArrayList(), new HashMap());
    1. hasSize():大小校验

    List list = new ArrayList();
    assertThat(list).hasSize(1);
    1. startsWith() / endsWith()等等...可参考API

  4. 集成测试

    Spring Boot项目单元测试及集成测试的应用

    1. 搭建测试环境

      SpringBoot项目中单元测试与集成测试的应用_第7张图片

      Junit:Java应用程序单元测试标准类库

      Spring Test & Spring Boot Test:Spring Boot 应用程序功能集成化测试支持

      AssertJ:一个轻量级断言类库

      Hamcrest:一个对象匹配器类库,用Junit自带的即可

      Mockito: 一个java Mock测试框架

      JSONassert:一个用于JSON的断言库

      JsonPath: 一个Json操作类库

      手册

      Junit:JUnit API

      AssertJ:assertj-core 3.20.2 javadoc (org.assertj)

      Mockito:mockito-core 3.11.2 javadoc (org.mockito)

      Spring Boot Test:Spring Boot Reference Documentation

    2. 集成测试demo

      2.1 Endpoint层

      @SpringBootTest(classes = ScheduleSetup.class)
      @AutoConfigureMockMvc
      class ScheduleEndpointTest {
      
          @Autowired
          private MockMvc mockMvc;
      
          private ScheduleService scheduleService;
      
          @BeforeEach
          void initScheduleService() {
              scheduleService = mock(ScheduleServiceImpl.class);
          }
      
          @Test
          void add_schedule() {
              try {
                  System.out.println("scheduleService>>>" + scheduleService);
                  //构造请求数据
                  Map startPolicyParams = new HashMap<>();
                  ScheduleDTO scheduleDTO = ScheduleDTO.builder().name("单元测试-1").owner("lijialin").severity(Severity.HIGH)
                          .description("hah").startPolicyType(StartPolicyType.ONCE).startPolicyParams(null)
                          .startTime(System.currentTimeMillis()).paramData(null).createBy("lijialin")
                          .build();
                  //模拟post请求
                  mockMvc.perform(MockMvcRequestBuilders.post("/v1/soar-common/schedules")
                          .content(JsonUtil.toJson(scheduleDTO))
                          .contentType(MediaType.APPLICATION_JSON))
                  /*.andExpect(MockMvcResultMatchers.status().isOk())*/;
                  assertThat(scheduleService.existsByName("单元测试-1"));
              } catch (Exception e) {
                  e.printStackTrace();
              }
          }

      2.2 Application-Service层

      class ScheduleApplicationServiceTest {
      
          private ScheduleApplicationService applicationService;
      
          private ScheduleService scheduleService;
      
          @BeforeEach
          void init() {
              applicationService = new ScheduleApplicationService(mock(ScheduleServiceImpl.class), mock(ScheduleRecordServiceImpl.class));
              scheduleService = mock(ScheduleServiceImpl.class);
          }
      
          @Test
          void edit() {
              Map paramsMap = new HashMap<>();
              paramsMap.put("week", 6);
              paramsMap.put("hour", 8);
              paramsMap.put("minute", 12);
              paramsMap.put("interval", 1);
              ScheduleDTO scheduleDTO = ScheduleDTO.builder().name("新增调度1")
                      .createBy("lijialin")
                      .severity(Severity.HIGH)
                      .startTime(1646387494000L)
                      .startPolicyType(StartPolicyType.WEEKLY)
                      .endPolicyType(EndPolicyType.END_TIME)
                      .endPolicyValue(1646964300000L)
                      .startPolicyParams(paramsMap)
                      .build();
              applicationService.save(scheduleDTO);
              assertThat(scheduleService.existsByName("新增调度1"));
              ScheduleEditDTO scheduleEditDTO = ScheduleEditDTO.builder()
                      .id("37f62f73-65fc-4c00-89ff-eff295f784bf")
                      .name("编辑调度1")
                      .createBy("lijialin")
                      .severity(Severity.HIGH)
                      .startTime(1646387494000L)
                      .startPolicyType(StartPolicyType.WEEKLY)
                      .endPolicyType(EndPolicyType.END_TIME)
                      .endPolicyValue(1646784300000L)
                      .startPolicyParams(paramsMap)
                      .build();
              applicationService.edit(scheduleEditDTO);
              assertThat(scheduleService.existsByName("编辑调度1"));
          }
      }

      2.3 Domain-Service层

      @Slf4j
      class ScheduleServiceImplTest {
      
          private ScheduleService scheduleService;
      
          private Scheduler scheduler;
      
          @BeforeEach
          void init() {
              scheduler = mock(Scheduler.class);
              scheduleService = new ScheduleServiceImpl(scheduler);
          }
      
          @Test
          void addSchedule() {
              Map cornParams = new HashMap<>();
              cornParams.put("type", "ONCE");
              String cornParamsStr = JsonUtil.toJson(cornParams);
              ScheduleObject scheduleObject = ScheduleObject.builder().uuid("72bc5a2d-e5ee-4e83-9866-32b0df54bdb2")
                      .name("单元测试-6")
                      .creator("lijialin")
                      .owner("lijialin")
                      .type("ONCE")
                      .severity(3)
                      .description("desc...")
                      .startTime(System.currentTimeMillis())
                      .cornParams(cornParamsStr)
                      .build();
              scheduleService.addSchedule(scheduleObject);
              assertThat(scheduleService.existsByName("单元测试-6"));
          }
      

    你可能感兴趣的:(#,单元测试,单元测试,postman,测试工具)