偷偷摘套内射激情视频,久久精品99国产国产精,中文字幕无线乱码人妻,中文在线中文a,性爽19p

單元測(cè)試難?來(lái)試試這些套路

開(kāi)發(fā) 開(kāi)發(fā)工具
測(cè)試不應(yīng)該是一門很高大尚的技術(shù),應(yīng)該是我們技術(shù)人的基本功。但現(xiàn)在好像慢慢地,單元測(cè)試已經(jīng)脫離了基本功的范疇。筆者曾經(jīng)在不同團(tuán)隊(duì)中推過(guò)單元測(cè)試,要求過(guò)覆蓋率,但發(fā)現(xiàn)實(shí)施下去很難。

測(cè)試不應(yīng)該是一門很高大尚的技術(shù),應(yīng)該是我們技術(shù)人的基本功。但現(xiàn)在好像慢慢地,單元測(cè)試已經(jīng)脫離了基本功的范疇。筆者曾經(jīng)在不同團(tuán)隊(duì)中推過(guò)單元測(cè)試,要求過(guò)覆蓋率,但發(fā)現(xiàn)實(shí)施下去很難。后來(lái)在不停地刻意練習(xí)后,發(fā)現(xiàn)阻礙寫UT的只是筆者的心魔,并不是時(shí)間和項(xiàng)目的問(wèn)題。在經(jīng)過(guò)一些項(xiàng)目的實(shí)踐后,也是有了一些自己的理解和實(shí)踐,希望和大家分享一下,和大家探討下如何克服“單元測(cè)試”的心魔。

內(nèi)功

前人們?cè)趩卧獪y(cè)試方面的研究很多,有很多的方法論,我們可以拿來(lái)即用。我簡(jiǎn)單介紹兩個(gè)方法論,一個(gè)概念。希望大家可以查閱更多的資料,凝聚自己的內(nèi)功心法。

TDD

Test Driven Development,也被認(rèn)為是Test Driven Design,我們這里按第一種定義來(lái)聊。TDD一改以往的破壞性測(cè)試的思維方式,測(cè)試在先、編碼在后,更符合“缺陷預(yù)防”的思想。簡(jiǎn)單來(lái)說(shuō),TDD的流程是“紅-綠-重構(gòu)”三個(gè)步驟的循環(huán)往復(fù)。

  • 紅:測(cè)試先行,現(xiàn)在還沒(méi)有任何實(shí)現(xiàn),跑UT的時(shí)候肯定不過(guò),測(cè)試狀態(tài)是紅燈。編譯失敗也屬于“紅”的一種情況。
  • 綠:當(dāng)我們用最快,最簡(jiǎn)單的方式先實(shí)現(xiàn),然后跑一遍UT,測(cè)試會(huì)通過(guò),變成“綠”的狀態(tài)。
  • 重構(gòu):看一下系統(tǒng)中有沒(méi)有要重構(gòu)的點(diǎn),重構(gòu)完,一定要保證測(cè)試是“綠”的。

業(yè)界有很多TDD的呼聲,也有TDD已死的文章。方法本來(lái)沒(méi)有對(duì)錯(cuò),只有優(yōu)劣,我們要辯證地來(lái)看。只能說(shuō)TDD不是一個(gè)銀彈,不能解決所有問(wèn)題。以筆者自己的經(jīng)驗(yàn),TDD比較適用于輸入輸出很明確的CASE,很多時(shí)候我們?cè)诿饕环N新的模式的時(shí)候,可能并不太適用。

如果你和前端已經(jīng)商議好了接口的出參、入?yún)ⅲ梢試L試一下TDD,一種新的思路,新的思想。

BDD

嚴(yán)格來(lái)說(shuō)BDD是TDD衍生出來(lái)的一個(gè)小分支。但也可以用于一些不同維度的東西。概念大家自行尋找資料。這里講一下BDD的一點(diǎn)實(shí)踐經(jīng)驗(yàn)。直接上代碼:

