<kbd id="afajh"><form id="afajh"></form></kbd>
<strong id="afajh"><dl id="afajh"></dl></strong>
    <del id="afajh"><form id="afajh"></form></del>
        1. <th id="afajh"><progress id="afajh"></progress></th>
          <b id="afajh"><abbr id="afajh"></abbr></b>
          <th id="afajh"><progress id="afajh"></progress></th>

          聊聊單元測(cè)試

          共 4012字,需瀏覽 9分鐘

           ·

          2020-11-15 05:46












          這里是Z哥的個(gè)人公眾號(hào)

          每周五11:45 按時(shí)送達(dá)

          當(dāng)然了,也會(huì)時(shí)不時(shí)加個(gè)餐~

          我的第「167」篇原創(chuàng)敬上



          大家好,我是Z哥。

          提起單元測(cè)試,很多人對(duì)它的態(tài)度是,我知道它有用,但是我不想寫。大多數(shù)人的理由是沒時(shí)間寫,任務(wù)太多。

          但是說(shuō)實(shí)話,是真的沒時(shí)間嗎?Z哥認(rèn)為真是由于沒時(shí)間而不寫單元測(cè)試的人絕對(duì)是少數(shù)。況且,導(dǎo)致沒時(shí)間很大原因可能就是花了太多時(shí)間在處理bug上。


          所以,很多人沒有把單元測(cè)試當(dāng)作一個(gè)“工具”,而把它看作是一種“負(fù)擔(dān)”。

          在這種心態(tài)下,就算要寫單元測(cè)試,也是為了寫而寫。更可怕的是,通過(guò)mock工具,還真能給任意代碼寫單元測(cè)試。

          但是這樣的做法其實(shí)是“買櫝還珠”,真正的浪費(fèi)時(shí)間。


          最典型的情況是,很多人一開始寫測(cè)試代碼就錯(cuò)了,看上去寫了很多Mock、Assert,但是到底想驗(yàn)證什么,測(cè)試什么其實(shí)并不明白。一個(gè)不留神,測(cè)試代碼就變成驗(yàn)證某個(gè)RPC接口對(duì)不對(duì),某個(gè)第三方系統(tǒng)庫(kù)的函數(shù)對(duì)不對(duì)等等,這明顯就跑偏了。

          對(duì)于這種情況,無(wú)論多么牛逼的工具都幫不了你,只能提高自己對(duì)單元測(cè)試的理解。


          還有一種情況是,寫代碼的時(shí)候并沒有考慮這代碼要怎么測(cè),因此寫完了以后發(fā)現(xiàn)寫單元測(cè)試很難,沒有現(xiàn)成的測(cè)試入口。這時(shí)候項(xiàng)目交付的deadline又快到了,唉,要不先放著改天再寫吧。當(dāng)然我們都知道,這個(gè)改天大概率再也不會(huì)做。


          我們有一萬(wàn)個(gè)理由可以不做單元測(cè)試。但是這就好比,組裝一架飛機(jī)不用測(cè)試各個(gè)零件的運(yùn)作是否符合預(yù)期,直接讓它飛起來(lái)再看有哪些問(wèn)題。

          以后如果誰(shuí)說(shuō)單元測(cè)試不重要的時(shí)候,你不妨問(wèn)他“你敢坐沒有檢查過(guò)的飛機(jī)么?”


          另外,單元測(cè)試除了對(duì)軟件質(zhì)量有提升外,對(duì)軟件的開發(fā)效率提升也很明顯。

          在《實(shí)用軟件度量》一書中提到了微軟內(nèi)部的統(tǒng)計(jì)數(shù)據(jù),單元測(cè)試的成本效率是系統(tǒng)測(cè)試的3倍。

          在《單元測(cè)試的藝術(shù)》中也提到過(guò)一個(gè)案例,找了開發(fā)能力相近的兩個(gè)團(tuán)隊(duì),同時(shí)開發(fā)相近的需求。進(jìn)行單測(cè)的團(tuán)隊(duì)在編碼階段時(shí)長(zhǎng)增長(zhǎng)了一倍,從7天到14天,但是,這個(gè)團(tuán)隊(duì)在集成測(cè)試階段的表現(xiàn)非常順暢,bug量小,定位bug迅速等。最終的效果,整體交付時(shí)間和缺陷數(shù),均是單元測(cè)試團(tuán)隊(duì)最少。


          單元測(cè)試還有一個(gè)好處,就是讓我們嘴上說(shuō)的「高內(nèi)聚低耦合」的代碼有了一條統(tǒng)一的實(shí)現(xiàn)路徑。因?yàn)榇a到底算不算高內(nèi)聚低耦合,其實(shí)每個(gè)人的主觀標(biāo)準(zhǔn)都不同。但是是否容易做單元測(cè)試,這卻是一個(gè)相對(duì)更客觀的標(biāo)準(zhǔn)。

          所以,如果有人跟你說(shuō)他這段代碼設(shè)計(jì)得非常好,但就是不好寫單元測(cè)試,相信你知道該怎么做了:D


          那么,正兒八經(jīng)的單元測(cè)試應(yīng)該怎么寫呢?我來(lái)分享一些我的經(jīng)驗(yàn)和思考,希望能讓更多的人參與到編寫單元測(cè)試的隊(duì)伍中來(lái)。


          /01 ?怎么才算“單元”?/

          相信很多人和Z哥一樣,剛接觸單元測(cè)試的時(shí)候覺得單元測(cè)試就是用來(lái)測(cè)某個(gè)方法的。其實(shí)并不是這樣,這里的「單元」如何定義取決你如何定義“一件事”。只要這個(gè)「單元」里做的是“同一件事”,那么哪怕其中包含了3個(gè)方法,它也可以是一個(gè)「單元」。

          比如,你寫了一個(gè)下訂單的單元測(cè)試,你可以把生成訂單方法和扣減紅包方法放在一起做單測(cè),這樣比兩個(gè)方法分別做單測(cè)還可以多做一些關(guān)聯(lián)驗(yàn)證。比如,訂單上的紅包金額是否與扣減的紅包金額一致?


          /02? 如何判斷單元測(cè)試的好壞/

          單元測(cè)試和大多數(shù)技術(shù)工作不同,寫得越好的單元測(cè)試往往用到的工具越簡(jiǎn)單,甚至不需要額外的工具。

          在我的概念里,單元測(cè)試的好壞分為以下幾個(gè)等級(jí)。

          第一級(jí),大部分代碼不需要 Mock 就可以測(cè)試。這是最優(yōu)秀的。
          第二級(jí),大部分代碼需要Mock才能測(cè)試,但都不是靜態(tài)方法。
          第三級(jí),大部分代碼需要Mock才能測(cè)試,而且包含大量靜態(tài)方法。(一般的Mock工具還無(wú)法Mock靜態(tài)方法)

          可能你會(huì)有疑問(wèn),為什么Mock靜態(tài)方法是不好的?這個(gè)后面講具體做法的時(shí)候會(huì)說(shuō)。


          說(shuō)了這么多,具體怎么寫呢?寫單元測(cè)試其實(shí)就是做以下三件事。


          /01 ?確定寫單元測(cè)試的范圍/

          做任何的事都得回歸到價(jià)值本身,單元測(cè)試也是如此。比如,你給一個(gè)固定返回字符串“Hello World”的方法寫單元測(cè)試就是一個(gè)浪費(fèi)時(shí)間的事情。

          一般來(lái)說(shuō),哪些類型的代碼適合寫單元測(cè)試?

          1. 公用組件庫(kù)。這些代碼變更不會(huì)特別頻繁,所以覆蓋率需要盡量達(dá)到100%。

          2. 被調(diào)用頻次越高的代碼。



          /02 ?怎么寫?/

          具體怎么寫其實(shí)就是確定你要通過(guò)代碼驗(yàn)證的東西是什么。這里你可以根據(jù)以下這4個(gè)標(biāo)準(zhǔn)來(lái),不同重要度的方法,可以選擇適合的標(biāo)準(zhǔn)來(lái)寫。

          • L1:輸入正確的參數(shù)時(shí),會(huì)有正確的輸出。(測(cè)試正確的處理邏輯是否符合預(yù)期)

          • L2:輸入錯(cuò)誤的參數(shù)時(shí),不能拋出系統(tǒng)級(jí)的異常。(測(cè)試錯(cuò)誤的處理邏輯是否符合預(yù)期)

          • L3:極端情況和邊界數(shù)據(jù)可用??赡芤婚_始無(wú)法考慮到很多邊界條件和極端情況,所以這是一個(gè)需要長(zhǎng)期維護(hù)的部分。

          • L4:覆蓋率達(dá)到100%。



          Z哥我對(duì)這4個(gè)標(biāo)準(zhǔn)的運(yùn)用場(chǎng)景是:

          • L1,實(shí)在時(shí)間緊迫并且代碼對(duì)應(yīng)的功能不是核心部分。

          • L2,非核心模塊大部分時(shí)候應(yīng)該要達(dá)到的標(biāo)準(zhǔn)。

          • L3,核心模塊要達(dá)到的標(biāo)準(zhǔn)。

          • L4,全局基礎(chǔ)框架、封裝的非業(yè)務(wù)型類庫(kù)要達(dá)到的標(biāo)準(zhǔn)。



          /03 ?單元測(cè)試的數(shù)據(jù)從哪來(lái)?/

          很多人覺得寫單元測(cè)試麻煩,主要的原因就是覺得構(gòu)造測(cè)試數(shù)據(jù)費(fèi)時(shí)間。所以,取巧的方法是直接連到DB,基于DB里的數(shù)據(jù)做單元測(cè)試。

          但是這樣的數(shù)據(jù)是不穩(wěn)定的,一旦某個(gè)前置方法的邏輯有問(wèn)題,導(dǎo)致數(shù)據(jù)庫(kù)里的數(shù)據(jù)出現(xiàn)異常,那么后續(xù)的測(cè)試方法都會(huì)連續(xù)出錯(cuò)。

          所以我認(rèn)為單元測(cè)試的測(cè)試數(shù)據(jù)應(yīng)該人為地在測(cè)試代碼里構(gòu)造。如此不但能讓數(shù)據(jù)變得穩(wěn)定,而且單元測(cè)試的運(yùn)行效率也會(huì)更高,畢竟少了多次連接數(shù)據(jù)庫(kù)的操作。

          《Google軟件測(cè)試之道》中提到谷歌的做法也是如此。在谷歌,單元測(cè)試被劃分為「小型測(cè)試」類型,對(duì)于小型測(cè)試的特點(diǎn)就是不需要外部依賴,所以涉及到的外部服務(wù)需通過(guò)Mock或Fack來(lái)實(shí)現(xiàn)。(Mock、Fake、Stub都是單元測(cè)試中的基本概念,可以自行搜索了解)


          再分享兩個(gè)最佳實(shí)踐給你,讓你可以更容易編寫單元測(cè)試。


          /01/

          涉及到I/O的代碼和業(yè)務(wù)代碼盡量分開。這里的I/O不僅僅是磁盤I/O還有網(wǎng)絡(luò)I/O。

          Pascal之父——Niklaus Wirth提出過(guò)一個(gè)著名的公式:程序 = 算法 + 數(shù)據(jù)。數(shù)據(jù)的操作和獲取就是通過(guò)I/O進(jìn)行的,一旦剝離后,剩下的代碼就是算法,也就是“邏輯”,我們寫單元測(cè)試要驗(yàn)證的恰好就是它。

          實(shí)現(xiàn)方式也很簡(jiǎn)單,將I/O部分抽象出接口,通過(guò)依賴注入方式調(diào)用。這樣你在寫單元測(cè)試的時(shí)候可以通過(guò)Mock方式來(lái)提供一個(gè)I/O方法的實(shí)現(xiàn)。


          /02/

          測(cè)試數(shù)據(jù)與用例分離。在你寫單元測(cè)試的時(shí)候,因?yàn)樾枰紤]很多種情況,所以需要構(gòu)造好幾套測(cè)試數(shù)據(jù)。

          為了便于管理和維護(hù)這些數(shù)據(jù),你可以避免將數(shù)據(jù)與單元測(cè)試代碼寫在一起。舉個(gè)例子,你可能平時(shí)是這么寫的。

          @Test
          Public void testAdd(){
          assertEquals(expect: 2, MethodAdd(a: 1,b: 1));
          assertEquals(expect: 0, MethodAdd(a: -1,b: 1));
          assertEquals(expect: 0, MethodAdd(a: 0,b: 0));
          }

          以后你可以試試這樣寫:

          @Test
          void testAdd(){
          ? ? ? ? for(Object[] s : data()){
          ? ? ? ? ? ? assertEquals(s[2], (int)s[0]+(int)s[1]);
          ? ? ? ? }
          }

          public static Iterable data(){
          ? ? ? ? ? ? List list = new ArrayList();
          ? ? ? ? ? ? list.add(new Object[]{1,1,2});
          ? ? ? ? ? ? list.add(new Object[]{-1,1,0});
          ? ? ? ? ? ? list.add(new Object[]{0,0,0});
          ? ? ? ? ? ? return list;
          }

          這樣,后續(xù)維護(hù)測(cè)試參數(shù)只要在data()方法里進(jìn)行就好了(當(dāng)然你也可以使用junit之類的工具來(lái)簡(jiǎn)化這個(gè)寫法)。畢竟做單元測(cè)試是一件長(zhǎng)期的事情,需要根據(jù)新發(fā)現(xiàn)的bug保持測(cè)試數(shù)據(jù)的更新,以確保已發(fā)生的bug總是被覆蓋在單元測(cè)試范圍內(nèi)。


          另外,易于做單元測(cè)試的代碼,其實(shí)它的性能也會(huì)不錯(cuò)。因?yàn)楹臅r(shí)的I/O操作不會(huì)隱藏在各個(gè)方法里,讓你無(wú)意間就重復(fù)調(diào)用了。相反,你可以直觀的看到每個(gè)方法里有哪些I/O操作,能合并請(qǐng)求的可以在調(diào)用這些方法之前合并掉。


          好了總結(jié)一下。

          這篇呢,Z哥和你分享了我對(duì)寫單元測(cè)試這件事的看法。

          首先,我們應(yīng)該把它當(dāng)作“工具”而不是“負(fù)擔(dān)”。因?yàn)閱卧獪y(cè)試除了可以提升軟件質(zhì)量,還可以提高開發(fā)效率,以及優(yōu)化代碼設(shè)計(jì)。

          然后,實(shí)際在做的時(shí)候,我從「確定寫單元測(cè)試的范圍」、「怎么寫?」、「單元測(cè)試的數(shù)據(jù)從哪來(lái)?」三個(gè)方面給了我的建議。并且分享了兩個(gè)有效的最佳實(shí)踐給你。

          以我的親身經(jīng)歷告訴你,當(dāng)你每次改完代碼run一遍單元測(cè)試,看到那些success和failurel列表的時(shí)候,你會(huì)覺得“真香”。不信你試試。


          如今,好像一個(gè)團(tuán)隊(duì)不說(shuō)自己在敏捷開發(fā)就落伍了。然而類似于測(cè)試驅(qū)動(dòng)開發(fā)(TDD)之類的開發(fā)方式恰恰是敏捷開發(fā)實(shí)踐的重要組成部分,但是我們卻嫌棄它拖慢迭代速度。

          那么我們到底是不是在敏捷呢?


          推薦閱讀:


          原創(chuàng)不易,如果你覺得這篇文章還不錯(cuò),就「在看」或者「分享」一下吧。鼓勵(lì)我的創(chuàng)作 :)


          如果你有關(guān)于軟件架構(gòu)、分布式系統(tǒng)、產(chǎn)品、運(yùn)營(yíng)的困惑

          可以試試點(diǎn)擊「閱讀原文

          瀏覽 32
          點(diǎn)贊
          評(píng)論
          收藏
          分享

          手機(jī)掃一掃分享

          分享
          舉報(bào)
          評(píng)論
          圖片
          表情
          推薦
          點(diǎn)贊
          評(píng)論
          收藏
          分享

          手機(jī)掃一掃分享

          分享
          舉報(bào)
          <kbd id="afajh"><form id="afajh"></form></kbd>
          <strong id="afajh"><dl id="afajh"></dl></strong>
            <del id="afajh"><form id="afajh"></form></del>
                1. <th id="afajh"><progress id="afajh"></progress></th>
                  <b id="afajh"><abbr id="afajh"></abbr></b>
                  <th id="afajh"><progress id="afajh"></progress></th>
                  国产欧美黄色一级片 | 曰韩黄色视频 | 韩国成人免费无码免费视频 | 亚洲一区二区视频 | aⅴ中文字幕不卡在线无码 |