第三十五章:SpringBoot與單元測試的小祕密

單元測試對於開發人員來講是很是熟悉的,咱們天天的工做也都是圍繞着開發與測試進行的,在最先的時候測試都是採用工具Debug模式進行調試程序,後來Junit的誕生也讓程序測試發生了很大的變化。咱們今天來說解下基於SpringBoot結合Junit怎麼來完成單元測試java

本章目的

基於SpringBoot平臺整合Junit分別完成客戶端服務端單元測試mysql

構建項目

咱們首先使用idea工具建立一個SpringBoot項目,而且添加相關Web、MySQL、JPA依賴,具體pom.xml配置依賴內容以下所示:git

.../省略其餘配置
<dependencies>
        <!--web依賴-->
        <dependency>
            <groupId>org.springframework.boot</groupId>
            <artifactId>spring-boot-starter-web</artifactId>
        </dependency>
        <!--data jpa依賴-->
        <dependency>
            <groupId>org.springframework.boot</groupId>
            <artifactId>spring-boot-starter-data-jpa</artifactId>
        </dependency>
        <!--druid數據源依賴-->
        <dependency>
            <groupId>com.alibaba</groupId>
            <artifactId>druid</artifactId>
            <version>1.0.31</version>
        </dependency>
        <!--lombok依賴-->
        <dependency>
            <groupId>org.projectlombok</groupId>
            <artifactId>lombok</artifactId>
        </dependency>
        <!--MySQL依賴-->
        <dependency>
            <groupId>mysql</groupId>
            <artifactId>mysql-connector-java</artifactId>
        </dependency>

        <!--springboot程序測試依賴,建立項目默認添加-->
        <dependency>
            <groupId>org.springframework.boot</groupId>
            <artifactId>spring-boot-starter-test</artifactId>
            <scope>test</scope>
        </dependency>

    </dependencies>
.../省略其餘配置

配置數據庫

咱們本章的內容須要訪問數據庫,咱們先在src/main/resources下添加application.yml配置文件,對應添加數據庫配置信息以下所示:web

spring:
  datasource:
    type: com.alibaba.druid.pool.DruidDataSource
    driver-class-name: com.mysql.jdbc.Driver
    url: jdbc:mysql://127.0.0.1:3306/test?characterEncoding=utf8
    username: root
    password: 123456
    #最大活躍數
    maxActive: 20
    #初始化數量
    initialSize: 1
    #最大鏈接等待超時時間
    maxWait: 60000
    #打開PSCache,而且指定每一個鏈接PSCache的大小
    poolPreparedStatements: true
    maxPoolPreparedStatementPerConnectionSize: 20
    #經過connectionProperties屬性來打開mergeSql功能;慢SQL記錄
    #connectionProperties: druid.stat.mergeSql=true;druid.stat.slowSqlMillis=5000
    minIdle: 1
    timeBetweenEvictionRunsMillis: 60000
    minEvictableIdleTimeMillis: 300000
    validationQuery: select 1 from dual
    testWhileIdle: true
    testOnBorrow: false
    testOnReturn: false
    #配置監控統計攔截的filters,去掉後監控界面sql將沒法統計,'wall'用於防火牆
    filters: stat, wall, log4j
  jpa:
    properties:
      hibernate:
        show_sql: true
        format_sql: true

以上配置都是比較經常使用到,這裏不作多解釋了,若是不明白能夠去本文底部SpringBoot學習目錄文章內找尋對應的章節。spring

構建實體

對應數據庫內的數據表來建立一個商品基本信息實體,實體內容以下所示:sql

package com.yuqiyu.chapter35.bean;

import lombok.Data;

import javax.persistence.*;
import java.io.Serializable;

/**
 * 商品基本信息實體
 * ========================
 * Created with IntelliJ IDEA.
 * User:恆宇少年
 * Date:2017/9/13
 * Time:22:20
 * 碼雲:http://git.oschina.net/jnyqy
 * ========================
 */
