1 概述
總所周知,測(cè)試是軟件開(kāi)發(fā)中一個(gè)非常重要的環(huán)節(jié),用來(lái)驗(yàn)證程序運(yùn)行是否符合預(yù)期(這個(gè)預(yù)期包括了程序的正確性、性能質(zhì)量等),如果不符合預(yù)期,就根據(jù)測(cè)試的結(jié)果報(bào)告定位問(wèn)題,修復(fù)問(wèn)題,然后再次測(cè)試,這個(gè)過(guò)程往往需要重復(fù)多次,直到程序的運(yùn)行狀況符合預(yù)期才可以嘗試發(fā)布、上線,否則就是對(duì)產(chǎn)品,軟件不負(fù)責(zé)。
根據(jù)分類方式不同,測(cè)試可以分成不同的類型,一般最常見(jiàn)也是最重要的是根據(jù)開(kāi)發(fā)階段劃分,可以劃分出4個(gè)主要的測(cè)試類型:
- 單元測(cè)試
- 集成測(cè)試
- 系統(tǒng)測(cè)試
- 驗(yàn)收測(cè)試
本文主要介紹的就是第一個(gè):單元測(cè)試。作為開(kāi)發(fā)人員,其他三個(gè)可以不那么熟悉,但單元測(cè)試必須要非常熟悉。
下面是從維基百科上摘取的單元測(cè)試的定義:
在計(jì)算機(jī)編程中,單元測(cè)試(英語(yǔ):Unit Testing)又稱為模塊測(cè)試, 是針對(duì)程序模塊(軟件設(shè)計(jì)的最小單位)來(lái)進(jìn)行正確性檢驗(yàn)的測(cè)試工作。程序單元是應(yīng)用的最小可測(cè)試部件。在過(guò)程化編程中,一個(gè)單元就是單個(gè)程序、函數(shù)、過(guò)程等;對(duì)于面向?qū)ο缶幊蹋钚卧褪欠椒?,包括基類(超類)、抽象類、或者派生類(子類)中的方法?/p>
可以說(shuō)單元測(cè)試的目的就是檢驗(yàn)程序的正確性,對(duì)于性能、可用性等沒(méi)有要求,所以也常常說(shuō)單元測(cè)試是最基本的測(cè)試,如果單元測(cè)試都無(wú)法通過(guò),后面的測(cè)試完全沒(méi)必要進(jìn)行。
Java社區(qū)中有很多第三方優(yōu)秀的開(kāi)源測(cè)試框架,例如JUnit,Mockito,TestNG等,下面我將介紹Junit和Mockito的使用。
本文不涉及軟件測(cè)試的理論知識(shí),僅會(huì)談到測(cè)試工具的使用。
2 JUnit
JUnit是一款非常出名的開(kāi)源測(cè)試框架,甚至很多非Java開(kāi)發(fā)者都或多或少聽(tīng)說(shuō)過(guò)。Junit現(xiàn)在(2018-10-15)已經(jīng)發(fā)布了Junit5,多了一些特性,而且最低支持的Java版本的是Java8,但本文不打算使用Junit5,而是采用JUnit4。關(guān)于JUnit5的變化,建議到官網(wǎng)查看。
2.1 下載安裝
官網(wǎng)中提供了JUnit的jar包的下載地址,導(dǎo)入jar包即可使用。如果項(xiàng)目是Maven項(xiàng)目的話,也可以往pom.xml文件里加入junit依賴,如下所示:
<dependency>
<groupId>junit</groupId>
<artifactId>junit</artifactId>
<version>4.12</version>
<scope>test</scope>
</dependency>
2.2 初嘗JUnit
從JUnit4開(kāi)始,我們可以在需要測(cè)試的方法上加上@Test注解來(lái)表示該方法是一個(gè)待測(cè)試的方法。在JUnit3的時(shí)候,要想測(cè)試一個(gè)方法,只能使用“命名模式”將待測(cè)試方法的方法名設(shè)置成testXXX的形式,命名模式有很多缺點(diǎn)和不足,所以推薦大家盡量使用JUnit4之后的版本。下面是一個(gè)JUnit4的簡(jiǎn)單使用案例:
public class ApplicationTest {
private int calculateSum(int a, int b) {
return a + b;
}
@Test
//這里的方法名只是一種習(xí)慣用法,JUnit4并不強(qiáng)制要求必須是testXXX
public void testCalculate() {
Assert.assertEquals(10, calculateSum(5, 5)); //通過(guò)
Assert.assertEquals(10, calculateSum(20, -10)); //通過(guò)
Assert.assertEquals(10, calculateSum(0,0)); //不通過(guò),一般不會(huì)這樣寫(xiě),這里只是為了演示
Assert.assertNotEquals(10, calculateSum(10, 10)); //通過(guò)
}
}
有@Test注解方法是待測(cè)試方法,當(dāng)程序啟動(dòng)的時(shí)候,會(huì)依次調(diào)用所有的待測(cè)試方法,如果在方法里拋出異常,那么該方法就算是測(cè)試失敗了。Assert是org.junit包下的一個(gè)類,提供了豐富的斷言API供我們使用,例如assertEquals用來(lái)斷言期待值和實(shí)際值相等,assertNull用來(lái)斷言參數(shù)是一個(gè)null值。在案例代碼中,只有一個(gè)待測(cè)試方法,該方法的測(cè)試目標(biāo)是calculateSum方法,其中的4個(gè)斷言都是為了驗(yàn)證calculateSum方法的返回值是否符合預(yù)期,啟動(dòng)程序,控制臺(tái)輸出內(nèi)容大致如下所示:
java.lang.AssertionError:
Expected :10
Actual :0
<Click to see difference>
at org.junit.Assert.fail(Assert.java:88)
at org.junit.Assert.failNotEquals(Assert.java:834)
at org.junit.Assert.assertEquals(Assert.java:645)
at org.junit.Assert.assertEquals(Assert.java:631)
at top.yeonon.ApplicationTest.testCalculate(ApplicationTest.java:21)
.......
可以看到方法拋出了一個(gè)AssertionError異常,并打印了異常堆棧,用于定位問(wèn)題所在,除此之外,JUnit還給出了一個(gè)簡(jiǎn)單的測(cè)試報(bào)告,即:
java.lang.AssertionError:
Expected :10
Actual :0
Expected即期待值,使我們?cè)诔绦蛑凶远x的,Actual是calculateSum的返回值,JUnit想要告訴我們的是:你期待的值是10,但實(shí)際值卻是0,即不符合預(yù)期,應(yīng)該嘗試修復(fù)問(wèn)題。
下面是一個(gè)相對(duì)比較復(fù)雜的例子(只是和上面的例子比較,實(shí)際開(kāi)發(fā)中不會(huì)那么簡(jiǎn)單):
public class AppTest {
@Test
public void testAssertEqualAndNotEqual() {
String name = "yeonon";
Assert.assertEquals("yeonon", name);
Assert.assertNotEquals("weiyanyu", name);
}
@Test
public void testArrayEqual() {
byte[] expected = "trial".getBytes();
byte[] actual = "trial".getBytes();
Assert.assertArrayEquals("failure - byte arrays not same", expected, actual);
}
@Test
public void testBoolean() {
Assert.assertTrue(true);
Assert.assertFalse(false);
}
@Test
public void testNull() {
Assert.assertNull(null);
Assert.assertNotNull(new Object());
}
@Test
public void testThatHashItems() {
Assert.assertThat(Arrays.asList("one","two","three"), CoreMatchers.hasItems("one","two"));
}
@Test
public void testThatBoth() {
Assert.assertThat("yeonon",
CoreMatchers.both(
CoreMatchers.containsString("e"))
.and(CoreMatchers.containsString("o")));
}
}
其實(shí)就是試試Assert的各種API,不多說(shuō)了,看看方法名字大概就知道功能了。
順便說(shuō)一下,如果覺(jué)得太多的Assert和CoreMatchers看著煩,可以使用靜態(tài)導(dǎo)入包的方式導(dǎo)入包,例如:
import static org.junit.Assert.*; import static org.hamcrest.CoreMatchers.*;
JUnit的使用就是那么簡(jiǎn)單粗暴直接,這也是為什么JUnit如此火爆的原因之一。當(dāng)然,JUnit不僅僅只有那么點(diǎn)功能,關(guān)于JUnit更高級(jí)的功能,建議到JUnit官網(wǎng)查看官方文檔,它的文檔寫(xiě)的還是不錯(cuò)的。
3 Mockito
Mockito是一款非常強(qiáng)大的測(cè)試框架,其最大的特點(diǎn)就是“Mock”,即模擬。單元測(cè)試的一個(gè)很重要的關(guān)鍵點(diǎn)就是盡量在不涉及依賴關(guān)系的情況下測(cè)試代碼,盡量的模擬真實(shí)的環(huán)境去做測(cè)試。Mockito可以做到這一點(diǎn),他會(huì)將用到的類包裝成一個(gè)Mock對(duì)象,該Mock對(duì)象是可配置的,即可以將其行為配置成我們想要的樣子。
例如在通常的Web開(kāi)發(fā)中,后端會(huì)分為3層,即MVC,負(fù)責(zé)控制層的同學(xué)可能已經(jīng)把控制層寫(xiě)好了,但負(fù)責(zé)模型層的同學(xué)還沒(méi)寫(xiě)好,這時(shí)候控制層的同學(xué)想要對(duì)控制層的功能做測(cè)試,就可以使用Mock模擬出一個(gè)模型層(假設(shè)接口以及定義好了,只是功能還沒(méi)實(shí)現(xiàn)),然后進(jìn)行測(cè)試,這樣就不需要等待負(fù)責(zé)模型層的同學(xué)寫(xiě)完了。
3.1 下載和安裝
和JUnit一樣,可以下載jar包并導(dǎo)入項(xiàng)目,如果項(xiàng)目是Maven項(xiàng)目的話,可以在pom.xml文件里加入如下依賴:
<dependency>
<groupId>org.mockito</groupId>
<artifactId>mockito-core</artifactId>
<version>2.21.0</version>
<scope>test</scope>
</dependency>
在實(shí)際用的時(shí)候,還需要加入JUnit的依賴。(但不是說(shuō)mockito依賴JUnit,僅僅是項(xiàng)目依賴了JUnit)
3.2 簡(jiǎn)單使用
下面僅介紹一個(gè)簡(jiǎn)單例子,如下所示:
public class ApplicationTest {
//有返回值方法
public int calcSum(int a, int b) {
return 1;
}
//無(wú)返回值方法
public void noReturn() {
}
@Test
//設(shè)置單個(gè)返回值
public void testOneReturn() {
ApplicationTest test = mock(ApplicationTest.class);
when(test.calcSum(10, 10)).thenReturn(10);
assertEquals(10,test.calcSum(10, 10));
}
@Test
//設(shè)置多個(gè)返回值,按順序校驗(yàn)
public void testMultiReturn() {
ApplicationTest test = mock(ApplicationTest.class);
when(test.calcSum(10, 10)).thenReturn(10).thenReturn(20);
assertEquals(10, test.calcSum(10, 10));
assertEquals(20, test.calcSum(10, 10));
}
@Test
//根據(jù)輸入?yún)?shù)不同來(lái)定義不同的返回值
public void testMethodParam() {
ApplicationTest test = mock(ApplicationTest.class);
when(test.calcSum(0,0)).thenReturn(1);
when(test.calcSum(1,1)).thenReturn(0);
assertEquals(1, test.calcSum(0, 0));
assertEquals(0, test.calcSum(1, 1));
}
@Test
//返回值不依賴輸入
public void testNotMethodParam() {
ApplicationTest test = mock(ApplicationTest.class);
when(test.calcSum(anyInt(),anyInt())).thenReturn(-1);
assertEquals(-1, test.calcSum(10, 10));
assertEquals(-1, test.calcSum(100, -100));
}
@Test
//根據(jù)返回值的類型來(lái)決定輸出
public void testReturnTypeOfMethodParam() {
ApplicationTest test = mock(ApplicationTest.class);
when(test.calcSum(isA(Integer.class), isA(Integer.class))).thenReturn(-100);
assertEquals(-100, test.calcSum(100, 100));
assertEquals(-100, test.calcSum(111,111));
}
@Test
//行為驗(yàn)證,主要用于驗(yàn)證方法是否被調(diào)用
public void testBehavior() {
ApplicationTest test = mock(ApplicationTest.class);
test.calcSum(10, 10);
test.calcSum(10, 10);
//times(2)表示被調(diào)用兩次
verify(test, times(2)).calcSum(10, 10);
}
}
首先,我們?cè)诿總€(gè)方法里都構(gòu)造了一個(gè)Mock對(duì)象,即
ApplicationTest test = mock(ApplicationTest.class);
構(gòu)造完畢之后,就可以做一些配置了,拿testOneReturn方法來(lái)說(shuō),使用了when(...).thenReturn(...)的方式來(lái)對(duì)mock對(duì)象進(jìn)行配置,when的參數(shù)是一個(gè)方法調(diào)用,例如test.calcSum(10, 10),threnReturn的參數(shù)就是設(shè)置該方法調(diào)用的返回值。所以when(test.calcSum(10, 10)).thenReturn(10);這行代碼的意思就是“當(dāng)調(diào)用test.calcSum(10,10)的時(shí)候,應(yīng)該返回10”,然后調(diào)用assertEquals(10,test.calcSum(10, 10));來(lái)驗(yàn)證是否正確。
這里你可能會(huì)有點(diǎn)奇怪,代碼中的calcSum無(wú)論如何都應(yīng)該返回-1才對(duì)啊,那這行代碼是否能通過(guò)測(cè)試呢?答案是能!因?yàn)槲覀兪褂脀hen(...).thenReturn(...)就是在對(duì)這個(gè)方法調(diào)用做設(shè)置,即這里定義的返回值是我們自定義的,無(wú)論calcSum是如何實(shí)現(xiàn)的,只要我們按照when里規(guī)定的調(diào)用形式(例子中是test.calcSum(10, 10)),那么就一定會(huì)返回配對(duì)的thenReturn()里設(shè)置的值。
其他方法就不多說(shuō)了,和testOneReturn()差不多,而且也做了注釋,應(yīng)該不難理解。
4 SpringBoot Test
4.1 簡(jiǎn)單演示
SpringBoot Test模塊包含了JUnit、Mockito等依賴,在對(duì)Spring Boot項(xiàng)目進(jìn)行測(cè)試的時(shí)候,只需要添加一個(gè)Spring Boot Test的依賴即可,如下所示:
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-test</artifactId>
<version>根據(jù)官網(wǎng)發(fā)布的版本進(jìn)行選擇,記得避免版本沖突</version>
<scope>test</scope>
</dependency>
標(biāo)準(zhǔn)的Spring Boot的MVC三層代碼,我就省略了,非常簡(jiǎn)單,直接來(lái)看測(cè)試類。
package top.yeonon.springtest;
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.test.context.junit4.SpringRunner;
import org.springframework.test.web.servlet.MockMvc;
import org.springframework.test.web.servlet.RequestBuilder;
import org.springframework.test.web.servlet.setup.MockMvcBuilders;
import top.yeonon.springtest.controller.UserController;
import top.yeonon.springtest.repository.UserRepository;
import top.yeonon.springtest.service.UserService;
import static org.springframework.test.web.servlet.request.MockMvcRequestBuilders.*;
import static org.springframework.test.web.servlet.result.MockMvcResultMatchers.content;
import static org.springframework.test.web.servlet.result.MockMvcResultMatchers.status;
/**
* @Author yeonon
* @date 2018/10/15 0015 18:21
**/
@RunWith(SpringRunner.class)
@SpringBootTest
public class UserServiceTest {
private MockMvc mockMvc;
@Autowired
private UserController userController;
@Autowired
private UserService userService;
@Autowired
private UserRepository userRepository;
@Before
public void setUp() {
//構(gòu)造mockMvc
mockMvc = MockMvcBuilders.standaloneSetup(userController, userService, userRepository).build();
}
@Test
public void testUserService() throws Exception {
RequestBuilder request = null;
//1. 注冊(cè)用戶
request = post("/users")
.param("username", "yeonon")
.param("password", "admin")
.contentType(MediaType.APPLICATION_JSON_UTF8);
mockMvc.perform(request)
.andExpect(status().is(200))
.andExpect(content().string("注冊(cè)成功")); //在業(yè)務(wù)代碼中,如果成功就會(huì)返回“注冊(cè)成功”;
//2. 根據(jù)id獲取用戶
request = get("/users/1");
mockMvc.perform(request)
.andExpect(status().is(200))
.andExpect(content().string("{\"id\":1,\"username\":\"yeonon\",\"password\":\"admin\"}"));
//3. 修改用戶信息
request = put("/users")
.param("username", "weiyanyu")
.param("password", "aaa")
.param("id", "1");
mockMvc.perform(request)
.andExpect(status().is(200))
.andExpect(content().string("更新成功"));
//4. 再次獲取信息
request = get("/users/1");
mockMvc.perform(request)
.andExpect(status().is(200))
.andExpect(content().string("{\"id\":1,\"username\":\"weiyanyu\",\"password\":\"aaa\"}"));
//5. 刪除用戶
request = delete("/users/1");
mockMvc.perform(request)
.andExpect(status().is(200))
.andExpect(content().string("刪除成功"));
}
}
mockMvc是Spring封裝的一個(gè)類,從名字可以看出來(lái)是針對(duì)MVC的一個(gè)模擬,實(shí)際上也確實(shí)如此。整個(gè)測(cè)試過(guò)程可以分為以下幾個(gè)步驟:
- 構(gòu)造mockMvc對(duì)象,可以通過(guò)MockMvcBuilders并傳入相應(yīng)的Bean(如果傳入不完整,可能會(huì)導(dǎo)致Bean注入失敗并報(bào)出空指針異常)。
- 獲取一個(gè)RequestBuilder對(duì)象,可以通過(guò)MockMvcRequestBuilders.get(),MockMvcRequestBuilders.post()等方法獲取。
- 將RequestBuilder對(duì)象傳入mockMvc.perform()方法中,該方法會(huì)返回一個(gè)ResultActions對(duì)象,表示某種行為。
- 通過(guò)返回的ResultActions對(duì)象提供的API來(lái)對(duì)結(jié)果做驗(yàn)證,例如andExpect,andDo,andReturn等。其中andExpect接受的參數(shù)是一個(gè)ResultMatcher類型的對(duì)象,在MockMvcResultMatchers中有很多使用的方法可以供我們使用,例如status,content等。
這就完成了一次web測(cè)試。這里順便說(shuō)一下編碼問(wèn)題,在這個(gè)測(cè)試環(huán)境下,默認(rèn)的編碼方式不是UTF-8(好像是ISO-xxx,具體忘了),所以如果controller返回的有中文且不做特殊處理的話,可能會(huì)出錯(cuò)。一個(gè)解決方案是,修改controller中的@RequestMapping上的produces屬性,如下所示:
@DeleteMapping(value = "{id}",produces = "application/json;charset=UTF-8")
public String deleteUser(@PathVariable("id") Long id) {
return userService.deleteUser(id);
}
4.2 h2內(nèi)存數(shù)據(jù)庫(kù)
該小測(cè)試項(xiàng)目中,其實(shí)用到了h2數(shù)據(jù)庫(kù)。h2是一款用Java語(yǔ)言開(kāi)發(fā)的數(shù)據(jù)庫(kù),可直接嵌入到應(yīng)用程序中,與應(yīng)用程序打包發(fā)布,不受平臺(tái)限制,它還支持內(nèi)存模式,所以非常適合用于測(cè)試環(huán)境。一般為了方便,在測(cè)試環(huán)境使用的時(shí)候,會(huì)將項(xiàng)目的.sql文件載入到h2中,然后使用內(nèi)存模式進(jìn)行測(cè)試,在內(nèi)存模式下,所有的操作都在內(nèi)存中進(jìn)行,不會(huì)進(jìn)行持久化,所以無(wú)需擔(dān)心會(huì)弄臟生產(chǎn)環(huán)境的數(shù)據(jù)庫(kù)。
spring boot對(duì)h2也有支持,我們只需要在項(xiàng)目中加入h2的相關(guān)依賴并做少量配置即可使用,如下所示:
<dependency>
<groupId>com.h2database</groupId>
<artifactId>h2</artifactId>
<version>1.4.196</version>
</dependency>
配置如下所示:
spring.datasource.driver-class-name=org.h2.Driver
spring.datasource.url=jdbc:h2:mem:h2test;DB_CLOSE_DELAY=-1;DB_CLOSE_ON_EXIT=FALSE
spring.datasource.username=admin
spring.datasource.password=admin
spring.jpa.database=h2
spring.jpa.hibernate.ddl-auto=update
spring.h2.console.enabled=true
spring.h2.console.path=/console
- datasource的常規(guī)四個(gè)配置就不多說(shuō)了,不難理解。
- spring.jpa.database。因?yàn)轫?xiàng)目中使用了jpa,所以這里配置一下jpa的目標(biāo)數(shù)據(jù)庫(kù)類型是h2。
- spring.h2.console.enabled。是否啟用h2控制臺(tái),h2提供了一個(gè)web控制臺(tái),方便用戶增刪改查數(shù)據(jù)。
- spring.h2.console.path??刂婆_(tái)的路徑,例如上面配置的是/console,在使用的時(shí)候就可以在瀏覽器地址欄輸入http://localhost:port/console進(jìn)入控制臺(tái)。
啟動(dòng)項(xiàng)目之后,在瀏覽器里輸入url進(jìn)入h2控制臺(tái),如下所示:
做好配置之后,輸入用戶名密碼,點(diǎn)擊Connect即可進(jìn)入控制臺(tái)界面,如下所示:
在空白處可以輸入符合SQL規(guī)范的語(yǔ)句對(duì)數(shù)據(jù)庫(kù)進(jìn)行操作,左側(cè)邊欄可以看到有一個(gè)T_USER數(shù)據(jù)庫(kù)表,這是JPA幫我們創(chuàng)建的,在h2中,表的名字默認(rèn)都是大寫(xiě)的,但是在寫(xiě)SQL語(yǔ)句的時(shí)候可以使用小寫(xiě),h2會(huì)幫我們轉(zhuǎn)換成大寫(xiě)形式。如下所示:
關(guān)于h2數(shù)據(jù)庫(kù)的介紹就先這樣,因?yàn)閔2的接口也符合JDBC規(guī)范,所以如果熟悉JDBC的話,不需要太關(guān)注h2的操作細(xì)節(jié)。
5 TDD
TDD即Test-Driven Development (測(cè)試驅(qū)動(dòng)開(kāi)發(fā))。名字可能不那么好理解其意義,什么是測(cè)測(cè)試驅(qū)動(dòng)開(kāi)發(fā)?為什么要用測(cè)試來(lái)啟動(dòng)開(kāi)發(fā)?測(cè)試如何驅(qū)動(dòng)開(kāi)發(fā)的?下面將圍繞這三個(gè)問(wèn)題簡(jiǎn)單介紹一下TDD。
5.1 什么是測(cè)試驅(qū)動(dòng)開(kāi)發(fā)
如果之前沒(méi)有接觸過(guò)類似的概念,大多數(shù)人對(duì)測(cè)試的認(rèn)識(shí)應(yīng)該是:先編寫(xiě)代碼,完成之后再進(jìn)行測(cè)試,測(cè)試的目的是檢驗(yàn)程序的正確性、性能質(zhì)量、可用性、可伸縮性等。而測(cè)試驅(qū)動(dòng)開(kāi)發(fā)則恰恰相反,TDD提倡的是先編寫(xiě)測(cè)試程序,然后編寫(xiě)代碼滿足測(cè)試成功,使得測(cè)試程序能通過(guò),只要測(cè)試用例寫(xiě)的好,重構(gòu)代碼的時(shí)候需要考慮的事情就可以少很多,只需要讓代碼能通過(guò)測(cè)試即可。
5.2 為什么需要測(cè)試驅(qū)動(dòng)開(kāi)發(fā)
TDD和傳統(tǒng)的先開(kāi)發(fā)后測(cè)試的方式相比,至少有如下幾個(gè)好處:
- 降低開(kāi)發(fā)者的負(fù)擔(dān),開(kāi)發(fā)者只需要編寫(xiě)代碼通過(guò)測(cè)試用例即可,不需要在各種亂七八糟的需求中糾結(jié)。
- 對(duì)需求變化有很強(qiáng)的適應(yīng)性,但需求發(fā)生變化的時(shí)候,只需要根據(jù)需求修改測(cè)試,然后再次編寫(xiě)或者修改代碼來(lái)適應(yīng)測(cè)試用例,避免“撿了芝麻,丟了西瓜”的情況發(fā)生。
- 需求明確,提前編寫(xiě)測(cè)試可以督促我們理清需求,而不是寫(xiě)代碼寫(xiě)到一半才發(fā)現(xiàn)需求不明確,導(dǎo)致“返工”。
- 效率高,所謂磨刀不誤砍柴工,雖然提前編寫(xiě)測(cè)試需要花費(fèi)很長(zhǎng)的時(shí)間和很多的精力,但這些消耗都是值得的。如果不提前編寫(xiě)測(cè)試,最終也需要自己進(jìn)行手動(dòng)測(cè)試,而手動(dòng)測(cè)試又需要花時(shí)間去啟動(dòng)應(yīng)用,在各個(gè)界面之間來(lái)回跳轉(zhuǎn),其實(shí)花費(fèi)的時(shí)間比提前編寫(xiě)自動(dòng)化測(cè)試多得多。
5.3 測(cè)試如何驅(qū)動(dòng)開(kāi)發(fā)
其實(shí)上面隱隱有提到過(guò)這點(diǎn),但沒(méi)有明確給出一個(gè)思路或者步驟,下面是TDD的基本流程:
- 編寫(xiě)一個(gè)測(cè)試用例
- 運(yùn)行測(cè)試程序,此時(shí)應(yīng)該會(huì)測(cè)試不通過(guò)
- 編寫(xiě)代碼,目標(biāo)是使代碼能通過(guò)測(cè)試用例。
- 再次運(yùn)行測(cè)試程序,此時(shí)如果還是測(cè)試不通過(guò),回到步驟3,如此往復(fù),直到測(cè)試通過(guò)。
這里有一個(gè)問(wèn)題,步驟2顯然是肯定會(huì)失敗的(因?yàn)檫€沒(méi)有編寫(xiě)具體的代碼),為什么還要運(yùn)行一次測(cè)試程序呢?因?yàn)槭〉脑蛴泻芏啵灰欢ň褪且驗(yàn)檫€沒(méi)有編寫(xiě)具體代碼導(dǎo)致,也有可能是測(cè)試環(huán)境有問(wèn)題導(dǎo)致的,所以先運(yùn)行一次,查看錯(cuò)誤報(bào)告,如果是測(cè)試環(huán)境有問(wèn)題,那么就先嘗試修復(fù)測(cè)試環(huán)境,否則如果在有問(wèn)題的測(cè)試環(huán)境下進(jìn)行開(kāi)發(fā),可能會(huì)導(dǎo)致無(wú)論怎么編寫(xiě)程序都不可能通過(guò)測(cè)試的情況(因?yàn)槊看螠y(cè)試都會(huì)因?yàn)闇y(cè)試環(huán)境的問(wèn)題導(dǎo)致測(cè)試失敗)。
實(shí)際上,TDD遠(yuǎn)不止如此,還有很多很多好處,也有一些弊端,因?yàn)槲冶救藢?duì)TDD的了解也不算多,平時(shí)因?yàn)楸容^懶,也沒(méi)有養(yǎng)成先寫(xiě)測(cè)試的習(xí)慣,所以就不多說(shuō)了,建議自行搜索相關(guān)資料進(jìn)行學(xué)習(xí),這里就當(dāng)是“拋磚引玉”吧。
6 小結(jié)
本文介紹了單元測(cè)試的概念,順帶介紹了兩個(gè)測(cè)試框架JUnit,Mockito的簡(jiǎn)單使用,隨后還結(jié)合Spring Boot項(xiàng)目做了一次小實(shí)踐,希望對(duì)讀者有幫助。最后還簡(jiǎn)單介紹了TDD(測(cè)試驅(qū)動(dòng)開(kāi)發(fā)),TDD是敏捷開(kāi)發(fā)中的一項(xiàng)核心技術(shù),可以有效的提高開(kāi)發(fā)效率和產(chǎn)品質(zhì)量,TDD其實(shí)也算是一門(mén)學(xué)問(wèn),如果想要深入學(xué)習(xí),推薦到這里看看。