@RunWith(SpringBootRunner.class) 
@DelegateTo(SpringJUnit4ClassRunner.class)
@SpringBootTest(classes = {Application.class})
public class ApiServiceTest {

@Autowired
ApiService apiService;

@Test
public void testMobileRegister() {
AlispResult<Map<String, Object>> result = apiService.mobileRegister();
System.out.println("result = " + result);
Assert.assertNotNull(result);
Assert.assertEquals(54,result.getAlispCode().longValue());

AlispResult<Map<String, Object>> result2 = apiService.mobileRegister();
System.out.println("result2 = " + result2);
Assert.assertNotNull(result2);
Assert.assertEquals(9,result2.getAlispCode().longValue());

AlispResult<Map<String, Object>> result3 = apiService.mobileRegister();
System.out.println("result3 = " + result3);
Assert.assertNotNull(result3);
Assert.assertEquals(200,result3.getAlispCode().longValue());
}

@Test
public void should_return_mobile_is_not_correct_when_register_given_a_invalid_phone_number() {
AlispResult<Map<String, Object>> result = apiService.mobileRegister();
Assert.assertNotNull(result);
Assert.assertFalse(result.isSuccess());
}
}

第一個(gè)UT是以方法維度,把所有場(chǎng)景放到一個(gè)方法來(lái)測(cè)試。

第二個(gè)UT是以case為角度,針對(duì)每個(gè)case單獨(dú)的測(cè)試。

其實(shí)TDD里面有一個(gè)概念是隔離性,單元測(cè)試之間應(yīng)該隔離開(kāi),不要互相干擾。另外,從命名上,第二種也更好一點(diǎn)。我個(gè)人還是比較推薦以下命名方式的:

  • should:返回值,應(yīng)該產(chǎn)生的結(jié)果
  • when:哪個(gè)方法
  • given:哪個(gè)場(chǎng)景

另外BDD或者TDD中也有Task的概念,寫代碼之前先準(zhǔn)備好case。大家可以看一些BDD的文章,自己體會(huì)。如果對(duì)這個(gè)感興趣,可以在評(píng)論區(qū)探討。

測(cè)試金字塔

??

??

 

上圖來(lái)自martin fowler博客的TestPyramid[1]一文,也可以讀一下《Practical Test Pyramid》[2]。特別棒的文章,希望大家可以去讀一讀。

上面的金字塔的意思是,從Unit到Service,再到UI,速度越來(lái)越慢,成本也越來(lái)越高。

我們可以從服務(wù)端的角度把這三層稍微改一下:

  • 契約測(cè)試:測(cè)試服務(wù)與服務(wù)之間的契約,接口保證。代價(jià)最高,測(cè)試速度最慢。
  • 集成測(cè)試(Integration):集成當(dāng)前spring容器、中間件等,對(duì)服務(wù)內(nèi)的接口,或者其他依賴于環(huán)境的方法的測(cè)試。
// 加載spring環(huán)境 
@RunWith(SpringBootRunner.class)
@DelegateTo(SpringJUnit4ClassRunner.class)
@SpringBootTest(classes = {Application.class})
public class ApiServiceTest {

@Autowired
ApiService apiService;
//do some test
}

單元測(cè)試(Unit Test):純函數(shù),方法的測(cè)試,不依賴于spring容器,也不依賴于其他的環(huán)境。

??

??

 

我們現(xiàn)在寫測(cè)試,一般是單元測(cè)試和集成測(cè)試兩層。針對(duì)具體場(chǎng)景,選擇適合自己的測(cè)試粒度。

招數(shù)

其實(shí)寫單元測(cè)試是有一些招數(shù)的,下面會(huì)介紹筆者很喜歡的一種單元測(cè)試代碼組織結(jié)構(gòu),也會(huì)介紹一些常用的招數(shù),以及使用場(chǎng)景。

常見(jiàn)問(wèn)題

  • 一個(gè)類里面測(cè)試太多怎么辦?
  • 不知道別人mock了哪些數(shù)據(jù)怎么辦?
  • 測(cè)試結(jié)構(gòu)太復(fù)雜?
  • 測(cè)試莫名奇妙起不來(lái)?

Fixture-Scenario-Case

FSC(Fixture-Scenario-Case)是一種組織測(cè)試代碼的方法,目標(biāo)是盡量將一些MOCK信息在不同的測(cè)試中共享。其結(jié)構(gòu)如下:

??

??

 

  • 通過(guò)組合Fixture(固定設(shè)施),來(lái)構(gòu)造一個(gè)Scenario(場(chǎng)景)。
  • 通過(guò)組合Scenario(場(chǎng)景)+ Fixture(固定設(shè)施),構(gòu)造一個(gè)case(用例)。

