springboot企業級項目開發之項目測試——集成測試!

集成測試

集成測試是指項目代碼在單元測試完成後進行的第二階段測試。集成測試的重點是在集成組件或單元之間交互時暴露缺陷,以保證不同模塊之間相互調用的正確性。在Spring Boot的項目集成測試中,將測試Controller和Dao的完整請求處理。應用程序在服務器中運行,以創建應用程序上下文和所有的Bean,其中有些Bean可能會被覆蓋以模擬某些行為。進行集成測試,是為瞭保證項目能夠達到下面的目的:

降低項目中發生錯誤的風險;

驗證接口的功能是否符合設計的初衷;

測試接口是否可用;

查找項目中的Bug並進行修復。

在項目開發中,需要開展集成測試的情況有以下4種:

單個模塊由開發人員設計,而多個模塊之間的設計可能不盡相同;

檢查多個模塊與數據庫是否正確連通;

驗證不同模塊之間是否存在不兼容或錯誤的情況;

驗證不同模塊之間的異常和錯誤處理。

集成測試自動配置

Spring Boot框架支持集成測試,項目開發時隻需要做簡單的配置就可以完成集成測試。本節將在7.1節的基礎上介紹項目配置後進行集成測試的過程。

首先在pom.xml中添加項目依賴:

org.springframework.boot spring-boot-starter-parent

2.3.10.RELEASE

com.example

junit5-demo

0.0.1-SNAPSHOT

junit5-demo

Demo project for Spring Boot

11

org.springframework.boot

spring-boot-starter

org.springframework.boot

spring-boot-starter-web

org.springframework.boot

spring-boot-starter-test

test

org.projectlombok

lombok

true

org.mockito

mockito-core

3.11.2

@SpringBootTest是Spring Boot提供的一個註解,表示當前類是SpringBoot環境中的一個測試類。如果使用的是JUnit 4,則需要用到@RunWith(SpringRunner.class)和@Spring-BootTest註解。但是在JUnit 5中,隻需要使用@SpringBootTest註解就可以瞭。

測試Spring MVC入口

下面使用Spring中的集成測試模塊來測試項目入口Controller的方法。

(1)新建GoodsController類,並新建要測試的業務代碼,代碼如下:

package com.example.junit5demo.controller;

import com.example.junit5demo.service.GoodsService;

import org.springframework.beans.factory.annotation.Autowired;

import org.springframework.web.bind.annotation.*;

@RestController

public class GoodsController {

@Autowired

private GoodsService goodsService;

@GetMapping("/queryGood")

public String queryGood(@RequestParam("name") String name) {

goodsService.queryGood(name);

return "queryGood " + name;

}

@PostMapping("/countGood")

public String countGood(@RequestBody Goods goods) {

goodsService.countGood(goods.getName());

return "countGood " + goods;

}

}

(2)新建Goods的實體類,代碼如下:

package com.example.junit5demo.controller;

import lombok.Data;

@Data

public class Goods {

private long id;

private String name;

private int status;

}

(3)新建Goods的service和實現類,代碼如下:

package com.example.junit5demo.service;

public interface GoodsService {

void queryGood(String name);

void countGood(String name);

}

Goods的實現類代碼如下:

package com.example.junit5demo.service;

import org.springframework.stereotype.Service;

@Service

public class GoodsServiceImpl implements GoodsService {

@Override public void queryGood(String name) {

System.out.println("執行瞭goods的queryGood方法,參數:" + name);

}

@Override

public void countGood(String name) {

System.out.println("執行瞭goods的countGood方法,參數:" + name);

}

}

(4)編寫Controller的集成測試用例,代碼如下:

package com.example.junit5demo.controller;

import lombok.extern.SLF4J.SLF4J;

import org.junit.jupiter.api.BeforeEach;

import org.junit.jupiter.api.Test;

import org.springframework.beans.factory.annotation.Autowired;

import org.springframework.boot.test.autoconfigure.web.servlet.Auto

ConfigureMockMvc;

import org.springframework.boot.test.context.SpringBootTest;

import org.springframework.http.MediaType;

import org.springframework.mock.web.MockHttpSession;

import org.springframework.test.web.servlet.MockMvc;

import org.springframework.test.web.servlet.MvcResult;

import

org.springframework.test.web.servlet.request.MockMvcRequestBuilders;

import

org.springframework.test.web.servlet.result.MockMvcResultHandlers;

import

org.springframework.test.web.servlet.result.MockMvcResultMatchers;

@Slf4j

@SpringBootTest

@AutoConfigureMockMvc

