Springboot單元測試

看到一篇很是好的關於Springboot單元測試的文章,特此轉過來,原文地址: Spring Boot乾貨系列:(十二)Spring Boot使用單元測試

1、前言

此次來介紹下Spring Boot中對單元測試的整合使用,本篇會經過如下4點來介紹,基本知足平常需求java

  • Service層單元測試
  • Controller層單元測試
  • 新斷言assertThat使用
  • 單元測試的回滾

Spring Boot中引入單元測試很簡單,依賴以下:程序員

<dependency>
    <groupId>org.springframework.boot</groupId>
    <artifactId>spring-boot-starter-test</artifactId>
    <scope>test</scope>
</dependency>

在生成的測試類中就能夠寫單元測試了。用spring自帶spring-boot-test的測試工具類便可,spring-boot-starter-test 啓動器能引入這些 Spring Boot 測試模塊:web

  • JUnit:Java 應用程序單元測試標準類庫。
  • Spring Test & Spring Boot Test:Spring Boot 應用程序功能集成化測試支持。
  • Mockito:一個Java Mock測試框架。
  • AssertJ:一個輕量級的斷言類庫。
  • Hamcrest:一個對象匹配器類庫。
  • JSONassert:一個用於JSON的斷言庫。
  • JsonPath:一個JSON操做類庫。

image.png

2、Service單元測試

Spring Boot中單元測試類寫在在src/test/java目錄下,你能夠手動建立具體測試類,若是是IDEA,則能夠經過IDEA自動建立測試類,以下圖,也能夠經過快捷鍵⇧⌘T(MAC)或者Ctrl+Shift+T(Window)來建立,以下:spring

image.png

image.png

自動生成測試類以下:
imgjson

而後再編寫建立好的測試類,具體代碼以下:springboot

package com.dudu.service;
import com.dudu.domain.LearnResource;
import org.junit.Assert;
import org.junit.Test;
import org.junit.runner.RunWith;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.boot.test.context.SpringBootTest;
import org.springframework.test.context.junit4.SpringRunner;
import static org.hamcrest.CoreMatchers.*;

@RunWith(SpringRunner.class)
@SpringBootTest
public class LearnServiceTest {

    @Autowired
    private LearnService learnService;
    
    @Test
    public void getLearn(){
        LearnResource learnResource=learnService.selectByKey(1001L);
        Assert.assertThat(learnResource.getAuthor(),is("嘟嘟MD獨立博客"));
    }
}

上面就是最簡單的單元測試寫法,頂部只要@RunWith(SpringRunner.class)SpringBootTest便可,想要執行的時候,鼠標放在對應的方法,右鍵選擇run該方法便可。網絡

RunWith解釋session

@RunWith就是一個運行器,如 @RunWith(SpringJUnit4ClassRunner.class),讓測試運行於Spring測試環境,@RunWith(SpringJUnit4ClassRunner.class)使用了Spring的SpringJUnit4ClassRunner,以便在測試開始的時候自動建立Spring的應用上下文。其餘的想建立spring容器的話,就得子啊web.xml配置classloder。 註解了@RunWith就能夠直接使用spring容器,直接使用@Test註解,不用啓動spring容器

測試用例中我使用了assertThat斷言,下文中會介紹,也推薦你們使用該斷言。mvc

3、Controller單元測試

上面只是針對Service層作測試,可是有時候須要對Controller層(API)作測試,這時候就得用到MockMvc了,你能夠沒必要啓動工程就能測試這些接口。app

MockMvc實現了對Http請求的模擬,可以直接使用網絡的形式,轉換到Controller的調用,這樣可使得測試速度快、不依賴網絡環境,並且提供了一套驗證的工具,這樣可使得請求的驗證統一併且很方便。

Controller類:

package com.dudu.controller;

/** 教程頁面
 * Created by tengj on 2017/3/13.
 */
@Controller
@RequestMapping("/learn")
public class LearnController  extends AbstractController{
    @Autowired
    private LearnService learnService;
    private Logger logger = LoggerFactory.getLogger(this.getClass());

    @RequestMapping("")
    public String learn(Model model){
        model.addAttribute("ctx", getContextPath()+"/");
        return "learn-resource";
    }

    /**
     * 查詢教程列表
     * @param page
     * @return
     */
    @RequestMapping(value = "/queryLeanList",method = RequestMethod.POST)
    @ResponseBody
    public AjaxObject queryLearnList(Page<LeanQueryLeanListReq> page){
        List<LearnResource> learnList=learnService.queryLearnResouceList(page);
        PageInfo<LearnResource> pageInfo =new PageInfo<LearnResource>(learnList);
        return AjaxObject.ok().put("page", pageInfo);
    }
    
    /**
     * 新添教程
     * @param learn
     */
    @RequestMapping(value = "/add",method = RequestMethod.POST)
    @ResponseBody
    public AjaxObject addLearn(@RequestBody LearnResource learn){
        learnService.save(learn);
        return AjaxObject.ok();
    }