下面是一個(gè)FSC的示例:

??

?

  • Case:當(dāng)用戶正常登錄后,獲取當(dāng)前登錄信息時(shí),應(yīng)該返回正確的用戶信息。這是一個(gè)簡(jiǎn)單的用戶登錄的case,這個(gè)case里面總共有兩個(gè)動(dòng)作、場(chǎng)景,一個(gè)是用戶正常登錄,一個(gè)是獲取用戶信息,演化為兩個(gè)scenario。
  • Scenario:用戶正常登錄,肯定需要登錄參數(shù),如:手機(jī)號(hào)、驗(yàn)證碼等,另外隱含著數(shù)據(jù)庫(kù)中應(yīng)該有一個(gè)對(duì)應(yīng)的用戶,如果登錄時(shí)需要與第三方系統(tǒng)進(jìn)行交互,還需要對(duì)第三方系統(tǒng)進(jìn)行mock或者stub。獲取用戶信息時(shí),肯定需要上一階段頒發(fā)的憑證信息,另外該憑證可能是存儲(chǔ)于一些緩存系統(tǒng)的,所以還需要對(duì)中間件進(jìn)行mock或者stub。
  • Fixture
  • 利用Builder模式構(gòu)造請(qǐng)求參數(shù)。
  • 利用DataFile來(lái)存儲(chǔ)構(gòu)造用戶的信息,例如DB transaction進(jìn)行數(shù)據(jù)的存儲(chǔ)和隔離。
  • 利用Mockito進(jìn)行三方系統(tǒng)、中間件的Mock。

當(dāng)這樣組織測(cè)試時(shí),如果另外一個(gè)Case中需要用戶登錄,則可以直接復(fù)用用戶登錄的Scenario。也可以通過(guò)復(fù)用Fixture來(lái)減少數(shù)據(jù)的Mock。下面我們來(lái)詳細(xì)解釋看一下每一層如何實(shí)現(xiàn),show the code。

Case

case是用例的意思,在這里用例是場(chǎng)景和一些固定設(shè)施的組合。這里要注意的是,盡量不要直接修改接口的數(shù)據(jù),一個(gè)場(chǎng)景所依賴的環(huán)境應(yīng)該是另一個(gè)場(chǎng)景的輸出。當(dāng)然有些特定場(chǎng)景下,還是需要直接改數(shù)據(jù)的,這里不是禁止,而是建議。

public class GetUserInfoCase extends BaseTest { 
private String accessToken;

@Autowired
private UserFixture userFixture;

/**
* 通用場(chǎng)景的mock
*/
@Before
public void setUp() {
//三方系統(tǒng)mock
userFixture.whenFetchUserInfoThenReturn("1", new UserVO());

//依賴的其他場(chǎng)景
accessToken = new SimpleLoginScenario()
.mobile("1234567890")
.code("aaa")
.login()
.getAccessToken();
}

/**
* BDD的三段式
*/
@Test
public void should_return_user_info_when_user_login_given_a_effective_access_token() {
Response userInfoResponse = new GetUserInfoScenario()
.accessToken(accessToken)
.getUserInfo();

assertThat(userInfoResponse.jsonPath().getString("id"), equals("1"));
}
}

Scenario

JUNIT的用法就不說(shuō)了,相信大家都了解,這里提兩個(gè)框架REST Assured和Mock MVC。這兩個(gè)框架都可以用來(lái)做接口測(cè)試,Mock MVC是spring原生的,可以指定加載的Resource,一定程度上可以提升UT速度,但是和spring是耦合在一起的。REST Assured是脫離Spring的,可以理解為利用http進(jìn)行接口的測(cè)試,耦合性更低,使用靈活。兩者各有千秋,筆者比較推薦REST Assured。我們看一下,一個(gè)REST Assured打造的Scenario怎么寫,怎么用?