class GoodsControllerTest {

private MockHttpSession session; @Autowired

private MockMvc mvc;

@BeforeEach

public void setupMockMvc() {

//設置MVC

session = new MockHttpSession();

}

@Test

void queryGood() throws Exception {

MvcResult mvcResult = (MvcResult) mvc.perform(MockMvcRequest

Builders.get("/queryGood")

.accept(MediaType.ALL)

.session(session)

.param("name", "cc")

)

.andExpect(MockMvcResultMatchers.status().isOk())

.andDo(MockMvcResultHandlers.print())

.andReturn();

//得到返回代碼

int status = mvcResult.getResponse().getStatus();

//得到返回結果

String result = mvcResult.getResponse().getContentAsString();

log.info("status是:{},內容是:{}", status, result);

}

@Test

void countGood() throws Exception {

String body = "{"id":1,"name":"cc","status":2}";

MvcResult mvcResult = (MvcResult) mvc.perform(MockMvcRequest

Builders.post("/countGood")

.accept(MediaType.ALL)

.session(session)

.content(body)

.contentType(MediaType.APPLICATION_JSON)

)

.andExpect(MockMvcResultMatchers.status().isOk())

.andDo(MockMvcResultHandlers.print())

.andReturn(); //得到返回代碼

int status = mvcResult.getResponse().getStatus();

//得到返回結果

String result = mvcResult.getResponse().getContentAsString();

log.info("status是:{},內容是:{}", status, result);

}

}

(5)運行第一個測試用例,在控制臺上可以看到測試用例的輸出結果,它執行瞭queryGood的GET方法,實際調用瞭代碼中的方法,且返回瞭預期的結果。

執行瞭goods的queryGood方法,參數:cc

MockHttpServletRequest:

HTTP Method = GET

Request URI = /queryGood

Parameters = {name=[cc]}

Headers = [Accept:"*/*"]

Body = null

Session Attrs = {}

Handler:

Type = com.example.junit5demo.controller.GoodsController

Method = com.example.junit5demo.controller.GoodsController

#queryGood(String)

Async:

Async started = false

Async result = null

Resolved Exception:

Type = null

ModelAndView:

View name = null

View = null Model = null

FlashMap:

Attributes = null

MockHttpServletResponse:

Status = 200

Error message = null

Headers = [Content-Type:"text/plain;charset=UTF-8", Content

Length:"12"]

Content type = text/plain;charset=UTF-8

Body = queryGood cc

Forwarded URL = null

Redirected URL = null

Cookies = []

2021-07-10 15:51:30.786 INFO 15428 --- [ main]

c.e.j.controller.

GoodsControllerTest : status是:200,內容是:queryGood cc

2021-07-10 15:51:30.808 INFO 15428 --- [extShutdownHook]

o.s.s.concurrent.

ThreadPoolTaskExecutor : Shutting down ExecutorService 'application

TaskExecutor'

(6)執行第二個測試用例的方法,並執行countGood的POST方法,當前POST使用的是application/json方式,需要單獨設置,通過日志可以看到已經請求成功。

執行瞭goods的countGood方法,參數:cc

MockHttpServletRequest:

HTTP Method = POST

Request URI = /countGood

Parameters = {}

Headers = [Content-Type:"application/json;charset=UTF-8",

Accept:"*/*", Content-Length:"31"]

Body = {"id":1,"name":"cc","status":2}

Session Attrs = {}Handler:

Type = com.example.junit5demo.controller.GoodsController

Method = com.example.junit5demo.controller.GoodsController#

countGood(Goods)

Async:

Async started = false

Async result = null

Resolved Exception:

Type = null

ModelAndView:

View name = null

View = null

Model = null

FlashMap:

Attributes = null

MockHttpServletResponse:

Status = 200

Error message = null

Headers = [Content-Type:"text/plain;charset=UTF-8", Content

Length:"40"]

Content type = text/plain;charset=UTF-8

Body = countGood Goods(id=1, name=cc, status=2)

Forwarded URL = null

Redirected URL = null

Cookies = []

2021-07-10 15:54:18.096 INFO 15720 --- [ main]

c.e.j.controller.

GoodsControllerTest : status是:200,內容是:countGood Goods(id=1,

name=cc, status=2)

2021-07-10 15:54:18.119 INFO 15720 --- [extShutdownHook]

o.s.s.concurrent.

ThreadPoolTaskExecutor : Shutting down ExecutorService 'application

TaskExecutor'

通過以上的集成測試,完成瞭API從入口開始的全鏈路方法調用,驗證瞭所有的方法調用,並完成瞭業務代碼的測試,至此完成瞭Controller的集成測試過程。