    /**
     * 修改教程
     * @param learn
     */
    @RequestMapping(value = "/update",method = RequestMethod.POST)
    @ResponseBody
    public AjaxObject updateLearn(@RequestBody LearnResource learn){
        learnService.updateNotNull(learn);
        return AjaxObject.ok();
    }

    /**
     * 刪除教程
     * @param ids
     */
    @RequestMapping(value="/delete",method = RequestMethod.POST)
    @ResponseBody
    public AjaxObject deleteLearn(@RequestBody Long[] ids){
        learnService.deleteBatch(ids);
        return AjaxObject.ok();
    }

    /**
     * 獲取教程
     * @param id
     */
    @RequestMapping(value="/resource/{id}",method = RequestMethod.GET)
    @ResponseBody
    public LearnResource qryLearn(@PathVariable(value = "id") Long id){
       LearnResource lean= learnService.selectByKey(id);
        return lean;
    }
}

這裏咱們也自動建立一個Controller的測試類,具體代碼以下:

package com.dudu.controller;

import com.dudu.domain.User;
import org.junit.Before;
import org.junit.Test;
import org.junit.runner.RunWith;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.boot.test.context.SpringBootTest;
import org.springframework.http.MediaType;
import org.springframework.mock.web.MockHttpSession;
import org.springframework.test.context.junit4.SpringRunner;
import org.springframework.test.web.servlet.MockMvc;
import org.springframework.test.web.servlet.request.MockMvcRequestBuilders;
import org.springframework.test.web.servlet.result.MockMvcResultHandlers;
import org.springframework.test.web.servlet.result.MockMvcResultMatchers;
import org.springframework.test.web.servlet.setup.MockMvcBuilders;
import org.springframework.web.context.WebApplicationContext;

@RunWith(SpringRunner.class)
@SpringBootTest

public class LearnControllerTest {
    @Autowired
    private WebApplicationContext wac;

    private MockMvc mvc;
    private MockHttpSession session;


    @Before
    public void setupMockMvc(){
        mvc = MockMvcBuilders.webAppContextSetup(wac).build(); //初始化MockMvc對象
        session = new MockHttpSession();
        User user =new User("root","root");
        session.setAttribute("user",user); //攔截器那邊會判斷用戶是否登陸,因此這裏注入一個用戶
    }

    /**
     * 新增教程測試用例
     * @throws Exception
     */
    @Test
    public void addLearn() throws Exception{
        String json="{\"author\":\"HAHAHAA\",\"title\":\"Spring\",\"url\":\"http://tengj.top/\"}";
        mvc.perform(MockMvcRequestBuilders.post("/learn/add")
                    .accept(MediaType.APPLICATION_JSON_UTF8)
                    .content(json.getBytes()) //傳json參數
                    .session(session)
            )
           .andExpect(MockMvcResultMatchers.status().isOk())
           .andDo(MockMvcResultHandlers.print());
    }

    /**
     * 獲取教程測試用例
     * @throws Exception
     */
    @Test
    public void qryLearn() throws Exception {
        mvc.perform(MockMvcRequestBuilders.get("/learn/resource/1001")
                    .contentType(MediaType.APPLICATION_JSON_UTF8)
                    .accept(MediaType.APPLICATION_JSON_UTF8)
                    .session(session)
            )
           .andExpect(MockMvcResultMatchers.status().isOk())
           .andExpect(MockMvcResultMatchers.jsonPath("$.author").value("嘟嘟MD獨立博客"))
           .andExpect(MockMvcResultMatchers.jsonPath("$.title").value("Spring Boot乾貨系列"))
           .andDo(MockMvcResultHandlers.print());
    }

    /**
     * 修改教程測試用例
     * @throws Exception
     */
    @Test
    public void updateLearn() throws Exception{
        String json="{\"author\":\"測試修改\",\"id\":1031,\"title\":\"Spring Boot乾貨系列\",\"url\":\"http://tengj.top/\"}";
        mvc.perform(MockMvcRequestBuilders.post("/learn/update")
                .accept(MediaType.APPLICATION_JSON_UTF8)
                .content(json.getBytes())//傳json參數
                .session(session)
        )
                .andExpect(MockMvcResultMatchers.status().isOk())
                .andDo(MockMvcResultHandlers.print());
    }

    /**
     * 刪除教程測試用例
     * @throws Exception
     */
    @Test
    public void deleteLearn() throws Exception{
        String json="[1031]";
        mvc.perform(MockMvcRequestBuilders.post("/learn/delete")
                .accept(MediaType.APPLICATION_JSON_UTF8)
                .content(json.getBytes())//傳json參數
                .session(session)
        )
                .andExpect(MockMvcResultMatchers.status().isOk())
                .andDo(MockMvcResultHandlers.print());
    }

}

上面實現了基本的增刪改查的測試用例,使用MockMvc的時候須要先用MockMvcBuilders使用構建MockMvc對象,以下