@Data 
public class SimpleLoginScenario {
// 請(qǐng)求參數(shù)
private String mobile;
private String code;

// 登錄結(jié)果
private String accessToken;

public SimpleLoginScenario mobile(String mobile) {
this.mobile = mobile;
return this;
}

public SimpleLoginScenario code(String code) {
this.code = code;
return this;
}

//登錄,并且保存AccessToken,這里返回自身,是因?yàn)橛锌赡芊祷貐?shù)是多個(gè)。
public SimpleLoginScenario login() {
Response response = loginWithResponse();
this.accessToken = response.jsonPath().getString("accessToken");
return this;
}

//利用RestAssured進(jìn)行登錄,這個(gè)方法可以是public,也可以通過(guò)參數(shù)傳遞一些驗(yàn)證方法
private Response loginWithResponse() {
return RestAssured.get(API_PATH, ImmutableMap.of("mobile", mobile, "code", code))
.thenReturn();
}

}

Fixture

固定設(shè)施部分,主要是用來(lái)提供一些固定的組件和數(shù)據(jù)。盡量的讓這部分東西有復(fù)用性,如果沒(méi)復(fù)用性,盡量和測(cè)試放在一起,不要干擾他人。

(1)方法

(a)Mock

mockito挺通用的,而且spring也提供了@MockBean,可以直接將Mock一個(gè)bean放入spring的容器中。然后可以利用mockito提供的方法對(duì)方法進(jìn)行模擬或者驗(yàn)證。代碼示例:

public class MockitoTest { 
@MockBean(classes = CacheImpl.class)
private Cache cache;

@Test
public void should_return_success() {
// 固定參數(shù),固定返回值
Mockito.when(cache.get("KEY")).thenReturn("VALUE");

// 動(dòng)態(tài)參數(shù),固定返回值
Mockito.when(cache.get(Mockito.anyString())).thenReturn("VALUE");

// 動(dòng)態(tài)參數(shù),固定返回值
Mockito.when(cache.get(Mockito.anyString())).then((invocation) -> {
String key = (String) invocation.getArguments()[0];
return "VALUE";
});

// 固定參數(shù),異常
Mockito.when(cache.get("KEY")).thenThrow(new RuntimeException("ERROR"));

// 驗(yàn)證調(diào)用次數(shù)
Mockito.verify(cache.get("KEY"), Mockito.times(1));
}
}

(b)stub

stub是打樁,關(guān)于打樁和mock的區(qū)別,請(qǐng)自行百度,這里只是想展示一下,在spring的環(huán)境下,覆蓋原有bean達(dá)到stub的效果。

//使用spring的@Primary來(lái)替換一個(gè)bean,如果不同的測(cè)試需要的bean不同,推薦使用@Configuration + @Import的方式,動(dòng)態(tài)加載Bean 
@Primary
@Component("cache")
public class CacheStub implements Cache {

@Override
public String get(String key) {
return null;
}

@Override
public int setex(String key, Integer ttl, String element) {
return 0;
}

@Override
public int incr(String key, Integer ttl) {
return 0;
}

@Override
public int del(String key) {
return 0;
}
}

(c)嵌入式DB

這里簡(jiǎn)單介紹幾種嵌入式DB,可以自行選擇使用。

??

??

(d)直連DB + Transaction

  • 除了使用嵌入式的DB,也可以直連環(huán)境,但不推薦,因?yàn)榄h(huán)境上的數(shù)據(jù)是多變的,如果測(cè)試出現(xiàn)問(wèn)題,排查的復(fù)雜度會(huì)增加。這里其實(shí)想強(qiáng)調(diào)下@Transactional。因?yàn)镸ock的數(shù)據(jù)最好做到隔離,比如一個(gè)接口的操作是批量刪除數(shù)據(jù),有可能會(huì)把一個(gè)其他測(cè)試依賴的數(shù)據(jù)刪除掉,這樣問(wèn)題一旦出現(xiàn)很難排查,因?yàn)閱为?dú)跑每個(gè)測(cè)試都是通過(guò)的,但是一起跑就會(huì)出問(wèn)題。這里推薦兩種做法:
  • 使用@Transactional在一些測(cè)試的類上,這樣在跑完測(cè)試后,數(shù)據(jù)不會(huì)commit,會(huì)回滾。但如果測(cè)試中對(duì)事物的傳播有特殊要求,可能不適用。

通用的trancateAll和initSQL通過(guò)在每個(gè)測(cè)試前跑清除數(shù)據(jù)、mock數(shù)據(jù)的腳本,來(lái)達(dá)到每個(gè)測(cè)試對(duì)應(yīng)一個(gè)隔離環(huán)境,這樣數(shù)據(jù)間就不會(huì)產(chǎn)生干擾。

