自拍偷在线精品自拍偷,亚洲欧美中文日韩v在线观看不卡

前端單測,為什么不要測 “實(shí)現(xiàn)細(xì)節(jié)”?

開發(fā) 前端
在測試組件方面應(yīng)該更多關(guān)注 Props 以及 render 出來的內(nèi)容。測試 “實(shí)現(xiàn)細(xì)節(jié)” 有點(diǎn)像我們?nèi)鲋e,一次撒謊就要撒更多的謊來圓第一個(gè)謊,當(dāng)我們在測試一個(gè)細(xì)節(jié)的時(shí)候,我們只能管中窺豹,這無形中會產(chǎn)生一個(gè)不存在的用戶:Test,這也是為什么很多人覺得代碼一改,測試也得改的原因。

前言

哈嘍,大家好,我是海怪。

相信不少同學(xué)在寫單測的時(shí)候,最大的困擾不是如何寫測試代碼,而是:“應(yīng)該測什么?”,“要測多深入”,“哪些不該測”。

最近在給 React 組件寫單測的時(shí)候,發(fā)現(xiàn)了 Kent (React Testing Library 的貢獻(xiàn)者之一)的 《Testing Implementation Details》 這篇文章,里面對 “為什么不要測代碼實(shí)現(xiàn)細(xì)節(jié)?” 這個(gè)問題寫得非常好,今天就把這篇文章也分享給大家。

翻譯中會盡量用更地道的語言,這也意味著會給原文加一層 Buf,想看原文的可點(diǎn)擊文末[1]。

開始

我以前用 enzyme 的時(shí)候,都會盡量避免使用某些 API,比如 shallow rendering、instance()、state() 以及 find('ComponentName'),而且 Review 別人的 PR 的時(shí)候,也會跟他們說盡量別用這些 API。這樣做的原因主要是因?yàn)檫@些 API 會測到很多代碼的實(shí)現(xiàn)細(xì)節(jié) (Implementation Details)。 然后,很多人又會問:為什么不要測 代碼的實(shí)現(xiàn)細(xì)節(jié)(Implemantation Details) 呢?很簡單:測試本身就很困難了,我們不應(yīng)該再弄那么多規(guī)則來讓測試變得更復(fù)雜。

為什么測試“實(shí)現(xiàn)細(xì)節(jié)”是不好的?

為什么測試實(shí)現(xiàn)細(xì)節(jié)是不好的呢?主要有兩個(gè)原因:

  • 假錯(cuò)誤(False Negative):重構(gòu)的時(shí)候代碼運(yùn)行成功,但測試用例崩了。
  • 假正確(False Positive):應(yīng)用代碼真的崩了的時(shí)候,然而測試用例又通過了。

注:這里的測試是指:“確定軟件是否工作”。如果測試通過,那么就是 Positive,代碼能用。如果測試失敗,則是 Negative,代碼不可用。而這里的的 False 是指“不正確”,即不正確的測試結(jié)果。

如果上面沒看懂,沒關(guān)系,下面我們一個(gè)一個(gè)來講,先來看這個(gè)手風(fēng)琴組件(Accordion):

// Accordion.js
import * as React from 'react'
import AccordionContents from './accordion-contents'
class Accordion extends React.Component {
state = {openIndex: 0}
setOpenIndex = openIndex => this.setState({openIndex})
render() {
const {openIndex} = this.state
return (
<div>
{this.props.items.map((item, index) => (
<>
<button onClick={() => this.setOpenIndex(index)}>
{item.title}
</button>
{index === openIndex ? (
<AccordionContents>{item.contents}</AccordionContents>
) : null}
</>
))}
</div>
)
}
}
export default Accordion

看到這肯定有人會說:為什么還在用過時(shí)了的 Class Component 寫法,而不是用 Function Component 寫法呢?別急,繼續(xù)往下看,你會發(fā)現(xiàn)一些很有意思的事(相信用過 Enzymes 的人應(yīng)該能猜到會是什么)。

