<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è)試的反模式 | IDCF

          共 3391字,需瀏覽 7分鐘

           ·

          2022-01-02 11:16

          來(lái)源:Thoughtworks洞見(jiàn)
          作者:鐘立?


          一、過(guò)于關(guān)注實(shí)現(xiàn)細(xì)節(jié)的測(cè)試



          在為前端項(xiàng)目編寫(xiě)測(cè)試用例的時(shí)候,你也許和我一樣,曾遇到過(guò)以下困擾:

          • 明明進(jìn)行了功能正確的改動(dòng),測(cè)試卻掛了。修復(fù)測(cè)試有時(shí)候得認(rèn)真閱讀各種mock的細(xì)節(jié),或者去了解很多本沒(méi)有必要知道的代碼邏輯。最后修測(cè)試花的時(shí)間比進(jìn)行業(yè)務(wù)改動(dòng)花的時(shí)間還要長(zhǎng)(甚至長(zhǎng)很多)。
          • 對(duì)代碼進(jìn)行提取抽象之后,為各個(gè)組件或函數(shù)添加測(cè)試,實(shí)際上是用測(cè)試工具的API去重復(fù)業(yè)務(wù)代碼的內(nèi)部實(shí)現(xiàn)邏輯(有時(shí)候還很麻煩!)。任何正常的重構(gòu)都會(huì)導(dǎo)致測(cè)試失敗,你本來(lái)希望測(cè)試能告訴你什么樣的修改是對(duì)的,結(jié)果現(xiàn)在測(cè)試只能告訴你代碼確實(shí)有被修改。
          • 測(cè)試寫(xiě)好,覆蓋率提高,本應(yīng)信心十足地認(rèn)為代碼變得健壯了,可是捫心自問(wèn),你知道自己寫(xiě)的這個(gè)測(cè)試弱點(diǎn)在什么地方,或者說(shuō)還有多少細(xì)節(jié)沒(méi)有涵蓋。你精心模擬了一個(gè)條件,去觸發(fā)邏輯流程,并且測(cè)試通過(guò),可是在真實(shí)的瀏覽器交互中用戶也許并不能觸發(fā)這個(gè)條件。因此,同樣的道理,你在自己的代碼通過(guò)了他人寫(xiě)的測(cè)試之后,也不能確定真實(shí)場(chǎng)景下沒(méi)有問(wèn)題,只好把后續(xù)的重任交給QA。
          造成上面三個(gè)問(wèn)題的原因不止一個(gè),但測(cè)試過(guò)于關(guān)注實(shí)現(xiàn)細(xì)節(jié)在我看來(lái)是最主要的。
          • 第一個(gè)問(wèn)題,明明是正確的改動(dòng),可是測(cè)試不止是驗(yàn)證業(yè)務(wù)功能,還對(duì)實(shí)現(xiàn)細(xì)節(jié)提出了不該提出的要求,比如要求你的函數(shù)接受跟以前一樣的參數(shù),返回值必須是字符串而不能是數(shù)組等等。可是這個(gè)函數(shù)只是實(shí)現(xiàn)流程中一個(gè)小小的環(huán)節(jié),也許在下次重構(gòu)時(shí)就會(huì)不復(fù)存在。
          • 第二個(gè)問(wèn)題很類似,如果測(cè)試代碼去重復(fù)實(shí)現(xiàn)細(xì)節(jié),不管進(jìn)行正確還是錯(cuò)誤的重構(gòu),你都得把測(cè)試改一遍,那原先的測(cè)試又能提供什么價(jià)值呢?
          • 第三個(gè)問(wèn)題有時(shí)發(fā)生在,測(cè)試的實(shí)現(xiàn)細(xì)節(jié),不能覆蓋整個(gè)真實(shí)交互流程的時(shí)候。用戶點(diǎn)擊的是屏幕上的button按鈕,而測(cè)試的起點(diǎn)是onClick事件被觸發(fā)。后面的邏輯被驗(yàn)證成功,可問(wèn)題偏偏發(fā)生在點(diǎn)擊環(huán)節(jié),真實(shí)的點(diǎn)擊也許因?yàn)榘粹o狀態(tài)而無(wú)法觸發(fā)onClick事件。
          因此,才會(huì)有人提出前端的測(cè)試應(yīng)盡量去模擬真實(shí)的用戶行為,Testing-Library就在其官網(wǎng)的“指導(dǎo)原則”章節(jié),鼓勵(lì)使用者盡量仿照應(yīng)用真實(shí)的使用方式去編寫(xiě)測(cè)試,并明確提出,你的測(cè)試越接近用戶的真實(shí)使用方式,它就能給你越多的信心。換句話說(shuō),你的測(cè)試應(yīng)該盡量少用函數(shù)去手動(dòng)觸發(fā),而要盡量多地利用測(cè)試框架給你的API,去模擬Input框的輸入,按鈕的點(diǎn)擊,表單的提交等等。
          如此一來(lái),有的函數(shù),你也無(wú)需寫(xiě)測(cè)試證明它的返回值如你所愿,需要寫(xiě)的,是頁(yè)面顯示了期待的文字,發(fā)生了預(yù)期的變化,進(jìn)行了對(duì)應(yīng)的跳轉(zhuǎn)。你會(huì)發(fā)現(xiàn),這時(shí)的測(cè)試就像寫(xiě)在卡里的AC一樣。只要測(cè)試是通過(guò)的,你就有理由相信主體功能沒(méi)有破壞,而不只是函數(shù)工作正常。

          二、沒(méi)有獨(dú)立業(yè)務(wù)含義的測(cè)試單元



          看到上面的方案,你可能會(huì)立馬會(huì)想到一些問(wèn)題。
          首先就是測(cè)試流程可能會(huì)很長(zhǎng),從用戶填完表單,點(diǎn)擊提交,到期待的變化出現(xiàn),當(dāng)中可能經(jīng)歷了好幾個(gè)函數(shù)的執(zhí)行,連帶著一系列的副作用。模擬這一系列行為,似乎是集成測(cè)試與E2E測(cè)試該干的事情。如果項(xiàng)目中大部分邏輯都是由這種測(cè)試去覆蓋,看起來(lái)與測(cè)試金字塔所說(shuō)的由單元測(cè)試作為地基是矛盾的。
          我認(rèn)為,當(dāng)真實(shí)遇到的問(wèn)題碰到了某種教條規(guī)范時(shí),后者該適當(dāng)?shù)刈尣健?/span>
          鼓勵(lì)多寫(xiě)單元測(cè)試的原因在于它們成本低,有針對(duì)性。可是在前端項(xiàng)目里面,很多形式上的單元并沒(méi)有獨(dú)立的業(yè)務(wù)含義。
          拿React項(xiàng)目舉例,好多函數(shù)只是因?yàn)樗鼈冊(cè)谛问缴峡梢员怀槿〕鰜?lái),就被拎到一個(gè)單獨(dú)的文件里,從而降低主函數(shù)的復(fù)雜度。如果給它寫(xiě)單元測(cè)試,你就不得不手動(dòng)觸發(fā)它的參數(shù)變化,或者檢測(cè)它的參數(shù)函數(shù)是否有被調(diào)用。
          我們寫(xiě)的React hook尤其如此。很多時(shí)候抽取自定義的hook是出于邏輯上的原因,把相關(guān)的邏輯和數(shù)據(jù)聚合到一起,減輕UI組件的負(fù)擔(dān),但這些hook往往沒(méi)有一個(gè)可以輕易解釋清楚的業(yè)務(wù)含義,而且它們也不會(huì)被其它地方使用。
          所以這類 “單元”只是長(zhǎng)得像單元而已,它們其實(shí)只是一個(gè)實(shí)現(xiàn)環(huán)節(jié)。這里完整的UI操作流程,才更像一個(gè)有價(jià)值的單元,盡管它們?cè)谛问缴峡赡艹搅藛蝹€(gè)函數(shù)的范疇。
          但我不想矯枉過(guò)正,確實(shí)有不少情況下,一個(gè)util函數(shù),一個(gè)hook,一個(gè)很小的公共組件,都是有獨(dú)立存在的價(jià)值的,因此,它們也應(yīng)當(dāng)被視為真正的單元,確實(shí)“有資格”擁有自己的專屬測(cè)試。
          testing-library下面有一個(gè)單獨(dú)的庫(kù),叫react-hooks-testing-library,讓你無(wú)需通過(guò)UI行為層面,而是直接以hook的方式去測(cè)試它們。它的GitHub頁(yè)面上,明確提出了使用以及不使用它的場(chǎng)景:當(dāng)你的hook不與組件強(qiáng)相關(guān),擁有獨(dú)立含義時(shí)可以使用;當(dāng)你的hook只被一個(gè)組件使用,且和它的定義強(qiáng)相關(guān)時(shí),則不建議使用。
          插入一段:盡管存在react-hooks-testing-library這樣的工具,但像SWR這樣優(yōu)秀的三方庫(kù),在用testing-library為自己的hook API做測(cè)試的時(shí)候,依然選擇在UI層面進(jìn)行。方法是,把自己的hook置于一個(gè)臨時(shí)的div標(biāo)簽里進(jìn)行render,把數(shù)據(jù)的變化映射成html文字的變化,最后對(duì)文字內(nèi)容做斷言。其實(shí)對(duì)于獨(dú)立性強(qiáng)的函數(shù),個(gè)人覺(jué)得放置在UI里面做測(cè)試倒沒(méi)有太大區(qū)別,但SWR的例子體現(xiàn)了對(duì)“仿照真實(shí)使用場(chǎng)景去測(cè)試”這一原則的尊重。
          將上面的規(guī)律套用到Angular項(xiàng)目中,也是類似的。對(duì)于獨(dú)立性和通用性不強(qiáng)的pipe,directive,reducer,effect,service,都可以認(rèn)為它們是實(shí)現(xiàn)流程的一部分,從UI行為層面寫(xiě)好測(cè)試即可。
          總之,在構(gòu)思前端測(cè)試的時(shí)候,與其死守“單元測(cè)試”的字面含義,不如結(jié)合實(shí)際場(chǎng)景,重新思考什么才是真正有價(jià)值的“單元”,因地制宜地去寫(xiě)。換種角度表述,與其在意我們寫(xiě)的測(cè)試是不是“單元測(cè)試”,不如追求更核心的東西——我們的測(cè)試有沒(méi)有以合適的方式去校驗(yàn)邏輯。
          另外,當(dāng)我們的“單元”過(guò)大,一些邏輯可能就會(huì)覆蓋不上。像sonar這類工具,不僅會(huì)檢查你的行數(shù)覆蓋率,還會(huì)檢查你的各項(xiàng)條件語(yǔ)句是否有被測(cè)試執(zhí)行。當(dāng)一套測(cè)試的行為流程囊括了多個(gè)函數(shù),而且每個(gè)函數(shù)都有好幾個(gè)if…else語(yǔ)句時(shí),想要在UI操作與mock數(shù)據(jù)上把所有情況都覆蓋到,成本就會(huì)變得非常高昂。
          對(duì)于此,我們得承認(rèn),無(wú)論用什么方式組織測(cè)試,覆蓋所有的條件分支都是不太現(xiàn)實(shí)的,而且價(jià)值也不大。對(duì)于“滿足條件A就執(zhí)行XXX”之類的語(yǔ)句,條件為非A時(shí)沒(méi)有業(yè)務(wù)上的規(guī)定,如果為了刻意覆蓋函數(shù)的所有條件,就強(qiáng)行測(cè)它在非A的情況下返回一個(gè)undefined,則沒(méi)有太多價(jià)值。對(duì)這類情況,用UI行為測(cè)試主要條件即可,如果你實(shí)在覺(jué)得有重要的邏輯沒(méi)有被覆蓋,不妨回過(guò)頭來(lái)想想,是不是漏掉了某種輸入條件,例如特定的用戶鍵入或者特殊的API mock返回值。但是,當(dāng)有過(guò)多的條件分支很難用業(yè)務(wù)場(chǎng)景去表述和模擬的時(shí)候,我們可能需要重新思考代碼的實(shí)現(xiàn)邏輯是否合理了。
          當(dāng)然,即使按上面這樣做,有時(shí)候還是會(huì)發(fā)現(xiàn)要覆蓋的條件組合太多,從行為流程上寫(xiě)測(cè)試太復(fù)雜,這時(shí)就不得不做一定的妥協(xié),為那些沒(méi)有獨(dú)立性的部分去單獨(dú)寫(xiě)測(cè)試。如果這類測(cè)試不太好寫(xiě),可以參照剛才提到的SWR官方測(cè)試用到的技巧,把要測(cè)的函數(shù)或者是對(duì)象放置在一個(gè)臨時(shí)的UI組件下,以最小的成本做UI行為測(cè)試。

          最后



          總結(jié)一下上面談到的幾個(gè)原則:
          • 從真實(shí)用戶的行為流程去測(cè)試,往往比測(cè)函數(shù)本身,能給你帶來(lái)更多的信心。
          • 對(duì)于沒(méi)有獨(dú)立性和通用性的函數(shù)或?qū)ο螅阉鼈円曌鲗?shí)現(xiàn)的一部分,一般沒(méi)有必要為它們?nèi)?xiě)單獨(dú)的測(cè)試。不要拘泥于對(duì)“單元測(cè)試”的字面理解,不要被形式上的規(guī)律所束縛。
          • 不要把測(cè)試覆蓋率視為太過(guò)重要的指標(biāo),它的目的還是幫助提升代碼的穩(wěn)定。有的代碼沒(méi)有覆蓋也沒(méi)關(guān)系,有的代碼值得你覆蓋好多遍。畢竟,我們不是為了寫(xiě)測(cè)試而寫(xiě)測(cè)試。


          玩樂(lè)高,學(xué)敏捷,【規(guī)模化敏捷聯(lián)合作戰(zhàn)沙盤之「烏托邦計(jì)劃」】,2022年3月5-6日登陸深圳,將“多團(tuán)隊(duì)敏捷協(xié)同”基因內(nèi)化在研發(fā)流程中,為規(guī)模化提升研發(fā)效能保駕護(hù)航!!???
          企業(yè)組隊(duì)和個(gè)人均可報(bào)名參加,一起挑戰(zhàn)極客烏托邦


          瀏覽 38
          點(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>
                  国产9在线观看黄A片免费 | 一区二区无码在线 | 草逼大网| 久久久久无码国产精品一区 | 鸡巴操逼视频 |