(e)PowerMock

PowerMock是用來(lái)創(chuàng)建一些靜態(tài)方法的Mock的,如果你的代碼中會(huì)調(diào)用一些靜態(tài)方法,但是靜態(tài)方法依賴于一些其他復(fù)雜的邏輯或者資源??梢允褂眠@個(gè)包。

PowerMockito.mockStatic(C.class); 
PowerMockito.when(C.isTrue()).thenReturn(true);

注意:

  • PowerMock不僅僅是用來(lái)mock靜態(tài)方法的。
  • 不建議mock靜態(tài)方法,因?yàn)殪o態(tài)方法的使用場(chǎng)景都是些純函數(shù),大部分的純函數(shù)不需要mock。部分靜態(tài)方法依賴于一些環(huán)境和數(shù)據(jù),針對(duì)這些方法,需要考慮下到底是要mock其依賴的數(shù)據(jù)和方法,還是真的要mock這個(gè)函數(shù),因?yàn)橐坏﹎ock了這個(gè)函數(shù),意味著隱藏了細(xì)節(jié)。

(2)數(shù)據(jù)

(a)Builder模式

數(shù)據(jù)最簡(jiǎn)單的mock方式就是Builder,然后自己手填各種參數(shù),但有些對(duì)象有幾十個(gè)字段,而你的一個(gè)測(cè)試只需要改其中的兩個(gè)字段,你該怎么辦?Copy、Paste?

@Builder 
@Data
public class UserVO {
private String name;
private int age;
private Date birthday;
}

public class UserVOFixture {
// 注意:這里是個(gè)Supplier,并不是一個(gè)靜態(tài)的實(shí)例,這樣可以保證每個(gè)使用方,維護(hù)自己的實(shí)例
public static Supplier<UserVO.UserVOBuilder> DEFAULT_BUILDER = () -> UserVO.builder().name("test").age(11).birthday(new Date());
}

(b)數(shù)據(jù)文件

有時(shí)候通過(guò)builder構(gòu)造對(duì)象的時(shí)候,字段太多,并且數(shù)據(jù)的來(lái)源是前端或者其他服務(wù)提供的json。這個(gè)時(shí)候可以將這個(gè)數(shù)據(jù)存儲(chǔ)到文件中,利用一些工具方法,將數(shù)據(jù)讀取成制定的文件。這也是數(shù)據(jù)mock的常用手段。我這里是以json為例,其實(shí)sql等數(shù)據(jù)也可以這樣。

數(shù)據(jù)文件的優(yōu)點(diǎn):可承載的數(shù)據(jù)量大、編輯方便。

public class UserVOFixture { 

public static UserVO readUser(String filename) {
return readJsonFromResource(filename, UserVO.class);
}

public static <T> T readJsonFromResource(String filename, Class<T> clazz) {
try {
String jsonString = StreamUtils.copyToString(new ClassPathResource(filename).getInputStream(), Charset.defaultCharset());
return JSON.parseObject(jsonString, clazz);
} catch (IOException e) {
return null;
}
}
}

使用場(chǎng)景

在筆者的實(shí)踐中, 目前主要把FSC是用在接口測(cè)試上,也就是測(cè)試金字塔的Integration Test部分,放在這個(gè)層次,有幾個(gè)原因:

  • FSC本身會(huì)給測(cè)試帶來(lái)復(fù)雜度,而UnitTest應(yīng)該簡(jiǎn)單,如果UnitTest本身都很復(fù)雜了,項(xiàng)目帶來(lái)難以估量的測(cè)試成本。
  • Fixture其實(shí)可以在任何場(chǎng)景中使用,因?yàn)槭堑讓拥膹?fù)用。

缺陷

  • 增加了代碼復(fù)雜度。
  • 通過(guò)IDE工具無(wú)法直接定位的測(cè)試文件,折衷的方案是case的命名符合ResouceTest的命名。

校場(chǎng)

從簡(jiǎn)單到復(fù)雜

