1. 程式人生 > >基於SpringBoot框架的單元測試和整合測試的區別和聯絡

基於SpringBoot框架的單元測試和整合測試的區別和聯絡

1、單元測試和整合測試的區別:

  • Web整合測試:在嵌入式的Servlet容器(Tomcat,Jetty)裡啟動應用程式,在真正的應用伺服器裡進行測試。
  • Spring Mock MVC :能在一個接近真實的模擬Servlet容器裡啟動應用程式,而不用實際啟動應用伺服器,相當於單元測試。

2、可以採用Spring MVC進行單元測試,包括:

  • standaloneSetup():手工建立並配置的控制器
  • webAppContextSetup():使用應用程式上下文來構建MockMVC
    前者更加接近單元測試,專注於單一控制器的測試,後者更加接近整合測試,讓Spring載入控制器以及依賴。

3、常見的單元測試的例子:

  • 單元測試程式碼
/**
 * Created by wangxx-j on 2017/12/6.
 */
@RunWith(SpringJUnit4ClassRunner.class)
@WebAppConfiguration
@FixMethodOrder(MethodSorters.NAME_ASCENDING)
@SpringBootTest(classes = {ProjectConfigApplication.class},
        properties = {"EUREKA_DEFAULT_ZONE=http://192.168.xxx.xxx:10000/eureka/"
}) @ActiveProfiles("unit-test") public class ProjectConfigDictControllerTest extends ControllerTestSupport{ @Test public void testQueryDefaultProjectConfig() throws Exception{ List<ProjectConfigDict> projectConfigDicts = get("/project-config-dict/query",List.class); System.out.println("預設配置資訊:"
+projectConfigDicts); } }

其中,

@WebAppConfiguration

註解表示採用的上面2中webAppContextSetup(),採用應用程式上下文來進行單元測試。

@SpringBootTest(classes = {ProjectConfigApplication.class},
        properties = {"EUREKA_DEFAULT_ZONE=http://192.168.xxx.xxx:10000/eureka/"})
@ActiveProfiles("unit-test")

因為我們的專案採用的是SpringCloud的框架,使用eureka伺服器註冊了服務,所以每個服務的配置資訊都是從註冊中心讀取的,這裡讀取的是單元測試unit-test的配置。
如果你寫的是一個普通的專案,那麼可以將配置檔案直接寫到application.yml中,程式預設直接讀取本地配置。

  • application.yml檔案格式如下
spring:
  redis:
    host: 192.168.xxx.xxx
    port: 6379
    pool:
      max-idle: 10
      max-total: 20
      max-wait: 2000
  datasource:
    druid:
      driver-class-name: com.mysql.jdbc.Driver
      url: jdbc:mysql://192.168.xxx.xxx:3306/unit_test_org_v3?characterEncoding=UTF-8
      username: xxx
      password: xxx
    driver-class-name: com.mysql.jdbc.Driver
    url: jdbc:mysql://192.168.xxx.xxx:3306/unit_test_org_v3?characterEncoding=UTF-8
    username: xxx
    password: xxx

如果,你有redis服務,就需要配置redis伺服器地址,等資訊。
如果你有資料庫訪問服務,就需要配置datasource等資訊。

  • 輔助單元測試的類
    ControllerTestSupport.java
public abstract class ControllerTestSupport {
    protected static MockMvc mockMvc;
    private static final Long TENANT_ID = 100000L;

    private static final String HEADER_TENANT_ID = "x-tenant-id";
    @Autowired
    private WebApplicationContext context;

    private void initAction(ResultActions actions, List<CheckProperty> properties)
            throws Exception {
        if (null == properties || properties.size() == 0) {
            return;
        }
        for (CheckProperty property : properties) {
            if (property instanceof CheckValueProperty) {
                actions.andExpect(jsonPath("$." + property.getPropertyName(), equalTo(((CheckValueProperty) property).getValue())));
            } else if (property instanceof CheckNullProperty) {
                boolean isNull = ((CheckNullProperty) property).isNull();
                if (isNull) {
                    actions.andExpect(jsonPath("$." + property.getPropertyName(), nullValue()));
                } else {
                    actions.andExpect(jsonPath("$." + property.getPropertyName(), notNullValue()));
                }
            }
        }
    }

    protected <T> T put(String uriPath, Object data,
                        List<CheckProperty> properties, Class<T> tClass)
            throws Exception {
        ResultActions actions = getMockMvc().perform(
                MockMvcRequestBuilders
                        .put(uriPath)
                        .header(HEADER_TENANT_ID, TENANT_ID)
                        .content(JSON.toJSONBytes(data))
                        .contentType(MediaType.APPLICATION_JSON_UTF8_VALUE)
                        .accept(MediaType.APPLICATION_JSON_UTF8_VALUE))
                .andExpect(status().isOk());
        initAction(actions, properties);
        MvcResult mvcResult = actions.andReturn();
        return convert(mvcResult, tClass);
    }

    protected <T> T get(String uriPath, Class<T> tClass)
            throws Exception {
        return get(uriPath, tClass, HttpStatus.OK.value());
    }

    protected <T> T get(String uriPath, Class<T> tClass, int status)
            throws Exception {
        MvcResult mvcResult = getMockMvc().perform(
                MockMvcRequestBuilders
                        .get(uriPath)
                        .header(HEADER_TENANT_ID,TENANT_ID)
                        .contentType(MediaType.APPLICATION_JSON_UTF8_VALUE)
                        .accept(MediaType.APPLICATION_JSON_UTF8_VALUE))
                .andExpect(status().is(status))
                .andReturn();
        return convert(mvcResult, tClass);
    }

    protected <T> T convert(MvcResult mvcResult, Class<T> tClass) throws Exception {
        if (null == tClass) {
            return null;
        }
        String content = mvcResult.getResponse().getContentAsString();
        T actionResult = JSONObject.parseObject(content, tClass);
        return actionResult;
    }

    private synchronized MockMvc getMockMvc() {
        if (null == mockMvc) {
            mockMvc = MockMvcBuilders.webAppContextSetup(context).build();
        }
        return mockMvc;
    }
}

其中,我們看到:
initAction函式是在post、get方法中呼叫的,主要是處理一些特殊的屬性,比如有列舉型別的。
convert方法是為了得到返回的結果,將json轉化為對應的類。
getMockMvc是模擬上下文構建Servlet容器,和第二點的webAppContextSetup對應。

CheckProperty.java:

@Data
@NoArgsConstructor
@AllArgsConstructor
public class CheckProperty {
    private String propertyName;
}


CheckValueProperty .java:

@Data
@EqualsAndHashCode(callSuper = true)
public class CheckValueProperty extends CheckProperty {
    private Object value;

    public CheckValueProperty(String name, Object value) {
        super(name);
        setValue(value);
    }
}


CheckNullProperty .java:

@Data
@EqualsAndHashCode(callSuper = true)
public class CheckNullProperty extends CheckProperty {
    private boolean isNull;

    public CheckNullProperty(String name, boolean isNull) {
        super(name);
        setNull(isNull);
    }
}