Java SE基礎(chǔ)鞏固(十二):?jiǎn)卧獪y(cè)試

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è)步驟:

  1. 構(gòu)造mockMvc對(duì)象,可以通過(guò)MockMvcBuilders并傳入相應(yīng)的Bean(如果傳入不完整,可能會(huì)導(dǎo)致Bean注入失敗并報(bào)出空指針異常)。
  2. 獲取一個(gè)RequestBuilder對(duì)象,可以通過(guò)MockMvcRequestBuilders.get(),MockMvcRequestBuilders.post()等方法獲取。
  3. 將RequestBuilder對(duì)象傳入mockMvc.perform()方法中,該方法會(huì)返回一個(gè)ResultActions對(duì)象,表示某種行為。
  4. 通過(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

  1. datasource的常規(guī)四個(gè)配置就不多說(shuō)了,不難理解。
  2. spring.jpa.database。因?yàn)轫?xiàng)目中使用了jpa,所以這里配置一下jpa的目標(biāo)數(shù)據(jù)庫(kù)類型是h2。
  3. spring.h2.console.enabled。是否啟用h2控制臺(tái),h2提供了一個(gè)web控制臺(tái),方便用戶增刪改查數(shù)據(jù)。
  4. 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的基本流程:

  1. 編寫(xiě)一個(gè)測(cè)試用例
  2. 運(yùn)行測(cè)試程序,此時(shí)應(yīng)該會(huì)測(cè)試不通過(guò)
  3. 編寫(xiě)代碼,目標(biāo)是使代碼能通過(guò)測(cè)試用例。
  4. 再次運(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í),推薦到這里看看。

?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請(qǐng)聯(lián)系作者
【社區(qū)內(nèi)容提示】社區(qū)部分內(nèi)容疑似由AI輔助生成,瀏覽時(shí)請(qǐng)結(jié)合常識(shí)與多方信息審慎甄別。
平臺(tái)聲明:文章內(nèi)容(如有圖片或視頻亦包括在內(nèi))由作者上傳并發(fā)布,文章內(nèi)容僅代表作者本人觀點(diǎn),簡(jiǎn)書(shū)系信息發(fā)布平臺(tái),僅提供信息存儲(chǔ)服務(wù)。

相關(guān)閱讀更多精彩內(nèi)容

  • 1. 單元測(cè)試入門(mén)——優(yōu)秀基因 單元測(cè)試最初興起于敏捷社區(qū)。1997年,設(shè)計(jì)模式四巨頭之一Erich Gamma和...
    厲鉚兄閱讀 2,697評(píng)論 3 16
  • Spring Cloud為開(kāi)發(fā)人員提供了快速構(gòu)建分布式系統(tǒng)中一些常見(jiàn)模式的工具(例如配置管理,服務(wù)發(fā)現(xiàn),斷路器,智...
    卡卡羅2017閱讀 136,628評(píng)論 19 139
  • 單元測(cè)試 單測(cè)定義 單元測(cè)試(Unit Testing)又稱為模塊測(cè)試, 是針對(duì)程序模塊(軟件設(shè)計(jì)的最小單位)來(lái)進(jìn)...
    運(yùn)維開(kāi)發(fā)筆記閱讀 2,065評(píng)論 0 2
  • 一、百變怪 Mockito Mockito可謂是Java世界的百變怪,使用它,可以輕易的復(fù)制出各種類型的對(duì)象,并與...
    羅力閱讀 4,153評(píng)論 3 18
  • 一 JUnit介紹 JUnit是一個(gè)由Java語(yǔ)言編寫(xiě)的開(kāi)源的回歸測(cè)試框架,由Erich Gamma和Kent B...
    十丈_紅塵閱讀 1,854評(píng)論 0 4

友情鏈接更多精彩內(nèi)容