上面我們介紹了測(cè)試金字塔,越靠上層,復(fù)雜度越高。所以剛接觸單元測(cè)試的同學(xué),可以從“單元測(cè)試”的層次開(kāi)始練習(xí),可以練習(xí)Builder,F(xiàn)ixture怎么寫,方法怎么Mock。如果你感覺(jué)這些都到了拿來(lái)即用的階段,那就可以往上層寫,考慮下怎么給項(xiàng)目增加一些通用的基礎(chǔ)設(shè)施,來(lái)減少測(cè)試的整體復(fù)雜度。

刻意練習(xí):3F原則

刻意練習(xí),簡(jiǎn)而言之,就是刻意的練習(xí),它突出的是有目的的練習(xí)。刻意練習(xí)也有它的一整套過(guò)程,在這個(gè)過(guò)程里,你需要遵守它的3F法則:

  • 第一,F(xiàn)ocus(保持專注)。
  • 第二,F(xiàn)eedback(注重反饋,收集信息)。
  • 第三,F(xiàn)ix it(糾正錯(cuò)誤,并且進(jìn)行修改)。

UT本身是一項(xiàng)技術(shù),是需要我們打磨、練習(xí)的,最好的練習(xí)方式,就是刻意練習(xí),如果有決心,一個(gè)周末在家刻意練習(xí),為項(xiàng)目中的部分場(chǎng)景加上UT,相信收獲會(huì)很豐富。

打造自己的測(cè)試環(huán)境

自己要不斷的摸索,什么樣的組織方式,什么樣的工具方法是適合自己項(xiàng)目的。軟件工程中沒(méi)有銀彈,沒(méi)有最好,只有合適。

常見(jiàn)問(wèn)題

  • 應(yīng)不應(yīng)該連日常環(huán)境進(jìn)行測(cè)試?
  • 個(gè)人不建議直接連日常環(huán)境進(jìn)行測(cè)試,如果兩個(gè)人同時(shí)在跑測(cè)試,那么很有可能測(cè)試環(huán)境的數(shù)據(jù)會(huì)處于混亂狀態(tài)。而且UT盡可能不要依賴過(guò)多的外部環(huán)境,依賴越多越復(fù)雜。測(cè)試還是簡(jiǎn)單點(diǎn)好。
  • 一個(gè)類里面測(cè)試太多怎么辦?
  • 考慮按測(cè)試的case區(qū)分,也可按測(cè)試的方法區(qū)分,也可以按正常、異常場(chǎng)景區(qū)分。
  • 不知道別人mock了哪些數(shù)據(jù)怎么辦?
  • 盡量讓大家Mock數(shù)據(jù)的命名規(guī)范,通過(guò)Fixutre的復(fù)用,來(lái)減少新寫測(cè)試的成本。
  • 測(cè)試結(jié)構(gòu)太復(fù)雜?
  • 考慮是不是自己應(yīng)用的代碼組織就有問(wèn)題?
  • 測(cè)試莫名奇妙起不來(lái)?
  • 需要詳細(xì)了解JUNIT、Spring、PandoraBoot等是如何進(jìn)行測(cè)試環(huán)境的mock的,是不是測(cè)試間的數(shù)據(jù)沖突等。詳細(xì)的我們會(huì)在方法篇持續(xù)更新,遇到問(wèn)題解決問(wèn)題。

心魔

單元測(cè)試這件事,實(shí)施的時(shí)候還是有很多阻力的,筆者原來(lái)給自己也找過(guò)很多理由,無(wú)論是用來(lái)說(shuō)服領(lǐng)導(dǎo)的,還是說(shuō)服自己的。下面是筆者對(duì)于這些理由的一些思考,希望能和大家有一些共鳴。

不會(huì)寫

雖然很不愿意承認(rèn)這個(gè)事,但最后還是承認(rèn)了自己是真的不會(huì)寫單元測(cè)試。剛接觸單元測(cè)試的時(shí)候,看了看junit的文檔,心想單元測(cè)試,不就是個(gè)“Assert”嗎,有啥不會(huì)的,這東西好學(xué)。后來(lái)實(shí)施過(guò)程中發(fā)現(xiàn),單元測(cè)試不僅僅是“Assert”,還需要準(zhǔn)備環(huán)境,Mock數(shù)據(jù),復(fù)現(xiàn)場(chǎng)景,驗(yàn)證。著實(shí)是個(gè)麻煩事。