下面是一份測試代碼,對上面 Accordion 組件里 “實(shí)現(xiàn)細(xì)節(jié)” 進(jìn)行測試:

// __tests__/accordion.enzyme.js
import * as React from 'react'
// 為什么不用 shadow render,請看 https://kcd.im/shallow
import Enzyme, {mount} from 'enzyme'
import EnzymeAdapter from 'enzyme-adapter-react-16'
import Accordion from '../accordion'
// 設(shè)置 Enzymes 的 Adpater
Enzyme.configure({adapter: new EnzymeAdapter()})
test('setOpenIndex sets the open index state properly', () => {
const wrapper = mount(<Accordion items={[]} />)
expect(wrapper.state('openIndex')).toBe(0)
wrapper.instance().setOpenIndex(1)
expect(wrapper.state('openIndex')).toBe(1)
})
test('Accordion renders AccordionContents with the item contents', () => {
const hats = {title: 'Favorite Hats', contents: 'Fedoras are classy'}
const footware = {
title: 'Favorite Footware',
contents: 'Flipflops are the best',
}
const wrapper = mount(<Accordion items={[hats, footware]} />)
expect(wrapper.find('AccordionContents').props().children).toBe(hats.contents)
})

相信有不少同學(xué)會用 Enzyme 寫過上面類似的代碼。好,現(xiàn)在讓我們來搞點(diǎn)事情...

重構(gòu)中的 “假錯(cuò)誤”

我知道大多數(shù)人都不喜歡寫測試,特別是寫 UI 測試。原因千千萬,但其中我聽得最多的一個(gè)原因就是:大部分人會花特別多的時(shí)間來伺候這些測試代碼(指測試實(shí)現(xiàn)細(xì)節(jié)的測試代碼)。

每次我改點(diǎn)東西,測試都會崩!—— 心聲。

一旦測試代碼寫得不好,會嚴(yán)重拖垮你的開發(fā)效率。下面來看看這類的測試代碼會產(chǎn)生怎樣的問題。

假如說,現(xiàn)在我們要 將這個(gè)組件重構(gòu)成可以展開多個(gè) Item,而且這個(gè)改動只能改變代碼的實(shí)現(xiàn),不影響現(xiàn)有的組件行為。得到重構(gòu)后代碼是這樣的:

class Accordion extends React.Component {
state = {openIndexes: [0]}
setOpenIndex = openIndex => this.setState({openIndexes: [openIndex]})
render() {
const {openIndexes} = this.state
return (
<div>
{this.props.items.map((item, index) => (
<>
<button onClick={() => this.setOpenIndex(index)}>
{item.title}
</button>
{openIndexes.includes(index) ? (
<AccordionContents>{item.contents}</AccordionContents>
) : null}
</>
))}
</div>
)
}
}

上面將 openIndex 改成 openIndexes,讓 Accordion 可以一次展示多個(gè) AccordionContents??雌饋矸浅M昝?,而且在 UI 真實(shí)的使用場景中也沒任何問題,但當(dāng)我們回去跑一下測試用例,??kaboom??,會發(fā)現(xiàn) setOpenIndex sets the open index state properly 這個(gè)測試用例直接報(bào)錯(cuò):

expect(received).toBe(expected)
Expected value to be (using ===):
0
Received:
undefined

由于我們把 openIndex 改成 openIndexes,所以在測試中 openIndex 的值就變成了 undefined 了。 可是,這個(gè)報(bào)錯(cuò)是真的能說明我們的組件有問題么?No!在真實(shí)環(huán)境下,組件用得好好的。

這種情況就是上面所說的 “假錯(cuò)誤”。 它的意思是測試用例雖然失敗了,但它是因?yàn)闇y試代碼有問題所以崩了,并不是因?yàn)闃I(yè)務(wù)代碼/應(yīng)用代碼導(dǎo)致崩潰了。