@Before
public void setupMockMvc(){
    mvc = MockMvcBuilders.webAppContextSetup(wac).build(); //初始化MockMvc對象
    session = new MockHttpSession();
    User user =new User("root","root");
    session.setAttribute("user",user); //攔截器那邊會判斷用戶是否登陸,因此這裏注入一個用戶
}

由於攔截器那邊會判斷是否登陸,因此這裏我注入了一個用戶,你也能夠直接修改攔截器取消驗證用戶登陸,先測試完再開啓。

這裏拿一個例子來介紹一下MockMvc簡單的方法

/**
 * 獲取教程測試用例
 * @throws Exception
 */
@Test
public void qryLearn() throws Exception {
    mvc.perform(MockMvcRequestBuilders.get("/learn/resource/1001")
                .contentType(MediaType.APPLICATION_JSON_UTF8)
                .accept(MediaType.APPLICATION_JSON_UTF8)
                .session(session)
        )
       .andExpect(MockMvcResultMatchers.status().isOk())
       .andExpect(MockMvcResultMatchers.jsonPath("$.author").value("嘟嘟MD獨立博客"))
       .andExpect(MockMvcResultMatchers.jsonPath("$.title").value("Spring Boot乾貨系列"))
       .andDo(MockMvcResultHandlers.print());
}
  1. mockMvc.perform執行一個請求
  2. MockMvcRequestBuilders.get(「/user/1」)構造一個請求,Post請求就用.post方法
  3. contentType(MediaType.APPLICATION_JSON_UTF8)表明發送端發送的數據格式是application/json;charset=UTF-8
  4. accept(MediaType.APPLICATION_JSON_UTF8)表明客戶端但願接受的數據類型爲application/json;charset=UTF-8
  5. session(session)注入一個session,這樣攔截器才能夠經過
  6. ResultActions.andExpect添加執行完成後的斷言
  7. ResultActions.andExpect(MockMvcResultMatchers.status().isOk())方法看請求的狀態響應碼是否爲200若是不是則拋異常,測試不經過
  8. andExpect(MockMvcResultMatchers.jsonPath(「$.author」).value(「嘟嘟MD獨立博客」))這裏jsonPath用來獲取author字段比對是否爲嘟嘟MD獨立博客,不是就測試不經過
  9. ResultActions.andDo添加一個結果處理器,表示要對結果作點什麼事情,好比此處使用MockMvcResultHandlers.print()輸出整個響應結果信息

本例子測試以下:
image.png

4、新斷言assertThat使用

JUnit 4.4 結合 Hamcrest 提供了一個全新的斷言語法——assertThat。程序員能夠只使用 assertThat 一個斷言語句,結合 Hamcrest 提供的匹配符,就能夠表達所有的測試思想,咱們引入的版本是Junit4.12因此支持assertThat。

清單 1 assertThat 基本語法

assertThat( \[value\], \[matcher statement\] );
  • value 是接下來想要測試的變量值;
  • matcher statement 是使用 Hamcrest 匹配符來表達的對前面變量所指望的值的聲明,若是 value 值與 matcher statement 所表達的指望值相符,則測試成功,不然測試失敗。

assertThat 的優勢

  • 優勢 1:之前 JUnit 提供了不少的 assertion 語句,如:assertEquals,assertNotSame,assertFalse,assertTrue,assertNotNull,assertNull 等,如今有了 JUnit 4.4,一條 assertThat 便可以替代全部的 assertion 語句,這樣能夠在全部的單元測試中只使用一個斷言方法,使得編寫測試用例變得簡單,代碼風格變得統一,測試代碼也更容易維護。
  • 優勢 2:assertThat 使用了 Hamcrest 的 Matcher 匹配符,用戶可使用匹配符規定的匹配準則精確的指定一些想設定知足的條件,具備很強的易讀性,並且使用起來更加靈活。如清單 2 所示:

清單 2 使用匹配符 Matcher 和不使用之間的比較

// 想判斷某個字符串 s 是否含有子字符串 "developer" 或 "Works" 中間的一個
// JUnit 4.4 之前的版本:assertTrue(s.indexOf("developer")>-1||s.indexOf("Works")>-1 );
// JUnit 4.4:
assertThat(s, anyOf(containsString("developer"), containsString("Works"))); 
// 匹配符 anyOf 表示任何一個條件知足則成立,相似於邏輯或 "||", 匹配符 containsString 表示是否含有參數子 
// 字符串,文章接下來會對匹配符進行具體介紹

優勢 3:assertThat 再也不像 assertEquals 那樣,使用比較難懂的「謂賓主」語法模式(如:assertEquals(3, x);),相反,assertThat 使用了相似於「主謂賓」的易讀語法模式(如:assertThat(x,is(3));),使得代碼更加直觀、易讀。


注:本篇博文爲轉載,原問地址:Spring Boot乾貨系列:(十二)Spring Boot使用單元測試

相關文章
相關標籤/搜索