單元測試
對於開發人員來講是很是熟悉的,咱們天天的工做也都是圍繞着開發與測試進行的,在最先的時候測試都是採用工具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接口,該接口繼承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
,得到了操做商品基本信息的數據接口代理實例,咱們能夠經過該代理實例去作一些數據庫操做,如上代碼selectAll
、detail
方法所示。
在測試控制器內添加了三個測試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
,該註解纔是配置了測試類的啓動方式,以及啓動時使用實現類的類型。
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 }
我在上面代碼中進行了標記,咱們按照標記進行講解,這樣會更明白一些:1
perform
方法其實只是爲了構建一個請求,而且返回ResultActions
實例,該實例則是能夠獲取到請求的返回內容。2
MockMvcRequestBuilders
該抽象類則是能夠構建多種請求方式,如:Post
、Get
、Put
、Delete
等經常使用的請求方式,其中參數則是咱們須要請求的本項目的相對路徑,/
則是項目請求的根路徑。3
param
方法用於在發送請求時攜帶參數,固然除了該方法還有不少其餘的方法,你們能夠根據實際請求狀況選擇調用。4
andReturn
方法則是在發送請求後須要獲取放回時調用,該方法返回MvcResult
對象,該對象能夠獲取到返回的視圖名稱、返回的Response狀態、獲取攔截請求的攔截器集合等。5
咱們在這裏就是使用到了第4
步內的MvcResult
對象實例獲取的MockHttpServletResponse
對象從而才獲得的Status
狀態碼。6
一樣也是使用MvcResult
實例獲取的MockHttpServletResponse
對象從而獲得的請求返回的字符串內容。【能夠查看rest返回的json數據】7
使用Junit
內部驗證類Assert
判斷返回的狀態碼是否正常爲2008
判斷返回的字符串是否與咱們預計的同樣。
直接上代碼吧,跟上面的代碼幾乎一致,以下所示:
/** * 測試查詢詳情 * @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
方式進行的,咱們也能夠直接調用JPA
、Service
進行直接測試。下面咱們來測試下商品基本信息的添加,代碼以下所示:
/** * 測試添加商品基本信息 */ @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技術交流羣,共同進步。