@Data
@Entity
@Table(name = "good_infos")
public class GoodInfoEntity implements Serializable
{
    //商品編號
    @Id
    @Column(name = "tg_id")
    @GeneratedValue
    private Integer tgId;

    //商品類型編號
    @Column(name = "tg_type_id")
    private Integer typeId;

    //商品標題
    @Column(name = "tg_title")
    private String title;

    //商品價格
    @Column(name = "tg_price")
    private double price;

    //商品排序
    @Column(name = "tg_order")
    private int order;
}

構建JPA

基於商品基本信息實體類建立一個JPA接口,該接口繼承JpaRepository接口完成框架經過反向代理模式進行生成實現類,自定義JPA接口內容以下所示:數據庫

package com.yuqiyu.chapter35.jpa;

import com.yuqiyu.chapter35.bean.GoodInfoEntity;
import org.springframework.data.jpa.repository.JpaRepository;

/**
 * 商品jpa
 * ========================
 * Created with IntelliJ IDEA.
 * User:恆宇少年
 * Date:2017/9/13
 * Time:22:23
 * 碼雲:http://git.oschina.net/jnyqy
 * ========================
 */
public interface GoodInfoJPA
    extends JpaRepository<GoodInfoEntity,Integer>
{
}

構建測試控制器

下面咱們開始爲單元測試來作準備工做,先來建立一個SpringMVC控制器來處理請求,代碼以下所示:json

package com.yuqiyu.chapter35.controller;

import com.yuqiyu.chapter35.bean.GoodInfoEntity;
import com.yuqiyu.chapter35.jpa.GoodInfoJPA;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RequestMethod;
import org.springframework.web.bind.annotation.RestController;

import java.util.List;

/**
 * ===============================
 * Created with Eclipse.
 * User:於起宇
 * Date:2017/9/13
 * Time:18:37
 * 簡書:http://www.jianshu.com/u/092df3f77bca
 * ================================
 */
@RestController
public class TestController
{
    //商品基本信息數據接口
    @Autowired
    private GoodInfoJPA goodInfoJPA;

    /**
     * 查詢首頁內容
     * @return
     */
    @RequestMapping(value = "/index")
    public String index(String name)
    {
        return "this is index page" + name;
    }

    /**
     * 查詢所有商品
     * @return
     */
    @RequestMapping(value = "/all")
    public List<GoodInfoEntity> selectAll()
    {
        return goodInfoJPA.findAll();
    }

    /**
     * 查詢商品詳情
     * @param goodId
     * @return
     */
    @RequestMapping(value = "/detail",method = RequestMethod.GET)
    public GoodInfoEntity selectOne(Integer goodId)
    {
        return goodInfoJPA.findOne(goodId);
    }
}

咱們在測試控制內注入了GoodInfoJPA,得到了操做商品基本信息的數據接口代理實例,咱們能夠經過該代理實例去作一些數據庫操做,如上代碼selectAlldetail方法所示。
在測試控制器內添加了三個測試MVC方法,咱們接下來開始編寫單元測試代碼。數組

編寫單元測試

在咱們使用idea開發工具構建完成SpringBoot項目後,會自動爲咱們添加spring-boot-starter-test依賴到pom.xml配置文件內,固然也爲咱們自動建立了一個測試類,該類內一開始是沒有過多的代碼的。
下面咱們開始基於該測試類進行添加邏輯,代碼以下所示:springboot

....//省略依賴導包
/**
 * 單元測試
 */
@RunWith(SpringRunner.class)
@SpringBootTest
public class Chapter35ApplicationTests {
    /**
     * 模擬mvc測試對象
     */
    private MockMvc mockMvc;

    /**
     * web項目上下文
     */
    @Autowired
    private WebApplicationContext webApplicationContext;

    /**
     * 商品業務數據接口
     */
    @Autowired
    private GoodInfoJPA goodInfoJPA;