后來(lái)反思,為什么單元測(cè)試麻煩?一開(kāi)始學(xué)習(xí)ORM框架的時(shí)候不麻煩嗎?一開(kāi)始學(xué)Spring不麻煩嗎?后來(lái)熟悉了Bean的生命周期、BeanFactory、BeanProcessor等,Spring已經(jīng)不是個(gè)麻煩事了。仔細(xì)想想,自己對(duì)單元測(cè)試的理解僅僅是:“一個(gè)Mock加一個(gè)Assert”。僅僅學(xué)了幾個(gè)框架,看了幾篇文章,還做不到把單元測(cè)試這件事真正落地。

在落地單元測(cè)試的時(shí)候,有一些常見(jiàn)的問(wèn)題:

場(chǎng)景太復(fù)雜,需要的數(shù)據(jù)太多,怎么處理?

可以直接使用JSON、SQL將現(xiàn)有數(shù)據(jù)修改后導(dǎo)入到系統(tǒng)中。這樣的話可能需要mock的數(shù)據(jù)就不會(huì)那么多了,可以提煉一些工具類,直接從resource中讀取數(shù)據(jù)文件,導(dǎo)入到數(shù)據(jù)庫(kù)、或者提供給mock方法使用。

也可以構(gòu)建一些Fixture,將自己系統(tǒng)中UT的數(shù)據(jù)固定下來(lái),這樣,如果前面一個(gè)同學(xué)已經(jīng)mock過(guò)相關(guān)數(shù)據(jù)了,再新寫UT的時(shí)候可以拿來(lái)即用。構(gòu)建Fixture可以用工廠模式、構(gòu)建者模式等來(lái)達(dá)到數(shù)據(jù)隔離的效果,避免相互干擾。

好多東西都是和中間件或者其他系統(tǒng)頻繁交互,怎么寫測(cè)試?

數(shù)據(jù)庫(kù)層面可以使用內(nèi)存型數(shù)據(jù)庫(kù)“H2”、"Embedded Mysql"、“Embedded PostgreSql”等。

如果以上都不能解決問(wèn)題,可以使用mockito直接mock相應(yīng)的Bean。

單元測(cè)試的粒度問(wèn)題,這個(gè)方法該不該寫UT,另外一個(gè)方法為什么不需要寫UT?

單元測(cè)試的粒度沒(méi)有標(biāo)準(zhǔn)答案,筆者自己總結(jié)了一些寫UT粒度方面的方法:

  • 不熟悉單元測(cè)試寫法,盡量寫簡(jiǎn)單的單元測(cè)試,覆蓋核心方法。
  • 熟悉單元測(cè)試,業(yè)務(wù)復(fù)雜,覆蓋正常、一般異常場(chǎng)景,另外對(duì)核心業(yè)務(wù)邏輯要有單獨(dú)的測(cè)試。

測(cè)試如何復(fù)用?

測(cè)試應(yīng)該是有組織、有結(jié)構(gòu)的,就像我們寫業(yè)務(wù)代碼一樣,會(huì)想著如何在代碼層面復(fù)用、如何在功能層面復(fù)用、如何在業(yè)務(wù)維度復(fù)用。單元測(cè)試也應(yīng)該有結(jié)構(gòu),可以盡量復(fù)用一些前人的經(jīng)驗(yàn)。簡(jiǎn)單來(lái)說(shuō),測(cè)試的復(fù)用也分為三個(gè)維度:數(shù)據(jù)、場(chǎng)景、用例,好的代碼結(jié)構(gòu)應(yīng)該盡量的能讓測(cè)試復(fù)用,讓增加UT不再是從頭開(kāi)始。

不想寫

寫測(cè)試有什么用?

很多人都寫過(guò)單元測(cè)試的文章,羅列過(guò)很多單元測(cè)試的很多好處,這里就不贅述了。這里講幾個(gè)感觸比較深的用處吧?

  • DEBUG:阿里現(xiàn)在的基礎(chǔ)設(shè)施是真的完善,中間件、各種監(jiān)控、日志,只要系統(tǒng)埋點(diǎn)夠好,遇到的很多問(wèn)題都可以解決,即使有一些復(fù)雜問(wèn)題,也可以local debug。但在一些特殊場(chǎng)景下,將數(shù)據(jù)MOCK好,利用UT來(lái)DEBUG,可能效率更高,大家可以試試。
  • 測(cè)試如文檔:我們現(xiàn)在開(kāi)發(fā)有很多完善的文檔,但文檔這東西和代碼上畢竟有一層映射關(guān)系,如果能快速了解業(yè)務(wù),完善的測(cè)試,有時(shí)候也是個(gè)不錯(cuò)的選擇,例如大家學(xué)習(xí)一些開(kāi)源框架的時(shí)候,都會(huì)從測(cè)試開(kāi)始看。
  • 重構(gòu):當(dāng)你想下定決心重構(gòu)的時(shí)候,才發(fā)現(xiàn)項(xiàng)目中沒(méi)有單元測(cè)試,什么心情?

