前端測(cè)試常見的三個(gè)誤區(qū)
前言
在做前端測(cè)試時(shí),選用合適的測(cè)試策略遠(yuǎn)比一通狂寫測(cè)試更重要,所謂 “方向 > 努力”。
如果選擇了錯(cuò)誤的測(cè)試策略,很容易寫出維護(hù)性差和不穩(wěn)定的測(cè)試用例。一旦業(yè)務(wù)出現(xiàn)變化,用例就全崩了。可能這也是大家討厭寫測(cè)試的原因之一吧。
Kent C. Dodds[1] 在這篇文章 《Common Testing Mistakes 》[2] 分享 了 3 個(gè)他看到的測(cè)試誤區(qū)。今天,就把這篇文章分享給大家吧!
翻譯中會(huì)盡量用更地道的語(yǔ)言,這也意味著會(huì)給原文加一層 Buf,想看原文的可點(diǎn)擊 這里[3]。
誤區(qū)一:測(cè)試代碼的實(shí)現(xiàn)細(xì)節(jié)
說實(shí)話,我非常喜歡這個(gè)誤區(qū)(詳情可以看這里[4]),因?yàn)樵跍y(cè)試過程中,它是一個(gè)很嚴(yán)重的問題,這樣寫測(cè)試也不會(huì)帶給你對(duì)應(yīng)的信心。下面是一個(gè)測(cè)試代碼實(shí)現(xiàn)細(xì)節(jié)的例子:
// counter.js
import * as React from 'react'
export class Counter extends React.Component {
state = {count: 0}
increment = () => this.setState(({count}) => ({count: count + 1}))
render() {
const {count} = this.state
return <button onClick={this.increment}>{count}</button>
}
}
// __tests__/counter.js
import * as React from 'react'
// 用 React Testing Library 是很難測(cè)代碼實(shí)現(xiàn)細(xì)節(jié)的,所以這里用 enzyme 來測(cè)
import {mount} from 'enzyme'
import {Counter} from '../counter'
test('the increment method increments count', () => {
const wrapper = mount(<Counter />)
// 千萬別這么做
expect(wrapper.instance().state.count).toBe(0)
wrapper.instance().increment()
expect(wrapper.instance().state.count).toBe(1)
})
為什么說上面是在測(cè)代碼實(shí)現(xiàn)細(xì)節(jié)?以及,為什么測(cè)代碼細(xì)節(jié)是不好的呢?像上面那樣過度測(cè)試實(shí)現(xiàn)細(xì)節(jié)會(huì)帶來兩個(gè)結(jié)果:
- 我可以在測(cè)試完全通過的情況下弄崩業(yè)務(wù)代碼(比如在 onClick 賦值時(shí)故意寫錯(cuò)變量名)
- 我可以在重構(gòu)業(yè)務(wù)代碼的時(shí)候弄崩測(cè)試用例(例如,把 increment 重命名為 updateCount,測(cè)試就崩了,但業(yè)務(wù)代碼是能正常運(yùn)行的)
(譯注:作者對(duì)重構(gòu)的理解是:改動(dòng)業(yè)務(wù)代碼邏輯時(shí),測(cè)試代碼不應(yīng)該做改動(dòng)的,因?yàn)闃I(yè)務(wù)邏輯沒變,只是實(shí)現(xiàn)方式變了)
類似這樣的測(cè)試用例是最難維護(hù)的,因?yàn)槟阋粩嗟馗滤鼈?基于上面第二點(diǎn)),同時(shí),它們也不會(huì)給你帶來更多代碼的信心(基于上面第一點(diǎn))。
誤區(qū)二:100% 代碼覆蓋率
另一個(gè)誤區(qū)就是強(qiáng)求 100% 的代碼覆蓋率。 有趣的是,我經(jīng)??吹皆谝恍╉?xiàng)目里會(huì)被強(qiáng)制要求 100%
的覆蓋率。不管這種規(guī)定是從哪來的,這其實(shí)都是對(duì)代碼覆蓋的誤解,因?yàn)檫@樣并不能給你帶來相與之對(duì)應(yīng)的代碼信心。
代碼覆蓋只能告訴你一件事:
- 這行代碼有被測(cè)試用例跑過
然而,它沒有告訴你的事有:
- 代碼是否按業(yè)務(wù)需求來正常工作
- 代碼是否能和項(xiàng)目里其它代碼一起工作
- 項(xiàng)目崩了的時(shí)候會(huì)發(fā)生什么(這里指意外崩潰)
代碼覆蓋率的另一個(gè)問題是:每增加一行代碼的覆蓋,整體覆蓋量也會(huì)被增加。也就是說,如果你想提高整體覆蓋率,那么在 “支付頁(yè)” 添加測(cè)試和在 “關(guān)于” 頁(yè)添加測(cè)試的效果是一樣的(整體覆蓋率變高了)。比這更嚴(yán)重的另一個(gè)問題是:這樣的覆蓋率不能讓你深入地了解你的項(xiàng)目...
目前來說,還沒有一種萬能的解決方案來獲得準(zhǔn)確的代碼覆蓋率,畢竟每個(gè)項(xiàng)目的需求是不同的。我一般不會(huì)過度關(guān)注代碼覆蓋率,而是更關(guān)注于項(xiàng)目里重要的部分是否覆蓋到位。在確定項(xiàng)目中的關(guān)鍵部分之后,我會(huì)利用覆蓋率報(bào)告來找出還未被測(cè)試覆蓋到的邊界情況。
聲明一下,對(duì)于開源模塊來說,100% 代碼覆蓋率是完全合理的,因?yàn)樗鼈円话愀菀走_(dá)到 100%
覆蓋率(項(xiàng)目不大,而且相對(duì)簡(jiǎn)單),而且他們都是很重要的代碼,會(huì)被很多別的項(xiàng)目引用。
誤區(qū)三:重復(fù)測(cè)試
相比于集成測(cè)試和單測(cè),大多數(shù)人吐槽 E2E 最多就是很慢和不可靠。 你是不可能讓單個(gè) E2E
測(cè)試既能跑得快,又能像單測(cè)那樣穩(wěn)定的。反正就是不可能的。不過話說回來,單個(gè) E2E 測(cè)試會(huì)比單測(cè)帶來更多代碼信心。在很多情況下,單測(cè)是不能像 E2E
那樣帶來那么高的代碼信心的,所以項(xiàng)目中寫點(diǎn) E2E 測(cè)試是肯定值回本的!
當(dāng)然,上面這么說不代表我們不能讓我們的 E2E 測(cè)試跑更快和變得更可靠。其中,重復(fù)測(cè)試是人們寫 E2E
測(cè)試時(shí)經(jīng)常踩的一個(gè)坑,這會(huì)讓降低整個(gè)測(cè)試的性能以及可靠性。
我們應(yīng)該要在隔離環(huán)境下執(zhí)行測(cè)試。理論上,每個(gè)單獨(dú)的 E2E
測(cè)試在執(zhí)行時(shí)都應(yīng)該像不同的用戶使用軟件一樣。這樣的話,每次跑測(cè)試都要走一遍注冊(cè)登錄流程來創(chuàng)建新用戶了,對(duì)吧?看起來好像是對(duì)的,然后你每次就要點(diǎn)點(diǎn)按鈕,輸入用戶信息來做注冊(cè)登錄。這么做只是為了業(yè)務(wù)中要用一下用戶的登錄態(tài),是吧?錯(cuò)!這是不對(duì)的!
讓我們回過頭來想:為什么要寫測(cè)試?因?yàn)檫@樣你可以交付出更有自信、不容易崩潰的項(xiàng)目呀!假如,你有 100
個(gè)測(cè)試需要用已登錄的用戶來執(zhí)行。那你應(yīng)該要跑多少次注冊(cè)/登錄流程來讓你相信代碼是沒問題呢?100 次還是 1 次?正常人都會(huì)選 1 次,因?yàn)橹灰?1
次成功,處處都應(yīng)該成功。因此,剩下的 99 次額外的測(cè)試并不會(huì)給你帶來任何代碼信心。它們只是在做無用功。
那你應(yīng)該怎么做呢?既然我們已經(jīng)搭建好了測(cè)試的隔離環(huán)境,那么就不應(yīng)該在測(cè)試之間共享同一個(gè)
user。我推薦的做法是:當(dāng)每次要注冊(cè)和登錄新用戶時(shí),在項(xiàng)目中發(fā)送同一個(gè) HTTP
請(qǐng)求!發(fā)送請(qǐng)求肯定比在頁(yè)面點(diǎn)擊選中輸入框和輸入用戶名、密碼來得更快,而且會(huì)產(chǎn)生更少的假錯(cuò)誤 (譯注:假錯(cuò)誤是指:測(cè)試失敗了,但是其實(shí)應(yīng)用代碼本身沒任何問題)
。只要你能保證有 1 個(gè)能完整跑通注冊(cè)/登錄的流程,那么你就不會(huì)失去這個(gè)登錄注冊(cè)流程的信心。
總結(jié)
一定要時(shí)刻記住我們寫測(cè)試是為了提高代碼信心。如果你現(xiàn)在做的事不能讓你提高對(duì)代碼的信心,那可以考慮你是否真的要這么做!