    /**
     * 全部測試方法執行以前執行該方法
     */
    @Before
    public void before() {
        //獲取mockmvc對象實例
        mockMvc = MockMvcBuilders.webAppContextSetup(webApplicationContext).build();
    }
}

在上面測試代碼中咱們從上面開始講解下,其中@RunWith這裏就很少作解釋了,咱們最比較經常使用到的就是這個註解。

@SpringBootTest這個註解這裏要強調下,這是SpringBoot項目測試的核心註解,標識該測試類以SpringBoot方式運行,該註解的源碼以下所示:

...//省略導包
@Target({ElementType.TYPE})
@Retention(RetentionPolicy.RUNTIME)
@Documented
@Inherited
@BootstrapWith(SpringBootTestContextBootstrapper.class)
public @interface SpringBootTest {
    @AliasFor("properties")
    String[] value() default {};

    @AliasFor("value")
    String[] properties() default {};

    Class<?>[] classes() default {};

    SpringBootTest.WebEnvironment webEnvironment() default SpringBootTest.WebEnvironment.MOCK;

    public static enum WebEnvironment {
        MOCK(false),
        RANDOM_PORT(true),
        DEFINED_PORT(true),
        NONE(false);

        private final boolean embedded;

        private WebEnvironment(boolean embedded) {
            this.embedded = embedded;
        }

        public boolean isEmbedded() {
            return this.embedded;
        }
    }
}

咱們能夠看到在@SpringBootTest註解源碼中最爲重要的就是@BootstrapWith,該註解纔是配置了測試類的啓動方式,以及啓動時使用實現類的類型。

測試index請求

MockMvc這個類是一個被final修飾的類型,該類沒法被繼承使用。這個類是Spring爲咱們提供模擬SpringMVC請求的實例類,該類則是由MockMvcBuilders經過WebApplicationContext實例進行建立的,初始化MockMvc實例咱們能夠看下before方法邏輯。到如今爲止咱們纔是萬事俱備就差編寫單元測試邏輯了,咱們首先來編寫訪問/index請求路徑的測試,具體測試代碼以下所示:

/**
     * 測試訪問/index地址
     * @throws Exception
     */
    @Test
    public void testIndex() throws Exception {
        MvcResult mvcResult = mockMvc
                .perform(// 1
                        MockMvcRequestBuilders.get("/index") // 2
                        .param("name","admin") // 3
                )
                .andReturn();// 4

        int status = mvcResult.getResponse().getStatus(); // 5
        String responseString = mvcResult.getResponse().getContentAsString(); // 6

        Assert.assertEquals("請求錯誤", 200, status); // 7
        Assert.assertEquals("返回結果不一致", "this is index pageadmin", responseString); // 8
    }

MockMvc解析

我在上面代碼中進行了標記,咱們按照標記進行講解,這樣會更明白一些:
1 perform方法其實只是爲了構建一個請求,而且返回ResultActions實例,該實例則是能夠獲取到請求的返回內容。
2 MockMvcRequestBuilders該抽象類則是能夠構建多種請求方式,如:PostGetPutDelete等經常使用的請求方式,其中參數則是咱們須要請求的本項目的相對路徑,/則是項目請求的根路徑。
3 param方法用於在發送請求時攜帶參數,固然除了該方法還有不少其餘的方法,你們能夠根據實際請求狀況選擇調用。
4 andReturn方法則是在發送請求後須要獲取放回時調用,該方法返回MvcResult 對象,該對象能夠獲取到返回的視圖名稱、返回的Response狀態、獲取攔截請求的攔截器集合等。
5 咱們在這裏就是使用到了第4步內的MvcResult對象實例獲取的MockHttpServletResponse對象從而才獲得的Status狀態碼。
6 一樣也是使用MvcResult實例獲取的MockHttpServletResponse對象從而獲得的請求返回的字符串內容。【能夠查看rest返回的json數據】
7 使用Junit內部驗證類Assert判斷返回的狀態碼是否正常爲200
8 判斷返回的字符串是否與咱們預計的同樣。