價(jià)值不高

在面對(duì)復(fù)雜的接口時(shí),常常需要Mock很多數(shù)據(jù)來(lái)支撐一個(gè)小的點(diǎn),很多時(shí)候內(nèi)心感覺(jué)沒(méi)價(jià)值,因?yàn)橐粋€(gè)if-else的變動(dòng),竟然需要準(zhǔn)備N份數(shù)據(jù),得不償失。

后來(lái)反思,為什么一個(gè)if-else的變動(dòng),需要準(zhǔn)備N份數(shù)據(jù)?如果這個(gè)接口一開(kāi)始寫的時(shí)候就有健全的UT,那一個(gè)if-else的變更還需要準(zhǔn)備N份數(shù)據(jù)嗎?大概率不需要了吧,有可能只需要改一個(gè)測(cè)試case就好了。所以說(shuō)現(xiàn)在成本高,將來(lái)成本會(huì)更高,現(xiàn)在做了,做的好一點(diǎn),后面可能成本就低了。

筆者觀點(diǎn):寫單元測(cè)試,應(yīng)該比寫代碼的成本更低。

這個(gè)不用說(shuō)吧,通用理由,大家都明白。路是人踩出來(lái)的,總要有人要先走。Why not you?

最后

如果大家對(duì)于單元測(cè)試有好的實(shí)踐,或者對(duì)文章中的一些觀點(diǎn)有些共鳴,大家可以在評(píng)論區(qū)留言,我們互相學(xué)習(xí)一下。大家也可以在評(píng)論區(qū)寫出自己的場(chǎng)景,大家一起探討如何針對(duì)特定場(chǎng)景來(lái)實(shí)踐。

相關(guān)鏈接

[1]https://martinfowler.com/bliki/TestPyramid.html

[2]https://martinfowler.com/articles/practical-test-pyramid.html

【本文為51CTO專欄作者“阿里巴巴官方技術(shù)”原創(chuàng)稿件,轉(zhuǎn)載請(qǐng)聯(lián)系原作者】 

??戳這里,看該作者更多好文??

 

責(zé)任編輯:武曉燕 來(lái)源: 51CTO專欄
相關(guān)推薦

2017-01-14 23:42:49

單元測(cè)試框架軟件測(cè)試

2020-08-25 08:03:59

測(cè)試Sharness結(jié)構(gòu)

2017-01-16 12:12:29

單元測(cè)試JUnit

2017-01-14 23:26:17

單元測(cè)試JUnit測(cè)試

2020-08-18 08:10:02

單元測(cè)試Java

2017-03-23 16:02:10

Mock技術(shù)單元測(cè)試

2021-05-05 11:38:40

TestNGPowerMock單元測(cè)試

2011-07-04 18:16:42

單元測(cè)試

2020-05-07 17:30:49

開(kāi)發(fā)iOS技術(shù)

2023-07-26 08:58:45

Golang單元測(cè)試

2011-05-16 16:52:09

單元測(cè)試徹底測(cè)試

2017-02-23 15:59:53

測(cè)試MockSetup

2011-04-18 13:20:40

單元測(cè)試軟件測(cè)試

2012-05-17 09:09:05

Titanium單元測(cè)試

2009-09-25 10:33:25

Hibernate單元

2020-09-30 08:08:15

單元測(cè)試應(yīng)用

2010-01-28 15:54:19

Android單元測(cè)試

2011-06-14 15:56:42

單元測(cè)試

2013-06-04 09:49:04

Spring單元測(cè)試軟件測(cè)試

2024-07-29 12:12:59

點(diǎn)贊
收藏

51CTO技術(shù)棧公眾號(hào)