好,我們來把它修復(fù)一下,把原來的 toEqual(0) 改成 toEqual([0]),把 toEqual(1) 改成 toEqual([1]):

test('setOpenIndex sets the open index state properly', () => {
const wrapper = mount(<Accordion items={[]} />)
expect(wrapper.state('openIndexes')).toEqual([0])
wrapper.instance().setOpenIndex(1)
expect(wrapper.state('openIndexes')).toEqual([1])
})

小結(jié)一下:當(dāng)重構(gòu)的時(shí)候,這些測試“實(shí)現(xiàn)細(xì)節(jié)”的測試用例很可能出現(xiàn) “假錯(cuò)誤”,導(dǎo)致出現(xiàn)很多難維護(hù)、煩人的測試代碼。

“假正確”

好,現(xiàn)在我們來看另一種情況 “假正確”。假如現(xiàn)在你同事看到這段代碼:?

<button onClick={() => this.setOpenIndex(index)}>{item.title}</button>

他覺得:每次渲染都要生成一個(gè) () => this.setOpenIndex(index) 函數(shù)太影響性能了,我們要盡量減少重新生成函數(shù)的次數(shù),直接用第一次定義好的函數(shù)就好了,然后就改成了這樣:

<button onClick={this.setOpenIndex}>{item.title}</button>

一跑測試,唉,完美通過了~ ??,沒到瀏覽器去跑跑頁面就把代碼提交了,等別人一拉代碼,頁面又不能用了。(如果大家不清楚這里為什么不能用 onClick={this.setOpenIndex} 可以搜一下 Class Component onClick 的 bind 操作)。

那這里的問題是什么呢?我們不是已經(jīng)有一個(gè)測試用例來證明 “只要 setOpenIndex 調(diào)用了,狀態(tài)就會改變” 了么?對!有。但是,這并不能證明 setOpenIndex 是真的綁定到了的 onClick 上!所以我們還要另外再寫一個(gè)測試用例來測 setOpenIndex 真的綁到 onClick 了。

大家發(fā)現(xiàn)問題了么?因?yàn)槲覀冎粶y了業(yè)務(wù)中非常小的一個(gè)實(shí)現(xiàn)細(xì)節(jié),所以為測這個(gè)實(shí)現(xiàn)細(xì)節(jié),我們不得不補(bǔ)另外很多測試用例,來測其它毫不相關(guān)的實(shí)現(xiàn)細(xì)節(jié),那這樣我們永遠(yuǎn)都不可能補(bǔ)完所有實(shí)現(xiàn)細(xì)節(jié)的測試代碼。

這就是上面說的 “假正確”。 它是指,在我們跑測試時(shí)用例都通過了,但實(shí)際上業(yè)務(wù)代碼/應(yīng)用代碼里是有問題的,用例是應(yīng)該要拋出錯(cuò)誤的!那我們應(yīng)該怎么才能覆蓋這些情況呢?好吧,那我們只能又寫一個(gè)測試來保證 “點(diǎn)擊按鈕后可以正常更新狀態(tài)”。然后呢,我們還得添加一個(gè) 100% 的覆蓋率指標(biāo),這樣才能完美保證不會有問題。還要寫一些 ESLint 的插件來防止其它人來用這些 API。

算了,給這些 “假正確” 和 “假錯(cuò)誤” 打補(bǔ)丁,還不如不寫測試,把這些測試都干了得了。如果有一個(gè)工具可以解決這個(gè)問題不是更好嗎?是的,有的!

不再測試實(shí)現(xiàn)細(xì)節(jié)

當(dāng)然你也可能用 Enzyme 去重寫這些測試用例,然后限制其它人別用上面這些 API,但是我可能會選擇 React Testing Library,因?yàn)樗?API 本身限制了開發(fā)者,如果有人想用它來做 “實(shí)現(xiàn)細(xì)節(jié)” 的測試,這將會非常困難。

下面我們來看看 RTL 是怎么做測試的吧:

// __tests__/accordion.rtl.js
import '@testing-library/jest-dom/extend-expect'
import * as React from 'react'
import {render, screen} from '@testing-library/react'
import userEvent from '@testing-library/user-event'
import Accordion from '../accordion'
test('can open accordion items to see the contents', () => {
const hats = {title: 'Favorite Hats', contents: 'Fedoras are classy'}
const footware = {
title: 'Favorite Footware',
contents: 'Flipflops are the best',
}
render(<Accordion items={[hats, footware]} />)
expect(screen.getByText(hats.contents)).toBeInTheDocument()
expect(screen.queryByText(footware.contents)).not.toBeInTheDocument()
userEvent.click(screen.getByText(footware.title))
expect(screen.getByText(footware.contents)).toBeInTheDocument()
expect(screen.queryByText(hats.contents)).not.toBeInTheDocument()
})

只需一個(gè)測試用例就可以驗(yàn)證所有的組件行為。無論有沒有調(diào)用 openIndex、openIndexes 還是 tacosAreTasty,用例都會通過。這樣就可以解決這些 “假錯(cuò)誤” 了。如果沒有正確綁定 onClick 點(diǎn)擊事件,也會報(bào)錯(cuò)。這樣也可以解決 “假正確” 的問題。好處是,我們不再需要記住那些復(fù)雜的實(shí)現(xiàn)邏輯,只要關(guān)注理想情況下組件的使用行為,就可以測出用戶使用的真實(shí)場景了。

到底什么才是實(shí)現(xiàn)細(xì)節(jié)(Implementation Details)

簡單來說就是:

實(shí)現(xiàn)細(xì)節(jié)(Implementaion Details)就是:使用你代碼的人不會用到、看到、知道的東西。

那誰才是我們代碼的用戶呢?第一種就是跟頁面交互的真實(shí)用戶。第二種則是使用這些代碼的開發(fā)者。對 React Component 來說,用戶則是可以分為 End User 和 Developer,我們只需要關(guān)注這兩即可 。

接下來的問題就是:我們代碼中的哪部分是這兩類用戶會看到、用到和知道的呢?對 End User 來說,他們只會和 render 函數(shù)里的內(nèi)容有交互。而 Developer 則會和組件傳入的 Props 有交互。所以,我們的測試用例只和傳入的 Props 以及輸出內(nèi)容的 render 函數(shù)進(jìn)行交互就夠了。

這也正是 React Testing Library 的測試思路:把 Mock 的 Props 傳給 Accordion 組件,然后通過 RTL 的 API 來驗(yàn)證 render 函數(shù)輸出的內(nèi)容、測試<button/>的點(diǎn)擊事件。

現(xiàn)在回過頭再來看 Enzyme 這個(gè)庫,開發(fā)者一般都是用它來訪問 state 和 openIndex 來做測試。這其實(shí)對上面提到的兩類用戶來說,都是毫無意義的,因?yàn)樗麄兏静恍枰朗裁春瘮?shù)被調(diào)用了、哪個(gè) index 被改了、index 是存成數(shù)組了還是字符串。然而 Enzyme 的測試用例基本都是在測這些別人根本不 care 的內(nèi)容。

這也是為什么 Enzyme 測試用例為什么這么容易出現(xiàn) “假錯(cuò)誤”,因?yàn)?當(dāng)用它來寫一些 End User 和 Developer 都不 care 的測試用例時(shí),我們實(shí)際上是在創(chuàng)造第三個(gè)用戶視角:Tests 本身!。而 Tests 這個(gè)用戶,正好是誰都不會 care 的那個(gè)。所以,自動化測試應(yīng)該只服務(wù)于生產(chǎn)環(huán)境的用戶而不是這個(gè)誰都不會 care 的第三者。

當(dāng)你的測試和你軟件使用方式越相似,那么它給你的信心就越大 —— Kent。

React Hooks?