測試商品詳情

直接上代碼吧,跟上面的代碼幾乎一致,以下所示:

/**
     * 測試查詢詳情
     * @throws Exception
     */
    @Test
    public void testDetail() throws Exception
    {
        MvcResult mvcResult = mockMvc
                .perform(
                        MockMvcRequestBuilders.get("/detail")
                        .param("goodId","2")
                )
                .andReturn(); // 5

        //輸出經歷的攔截器
        HandlerInterceptor[] interceptors = mvcResult.getInterceptors();
        System.out.println(interceptors[0].getClass().getName());

        int status = mvcResult.getResponse().getStatus(); // 6
        String responseString = mvcResult.getResponse().getContentAsString(); // 7
        System.out.println("返回內容:"+responseString);
        Assert.assertEquals("return status not equals 200", 200, status); // 8
    }

上面惟一一個部分須要解釋下,在上面測試方法內輸出了請求經歷的攔截器,若是咱們配置了多個攔截器這裏會根據前後順序寫入到攔截器數組內,其餘的MockMvc測試方法以及參數跟上面測試方法一致。

測試添加

在測試類聲明定義全局字段時,咱們注入了GoodInfoJPA實例,固然單元測試也不只僅是客戶端也就是使用MockMvc方式進行的,咱們也能夠直接調用JPAService進行直接測試。下面咱們來測試下商品基本信息的添加,代碼以下所示:

/**
     * 測試添加商品基本信息
     */
    @Test
    public void testInsert()
    {
        /**
         * 商品基本信息實體
         */
        GoodInfoEntity goodInfoEntity = new GoodInfoEntity();
        goodInfoEntity.setTitle("西紅柿");
        goodInfoEntity.setOrder(2);
        goodInfoEntity.setPrice(5.82);
        goodInfoEntity.setTypeId(1);
        goodInfoJPA.save(goodInfoEntity);
        /**
         * 測試是否添加成功
         * 驗證主鍵是否存在
         */
        Assert.assertNotNull(goodInfoEntity.getTgId());
    }

在上面代碼中並無什麼特殊的部分,是咱們在使用Data JPA時用到的save方法用於執行添加,在添加完成後驗證主鍵的值是否存在,NotNull時證實添加成功。

測試刪除

與添加差異不大,代碼以下所示:

/**
     * 測試刪除商品基本信息
     */
    @Test
    public void testDelete()
    {
        //根據主鍵刪除
        goodInfoJPA.delete(3);
        
        //驗證數據庫是否已經刪除
        Assert.assertNull(goodInfoJPA.findOne(3));
    }

在上面代碼中,咱們根據主鍵的值進行刪除商品的基本信息,執行刪除完成後調用selectOne方法查看數據庫內是否已經不存在該條數據了。

總結

本章主要介紹了基於SpringBoot平臺的兩種單元測試方式,一種是在服務端採用Spring注入方式將須要測試的JPA或者Service注入到測試類中,而後調用方法便可。另一種則是在客戶端採用MockMvc方式測試Web請求,根據傳遞的不用參數以及請求返回對象反饋信息進行驗證測試。

本章代碼已經上傳到碼雲:
網頁地址:http://git.oschina.net/jnyqy/lessons
Git地址:https://git.oschina.net/jnyqy/lessons.git
SpringBoot相關係列文章請訪問:目錄:SpringBoot學習目錄
QueryDSL相關係列文章請訪問:QueryDSL通用查詢框架學習目錄
SpringDataJPA相關係列文章請訪問:目錄:SpringDataJPA學習目錄
感謝閱讀!
歡迎加入QQ技術交流羣,共同進步。
QQ技術交流羣

相關文章
相關標籤/搜索