單元測(cè)試
對(duì)于開(kāi)發(fā)人員來(lái)說(shuō)是非常熟悉的坡椒,我們每天的工作也都是圍繞著開(kāi)發(fā)與測(cè)試進(jìn)行的苞也,在最早的時(shí)候測(cè)試都是采用工具Debug
模式進(jìn)行調(diào)試程序邮弹,后來(lái)Junit
的誕生也讓程序測(cè)試發(fā)生了很大的變化超棺。我們今天來(lái)講解下基于SpringBoot
結(jié)合Junit
怎么來(lái)完成單元測(cè)試
括堤。
免費(fèi)教程專題
恒宇少年在博客整理三套免費(fèi)學(xué)習(xí)教程專題
碘耳,由于文章偏多
特意添加了閱讀指南
抱既,新文章以及之前的文章都會(huì)在專題內(nèi)陸續(xù)填充
剪返,希望可以幫助大家解惑更多知識(shí)點(diǎn)。
本章目的
基于SpringBoot
平臺(tái)整合Junit
分別完成客戶端
倚搬、服務(wù)端
的單元測(cè)試
冶共。
SpringBoot 企業(yè)級(jí)核心技術(shù)學(xué)習(xí)專題
專題 | 專題名稱 | 專題描述 |
---|---|---|
001 | Spring Boot 核心技術(shù) | 講解SpringBoot一些企業(yè)級(jí)層面的核心組件 |
002 | Spring Boot 核心技術(shù)章節(jié)源碼 | Spring Boot 核心技術(shù)簡(jiǎn)書(shū)每一篇文章碼云對(duì)應(yīng)源碼 |
003 | Spring Cloud 核心技術(shù) | 對(duì)Spring Cloud核心技術(shù)全面講解 |
004 | Spring Cloud 核心技術(shù)章節(jié)源碼 | Spring Cloud 核心技術(shù)簡(jiǎn)書(shū)每一篇文章對(duì)應(yīng)源碼 |
005 | QueryDSL 核心技術(shù) | 全面講解QueryDSL核心技術(shù)以及基于SpringBoot整合SpringDataJPA |
006 | SpringDataJPA 核心技術(shù) | 全面講解SpringDataJPA核心技術(shù) |
007 | SpringBoot核心技術(shù)學(xué)習(xí)目錄 | SpringBoot系統(tǒng)的學(xué)習(xí)目錄,敬請(qǐng)關(guān)注點(diǎn)贊C拷纭捅僵!! |
構(gòu)建項(xiàng)目
我們首先使用idea工具創(chuàng)建一個(gè)SpringBoot
項(xiàng)目,并且添加相關(guān)Web眨层、MySQL庙楚、JPA依賴,具體pom.xml配置依賴內(nèi)容如下所示:
.../省略其他配置
<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數(shù)據(jù)源依賴-->
<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程序測(cè)試依賴趴樱,創(chuàng)建項(xiàng)目默認(rèn)添加-->
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-test</artifactId>
<scope>test</scope>
</dependency>
</dependencies>
.../省略其他配置
配置數(shù)據(jù)庫(kù)
我們本章的內(nèi)容需要訪問(wèn)數(shù)據(jù)庫(kù)馒闷,我們先在src/main/resources
下添加application.yml
配置文件,對(duì)應(yīng)添加數(shù)據(jù)庫(kù)配置信息如下所示:
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
#最大活躍數(shù)
maxActive: 20
#初始化數(shù)量
initialSize: 1
#最大連接等待超時(shí)時(shí)間
maxWait: 60000
#打開(kāi)PSCache叁征,并且指定每個(gè)連接PSCache的大小
poolPreparedStatements: true
maxPoolPreparedStatementPerConnectionSize: 20
#通過(guò)connectionProperties屬性來(lái)打開(kāi)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
#配置監(jiān)控統(tǒng)計(jì)攔截的filters,去掉后監(jiān)控界面sql將無(wú)法統(tǒng)計(jì),'wall'用于防火墻
filters: stat, wall, log4j
jpa:
properties:
hibernate:
show_sql: true
format_sql: true
以上配置都是比較常用到捺疼,這里不做多解釋了疏虫,如果不明白可以去本文底部SpringBoot學(xué)習(xí)目錄
文章內(nèi)找尋對(duì)應(yīng)的章節(jié)。
構(gòu)建實(shí)體
對(duì)應(yīng)數(shù)據(jù)庫(kù)內(nèi)的數(shù)據(jù)表來(lái)創(chuàng)建一個(gè)商品基本信息實(shí)體,實(shí)體內(nèi)容如下所示:
package com.yuqiyu.chapter35.bean;
import lombok.Data;
import javax.persistence.*;
import java.io.Serializable;
/**
* 商品基本信息實(shí)體
* ========================
* 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
{
//商品編號(hào)
@Id
@Column(name = "tg_id")
@GeneratedValue
private Integer tgId;
//商品類(lèi)型編號(hào)
@Column(name = "tg_type_id")
private Integer typeId;
//商品標(biāo)題
@Column(name = "tg_title")
private String title;
//商品價(jià)格
@Column(name = "tg_price")
private double price;
//商品排序
@Column(name = "tg_order")
private int order;
}
構(gòu)建JPA
基于商品基本信息實(shí)體類(lèi)創(chuàng)建一個(gè)JPA接口卧秘,該接口繼承JpaRepository
接口完成框架通過(guò)反向代理模式進(jìn)行生成實(shí)現(xiàn)類(lèi)尤蛮,自定義JPA接口內(nèi)容如下所示:
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>
{
}
構(gòu)建測(cè)試控制器
下面我們開(kāi)始為單元測(cè)試
來(lái)做準(zhǔn)備工作,先來(lái)創(chuàng)建一個(gè)SpringMVC
控制器來(lái)處理請(qǐng)求斯议,代碼如下所示:
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
* 簡(jiǎn)書(shū):http://www.reibang.com/u/092df3f77bca
* ================================
*/
@RestController
public class TestController
{
//商品基本信息數(shù)據(jù)接口
@Autowired
private GoodInfoJPA goodInfoJPA;
/**
* 查詢首頁(yè)內(nèi)容
* @return
*/
@RequestMapping(value = "/index")
public String index(String name)
{
return "this is index page" + name;
}
/**
* 查詢?nèi)可唐? * @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);
}
}
我們?cè)跍y(cè)試控制內(nèi)注入了GoodInfoJPA
,獲得了操作商品基本信息的數(shù)據(jù)接口代理實(shí)例醇锚,我們可以通過(guò)該代理實(shí)例去做一些數(shù)據(jù)庫(kù)操作哼御,如上代碼selectAll
、detail
方法所示焊唬。
在測(cè)試控制器內(nèi)添加了三個(gè)測(cè)試MVC
方法恋昼,我們接下來(lái)開(kāi)始編寫(xiě)單元測(cè)試
代碼。
編寫(xiě)單元測(cè)試
在我們使用idea開(kāi)發(fā)工具構(gòu)建完成SpringBoot
項(xiàng)目后赶促,會(huì)自動(dòng)為我們添加spring-boot-starter-test
依賴到pom.xml
配置文件內(nèi)液肌,當(dāng)然也為我們自動(dòng)創(chuàng)建了一個(gè)測(cè)試類(lèi),該類(lèi)內(nèi)一開(kāi)始是沒(méi)有過(guò)多的代碼的鸥滨。
下面我們開(kāi)始基于該測(cè)試類(lèi)進(jìn)行添加邏輯嗦哆,代碼如下所示:
....//省略依賴導(dǎo)包
/**
* 單元測(cè)試
*/
@RunWith(SpringRunner.class)
@SpringBootTest
public class Chapter35ApplicationTests {
/**
* 模擬mvc測(cè)試對(duì)象
*/
private MockMvc mockMvc;
/**
* web項(xiàng)目上下文
*/
@Autowired
private WebApplicationContext webApplicationContext;
/**
* 商品業(yè)務(wù)數(shù)據(jù)接口
*/
@Autowired
private GoodInfoJPA goodInfoJPA;
/**
* 所有測(cè)試方法執(zhí)行之前執(zhí)行該方法
*/
@Before
public void before() {
//獲取mockmvc對(duì)象實(shí)例
mockMvc = MockMvcBuilders.webAppContextSetup(webApplicationContext).build();
}
}
在上面測(cè)試代碼中我們從上面開(kāi)始講解下,其中@RunWith
這里就不多做解釋了婿滓,我們最比較常用到的就是這個(gè)注解老速。
@SpringBootTest
這個(gè)注解這里要強(qiáng)調(diào)下,這是SpringBoot
項(xiàng)目測(cè)試的核心注解凸主,標(biāo)識(shí)該測(cè)試類(lèi)以SpringBoot
方式運(yùn)行橘券,該注解的源碼如下所示:
...//省略導(dǎo)包
@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
,該注解才是配置了測(cè)試類(lèi)的啟動(dòng)方式卿吐,以及啟動(dòng)時(shí)使用實(shí)現(xiàn)類(lèi)的類(lèi)型旁舰。
測(cè)試index請(qǐng)求
MockMvc
這個(gè)類(lèi)是一個(gè)被final
修飾的類(lèi)型,該類(lèi)無(wú)法被繼承使用嗡官。這個(gè)類(lèi)是Spring
為我們提供模擬SpringMVC
請(qǐng)求的實(shí)例類(lèi)箭窜,該類(lèi)則是由MockMvcBuilders
通過(guò)WebApplicationContext
實(shí)例進(jìn)行創(chuàng)建的,初始化MockMvc
實(shí)例我們可以看下before
方法邏輯衍腥。到現(xiàn)在為止我們才是萬(wàn)事俱備就差編寫(xiě)單元測(cè)試
邏輯了绽快,我們首先來(lái)編寫(xiě)訪問(wèn)/index
請(qǐng)求路徑的測(cè)試,具體測(cè)試代碼如下所示:
/**
* 測(cè)試訪問(wèn)/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("請(qǐng)求錯(cuò)誤", 200, status); // 7
Assert.assertEquals("返回結(jié)果不一致", "this is index pageadmin", responseString); // 8
}
MockMvc解析
我在上面代碼中進(jìn)行了標(biāo)記紧阔,我們按照標(biāo)記進(jìn)行講解坊罢,這樣會(huì)更明白一些:
1
perform
方法其實(shí)只是為了構(gòu)建一個(gè)請(qǐng)求,并且返回ResultActions
實(shí)例擅耽,該實(shí)例則是可以獲取到請(qǐng)求的返回內(nèi)容活孩。
2
MockMvcRequestBuilders
該抽象類(lèi)則是可以構(gòu)建多種請(qǐng)求方式,如:Post
乖仇、Get
憾儒、Put
询兴、Delete
等常用的請(qǐng)求方式,其中參數(shù)則是我們需要請(qǐng)求的本項(xiàng)目的相對(duì)路徑起趾,/
則是項(xiàng)目請(qǐng)求的根路徑诗舰。
3
param
方法用于在發(fā)送請(qǐng)求時(shí)攜帶參數(shù),當(dāng)然除了該方法還有很多其他的方法训裆,大家可以根據(jù)實(shí)際請(qǐng)求情況選擇調(diào)用眶根。
4
andReturn
方法則是在發(fā)送請(qǐng)求后需要獲取放回時(shí)調(diào)用,該方法返回MvcResult
對(duì)象边琉,該對(duì)象可以獲取到返回的視圖名稱属百、返回的Response狀態(tài)、獲取攔截請(qǐng)求的攔截器集合等变姨。
5
我們?cè)谶@里就是使用到了第4
步內(nèi)的MvcResult
對(duì)象實(shí)例獲取的MockHttpServletResponse
對(duì)象從而才得到的Status
狀態(tài)碼族扰。
6
同樣也是使用MvcResult
實(shí)例獲取的MockHttpServletResponse
對(duì)象從而得到的請(qǐng)求返回的字符串內(nèi)容《ㄅ罚【可以查看rest返回的json數(shù)據(jù)】
7
使用Junit
內(nèi)部驗(yàn)證類(lèi)Assert
判斷返回的狀態(tài)碼是否正常為200
8
判斷返回的字符串是否與我們預(yù)計(jì)的一樣渔呵。
測(cè)試商品詳情
直接上代碼吧,跟上面的代碼幾乎一致砍鸠,如下所示:
/**
* 測(cè)試查詢?cè)斍? * @throws Exception
*/
@Test
public void testDetail() throws Exception
{
MvcResult mvcResult = mockMvc
.perform(
MockMvcRequestBuilders.get("/detail")
.param("goodId","2")
)
.andReturn(); // 5
//輸出經(jīng)歷的攔截器
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("返回內(nèi)容:"+responseString);
Assert.assertEquals("return status not equals 200", 200, status); // 8
}
上面唯一一個(gè)部分需要解釋下厘肮,在上面測(cè)試方法內(nèi)輸出了請(qǐng)求經(jīng)歷的攔截器,如果我們配置了多個(gè)攔截器這里會(huì)根據(jù)先后順序?qū)懭氲綌r截器數(shù)組內(nèi)睦番,其他的MockMvc
測(cè)試方法以及參數(shù)跟上面測(cè)試方法一致类茂。
測(cè)試添加
在測(cè)試類(lèi)聲明定義全局字段時(shí),我們注入了GoodInfoJPA
實(shí)例托嚣,當(dāng)然單元測(cè)試也不僅僅是客戶端
也就是使用MockMvc
方式進(jìn)行的巩检,我們也可以直接調(diào)用JPA
、Service
進(jìn)行直接測(cè)試示启。下面我們來(lái)測(cè)試下商品基本信息的添加兢哭,代碼如下所示:
/**
* 測(cè)試添加商品基本信息
*/
@Test
public void testInsert()
{
/**
* 商品基本信息實(shí)體
*/
GoodInfoEntity goodInfoEntity = new GoodInfoEntity();
goodInfoEntity.setTitle("西紅柿");
goodInfoEntity.setOrder(2);
goodInfoEntity.setPrice(5.82);
goodInfoEntity.setTypeId(1);
goodInfoJPA.save(goodInfoEntity);
/**
* 測(cè)試是否添加成功
* 驗(yàn)證主鍵是否存在
*/
Assert.assertNotNull(goodInfoEntity.getTgId());
}
在上面代碼中并沒(méi)有什么特殊的部分,是我們?cè)谑褂?code>Data JPA時(shí)用到的save
方法用于執(zhí)行添加夫嗓,在添加完成后驗(yàn)證主鍵的值是否存在迟螺,NotNull
時(shí)證明添加成功。
測(cè)試刪除
與添加差別不大舍咖,代碼如下所示:
/**
* 測(cè)試刪除商品基本信息
*/
@Test
public void testDelete()
{
//根據(jù)主鍵刪除
goodInfoJPA.delete(3);
//驗(yàn)證數(shù)據(jù)庫(kù)是否已經(jīng)刪除
Assert.assertNull(goodInfoJPA.findOne(3));
}
在上面代碼中矩父,我們根據(jù)主鍵的值進(jìn)行刪除商品的基本信息,執(zhí)行刪除完成后調(diào)用selectOne方法查看數(shù)據(jù)庫(kù)內(nèi)是否已經(jīng)不存在該條數(shù)據(jù)了排霉。
總結(jié)
本章主要介紹了基于SpringBoot
平臺(tái)的兩種單元測(cè)試
方式窍株,一種是在服務(wù)端
采用Spring注入
方式將需要測(cè)試的JPA
或者Service
注入到測(cè)試類(lèi)中,然后調(diào)用方法即可。另外一種則是在客戶端
采用MockMvc
方式測(cè)試Web
請(qǐng)求球订,根據(jù)傳遞的不用參數(shù)以及請(qǐng)求返回對(duì)象反饋信息進(jìn)行驗(yàn)證測(cè)試后裸。
本章代碼已經(jīng)上傳到碼云:
SpringBoot配套源碼地址:https://gitee.com/hengboy/spring-boot-chapter
SpringCloud配套源碼地址:https://gitee.com/hengboy/spring-cloud-chapter