不使用 Enzyme 的另一個(gè)原因是 Enzyme 在 React Hooks 使用上有很多問題。事實(shí)證明,當(dāng)測試代碼 “實(shí)現(xiàn)細(xì)節(jié)” 時(shí),“實(shí)現(xiàn)細(xì)節(jié)” 的中的任何修改都會對測試有很大的影響。這是個(gè)很大的問題,因?yàn)槿绻銖?Class Component 遷移到 Function Component,你的測試用例是很難保證你會不會搞崩里面哪些東西的。React Testing Library 則可以很好地避免這些問題。

Implementation detail free and refactor friendly。

總結(jié)

我們應(yīng)該如何避免測試 “實(shí)現(xiàn)細(xì)節(jié)” 呢?首先是要用正確的工具,比如 React Testing Library :)。

如果你還是不知道應(yīng)該測試什么,可以跟著下面這個(gè)流程走一波:

  • 如果崩了,哪些沒有測試過的代碼影響最嚴(yán)重?(檢查流程)。
  • 盡量將測試用例縮小到一個(gè)單元或幾個(gè)代碼單元(比如:按下結(jié)賬按鈕,會發(fā)一個(gè) /checkout 請求)。
  • 思考一下誰是這部分代碼的真實(shí)用戶?(比如:Developer 拿來渲染結(jié)賬表單,End User 會用它操作點(diǎn)擊按鈕)。
  • 給使用者寫一份操作清單,并手動測試確認(rèn)功能正常(用假數(shù)據(jù)在購物車中渲染表單,點(diǎn)擊結(jié)賬按鈕,確保假 /checkout 請求執(zhí)行,并獲取成功的響應(yīng),確??梢哉故境晒ο?。
  • 將這份手動操作清單轉(zhuǎn)化成自動化測試。

好了,這篇外文就給大家?guī)У竭@里了,希望對大家在單測中有所幫助。總的來說,在測試組件方面應(yīng)該更多關(guān)注 Props 以及 render 出來的內(nèi)容。測試 “實(shí)現(xiàn)細(xì)節(jié)” 有點(diǎn)像我們?nèi)鲋e,一次撒謊就要撒更多的謊來圓第一個(gè)謊,當(dāng)我們在測試一個(gè)細(xì)節(jié)的時(shí)候,我們只能管中窺豹,這無形中會產(chǎn)生一個(gè)不存在的用戶:Test,這也是為什么很多人覺得代碼一改,測試也得改的原因。

參考資料

[1]原文: https://kentcdodds.com/blog/testing-implementation-details。

責(zé)任編輯:姜華 來源: 寫代碼的海怪
相關(guān)推薦

2022-04-10 11:52:43

前端單測程序

2022-12-23 19:22:47

前端單測

2022-07-06 08:34:17

前端單測項(xiàng)目

2015-06-04 11:22:22

前端程序員

2021-06-25 10:57:30

前端自動化測試開發(fā)

2015-06-05 11:23:19

前端為什么不要你

2014-05-19 13:20:37

數(shù)據(jù)管理

2018-04-20 14:37:43

互聯(lián)網(wǎng)技術(shù)細(xì)節(jié)

2023-03-08 22:37:59

單測業(yè)務(wù)系統(tǒng)

2024-04-01 08:26:30

單測覆蓋率字節(jié)碼

2023-03-14 22:32:24

業(yè)務(wù)單測數(shù)量

2023-04-11 08:02:26

單測技術(shù)JUnit框架

2022-10-31 08:29:37

MySQL單表參數(shù)

2023-06-29 18:08:41

2012-05-03 11:27:15

筆記本評測

2015-03-11 18:49:53

Testin眾測云測

2021-01-22 06:03:55

核酸隔離APP

2021-04-11 07:20:01

應(yīng)用APP濫用手機(jī)隱私風(fēng)險(xiǎn)

2017-02-16 07:37:19

前端程序軟件

2024-12-23 07:20:00

點(diǎn)贊
收藏

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