精品欧美一区二区三区在线观看 _久久久久国色av免费观看性色_国产精品久久在线观看_亚洲第一综合网站_91精品又粗又猛又爽_小泽玛利亚一区二区免费_91亚洲精品国偷拍自产在线观看 _久久精品视频在线播放_美女精品久久久_欧美日韩国产成人在线

React全家桶與前端單元測(cè)試藝術(shù)

開發(fā) 前端
單元測(cè)試的好壞在于“單元”而不在“測(cè)試”。如果一個(gè)系統(tǒng)毫無單元可言,那就沒法進(jìn)行單元測(cè)試,幾乎只能用 Selenium做大量的E2E測(cè)試,其成本和穩(wěn)定性可想而知。科學(xué)的單元?jiǎng)澐挚梢宰屇銛[脫mock,減少依賴提高并行度,不依賴實(shí)現(xiàn)/易重構(gòu),提高測(cè)試對(duì)業(yè)務(wù)的覆蓋率以及易學(xué)易用,大幅減少測(cè)試代碼。

TL;DR——什么是好的單元測(cè)試?

其實(shí)我是個(gè)標(biāo)題黨,單元測(cè)試根本沒有“藝術(shù)”可言。

好的測(cè)試來自于好的代碼,如果說有藝術(shù),那也是代碼的藝術(shù)。

注:以下“測(cè)試”一詞,如非特指均為單元測(cè)試。

React全家桶與前端單元測(cè)試藝術(shù)

單元測(cè)試的好壞在于“單元”而不在“測(cè)試”。如果一個(gè)系統(tǒng)毫無單元可言,那就沒法進(jìn)行單元測(cè)試,幾乎只能用 Selenium 做大量的E2E測(cè)試,其成本和穩(wěn)定性可想而知。科學(xué)的單元?jiǎng)澐挚梢宰屇銛[脫mock,減少依賴,提高并行度,不依賴實(shí)現(xiàn)/易重構(gòu),提高測(cè)試對(duì)業(yè)務(wù)的覆蓋率,以及易學(xué)易用,大幅減少測(cè)試代碼。

最好的單元是返回簡單數(shù)據(jù)結(jié)構(gòu)的函數(shù):函數(shù)是最基本的抽象,可大可小,不需要mock,只依靠傳參。簡單數(shù)據(jù)結(jié)構(gòu)可以判等。 最好的測(cè)試工具是Assert.Equal這種的:只是判等。判等容易,判斷發(fā)生了什么很難。你可以看到后面對(duì)于DOM和異步操作這些和副作用相關(guān)的例子都靠判等測(cè)試。把作用冪等于數(shù)據(jù),拿到數(shù)據(jù)就一定發(fā)生作用,然后再測(cè)數(shù)據(jù),是一個(gè)基本思路。

以上是你以前學(xué)習(xí)測(cè)試第一天就會(huì)的內(nèi)容,所以不存在門檻。

為什么不談TDD?

首先, TDD 肯定是有價(jià)值的(價(jià)值大小不論)。反對(duì)TDD的原因一般比較明顯,對(duì)于TDD是否帶來正收益不確定(動(dòng)機(jī)不足)。 某些項(xiàng)目質(zhì)量要求很高,預(yù)算寬綽,TDD勢(shì)在必行。某些項(xiàng)目比較緊急,或者并非關(guān)鍵或無長期維護(hù)計(jì)劃,TDD理由就不充分。

為什么談測(cè)試?

因?yàn)闇y(cè)試難。

第一難學(xué),第二難寫。寫測(cè)試是個(gè)挺困難的活,要在測(cè)試?yán)镎_重演業(yè)務(wù)要費(fèi)好大勁,只能靠反復(fù)練習(xí)。雖然這些測(cè)試在某些項(xiàng)目中是值得的,但是可能并不適合其他某些項(xiàng)目的基本情況。

測(cè)試難,就代表訓(xùn)練成本高,生產(chǎn)成本也高,收益就下降。要提高采用TDD的動(dòng)機(jī),與其說服別人,不如從簡化測(cè)試開始。

[[203003]]
(圖片來自: http://t.cn/Rpw9WKg )

為什么談前端測(cè)試?

一般項(xiàng)目都是后端測(cè)試覆蓋率高,同時(shí)后端套路也比較固定。測(cè)RESTful API粒度足夠大,可以很好地避開實(shí)現(xiàn)并且覆蓋業(yè)務(wù)。同時(shí)RESTful API一般也正好對(duì)應(yīng)Web框架的Action handler,在這里同時(shí)它粒度也足夠小,剛好可以直接調(diào)用而不啟動(dòng)真的Web server,使得測(cè)試最大程度并行化。所以這樣測(cè)試收益總是最高的,爭議很小。

前端不說套路不固定,測(cè)不測(cè)都有待商榷。因?yàn)榍岸肆髋刹唤y(tǒng)一,資源不規(guī)則,邊界也不清晰,有渲染又有點(diǎn)業(yè)務(wù),有導(dǎo)航有請(qǐng)求,很多團(tuán)隊(duì)不測(cè)試/測(cè)Model/測(cè)Component/測(cè)E2E,五花八門。 但得益于JavaScript本身,前端測(cè)試其實(shí)是可以非常高效的。

下面你可以看到各種極簡極快的測(cè)試工具和測(cè)試方式,并且它們完全可以 貫穿開發(fā)始終,而非僅給Hello World體量項(xiàng)目準(zhǔn)備的 ,你可以在很大的全家桶項(xiàng)目中完全機(jī)械地套用這些方法。(機(jī)械也是極限的一部分,你不應(yīng)該在使用工具過程中面臨太多抉擇,而應(yīng)當(dāng)專注于將業(yè)務(wù)翻譯成測(cè)試)。

為什么談React全家桶?

前端從每周刷新一個(gè)框架,穩(wěn)定到了 Angular , React , Vue 3個(gè)主流框架并存的階段。網(wǎng)絡(luò)中爭論這三個(gè)框架蓋的樓已經(jīng)可以繞太陽系了。根據(jù)蓋的各種大樓看來,現(xiàn)在哪個(gè)更優(yōu)秀還沒個(gè)定論。不過具體到單元測(cè)試方面,得益于Virtual DOM本身和模塊化設(shè)計(jì)(不然全家桶白叫了),React全家桶明顯更優(yōu)秀些。

測(cè)試工具

我們本篇中的測(cè)試有三個(gè)目標(biāo): 學(xué)得快,寫得快,跑得快 。

[[203004]]
(圖片來自: http://t.cn/RpwCke3 )

平臺(tái)上 Selenium , Phantom , Chrome , 包括 Karma 都比較重,最好的測(cè)試框架就是直接跑在 node 上的。本著極限編程的原則,我們將測(cè)試本身和測(cè)試環(huán)境盡可能簡化,以達(dá)到加快測(cè)試速度,最終反饋到開發(fā)速度的目的。

我們使用 AVA 進(jìn)行測(cè)試,它非常簡潔,速度非常快,和 mocha 不同,它默認(rèn)會(huì)啟動(dòng)多線程并發(fā)測(cè)試。因此我們的測(cè)試必須減少共享狀態(tài)來提高并發(fā)能力,不然就會(huì)出現(xiàn)意想不到的錯(cuò)誤。安裝和運(yùn)行: 

  1. yarn add ava 
  2. ava --watch 

 這樣可以運(yùn)行并watch測(cè)試。改變代碼測(cè)試結(jié)果會(huì)立刻改變,你也可以看到友善的錯(cuò)誤信息,以及expected和actual之間的diff。寫下第一段測(cè)試: 

  1. import test from 'ava' 
  2.  
  3. test(t => { 
  4.   t.is(1 + 1, 2) 
  5. }) 

 除了is方法以外,我們還會(huì)用到deepEqual和true方法。好,你現(xiàn)在已經(jīng)完全會(huì)用AVA了。其他的功能我們完全不關(guān)心。

Redux測(cè)試 (Model測(cè)試)

Redux 就是用一堆Reducer函數(shù)來reduce所有事件用來做全局Store的狀態(tài)機(jī)( FSM )。用源碼本身介紹它甚至比用上一小段文字介紹還快: 

  1. const createStore = reducer => { 
  2.   let state, listeners = [] 
  3.  
  4.   const dispatch = action => { 
  5.     state = reducer(state, action
  6.     listeners.forEach(listeners => listeners()) 
  7.   } 
  8.  
  9.   return { 
  10.     getState() { return state }, 
  11.     subscribe(listener) { 
  12.       listeners.push(listener) 
  13.       return () => { listeners = listeners.filter(l => l !== listener)} 
  14.     }, 
  15.     dispatch, 
  16.   } 

 這是一個(gè)簡化版的代碼,去掉了拋錯(cuò)等等細(xì)節(jié),但功能是完整的。把你自己寫的reducer扔進(jìn)去,然后可以發(fā)事件來使其更新,你還可以訂閱它來拿狀態(tài)。有點(diǎn)像 Event Sourcing ,以消息而非調(diào)用來處理邏輯,更新和訂閱的邏輯不在一起(事件是寫模型,各種view就是多個(gè)讀模型)。

reducer幾乎包括了我們所有前端業(yè)務(wù)的核心,測(cè)好它就測(cè)了大半。它們?nèi)际?(State, Action) => nextState 形式的純函數(shù),無異步操作,用swtich case來模擬模式匹配來處理事件。比如用喜聞樂見的簡陋版的棧停車場(chǎng)舉例: 

  1. export const parkingLot = (state = [], action) => { 
  2.   switch (action.type) { 
  3.     case 'parkingLot/PARK'
  4.       return [action.car, ...state] 
  5.     case 'parkingLot/PICK'
  6.       const [_, ...rest] = state 
  7.       return rest 
  8.     defaultreturn state 
  9.   } 

 Reducer是這么用的: 

  1. const store = createStore(parkingLot) 
  2. store.subscribe(() => renderMyView(store.getState())) 
  3. store.dispatch({ type: 'parkingLot/PARK' }) 

 好,現(xiàn)在你又理解了Redux。那我們可以看看怎么測(cè)試上面的parkingLot reducer了: 

  1. test('parking lot', t => { 
  2.   const initial = parkingLot(undefined, {}) 
  3.   t.deepEqual(initial, [], 'should be empty when init'
  4.  
  5.   const parked = parkingLot(initial, { type: 'parkingLot/PARK', car: 'Tesla Model S' }) 
  6.   t.deepEqual(parked, ['Tesla Model S'], 'should park Model S in lot'
  7.  
  8.   const picked = parkingLot(parked, { type: 'parkingLot/PICK' }) 
  9.   t.deepEqual(picked, [], 'should remove the car'
  10. }) 

 它就是你第一天學(xué)測(cè)試就會(huì)寫的那種測(cè)試。這些測(cè)試不受任何上下文影響,是冪等的。試著把那幾個(gè)const聲明的state挪到任何地方,你都可以發(fā)現(xiàn)測(cè)試還是正確的,這和我們平常小心翼翼分離各個(gè)測(cè)試case,并用beforeEach和afterEach重置截然不同。

[[203005]]
(圖片來自: http://t.cn/RpwS3AK )

測(cè)試Reducer是非常機(jī)械的,你不需要問自己“我到底應(yīng)該測(cè)哪些東西”,只需要機(jī)械地測(cè)試初始state和每個(gè)switch case就好了。(小秘密:redux-devtools寫完實(shí)現(xiàn),在瀏覽器里打開,反過來還可以自動(dòng)生成各種框架的測(cè)試代碼,粘貼回來就行了。推薦不寫測(cè)試的項(xiàng)目嘗試下,反正白送的測(cè)試……而且跟你寫的沒兩樣)

隨著業(yè)務(wù)變得復(fù)雜,當(dāng)state樹變大時(shí),我們可以將reducer結(jié)構(gòu)繼續(xù)往下抽,并繼續(xù)傳遞事件,函數(shù)沒有this,重構(gòu)起來比普通OO要簡單得多,就不贅述了。這時(shí)候測(cè)試還是完全一樣的,這種樹形結(jié)構(gòu)保證了我們能最大限度地覆蓋一個(gè)bounded context—也就是root reducer。

另外更好的方式是用t.is(斷言引用相同)而非t.deepEqual。但是JavaScript對(duì)象本身是可變的,引入 immutable.js 可以讓你只用t.is測(cè)試,不過immutable的API有點(diǎn)別扭,不展開了。

組件測(cè)試 (View測(cè)試)

React是一個(gè)View library,它干的活就是DOM domain里的兩個(gè)事:渲染和捕獲事件。我們?cè)谶@里依然從簡,只用stateless component這個(gè)子集,雖然在用到生命周期方法的時(shí)候需要用一下class,但絕大多數(shù)時(shí)候應(yīng)該只用stateless component。

它以Virtual DOM的形式封裝了惡心的瀏覽器基礎(chǔ)設(shè)施,讓我們以函數(shù)和數(shù)據(jù)結(jié)構(gòu)來描述組件,所以和大部分框架不同,我們的測(cè)試依然可以在node上并行運(yùn)行。如果用Karma + Chrome真正地渲染測(cè)試,你會(huì)發(fā)現(xiàn)共享一個(gè)瀏覽器實(shí)例的測(cè)試非常慢,幾乎無法watch測(cè)試,因此我們的TDD cycle就會(huì)變得不那么流暢了。

最基本的就是 state => UI 這種純函數(shù)組件: 

  1. const Greeter = ({ name }) => <p>Greetings {name}!</p> 

 使用的時(shí)候就像HTML一樣傳遞attribute就可以了。 

  1. render(<Greeter name="React"/>, document.body) 

最簡單的測(cè)試還是判等,我們用一個(gè)叫 jsx-test-helpers 的庫來幫我們渲染: 

  1. import { renderJSX, JSX } from 'jsx-test-helpers' 
  2.  
  3. const Paragraph = ({ children }) => <p>{children}</p> 
  4. const Greeter = ({ name }) => <Paragraph>Greetings {name}!</Paragraph> 
  5.  
  6. test('Greeter', t => { 
  7.   t.is(renderJSX(<Greeter name="React"/>),  
  8.        JSX(<Paragraph>Greetings React!</Paragraph>),  
  9.        'should render greeting text with name'
  10. }) 

 這里我多加了一層叫做Paragraph的組件,它的作用僅僅是傳遞給p標(biāo)簽,children這個(gè)prop表示XML標(biāo)簽傳進(jìn)來的子元素。多加這層Paragraph是為了展示renderJSX只向下渲染了一層,而非最終需要渲染的p標(biāo)簽。這樣我們?cè)赩iew上的測(cè)試粒度就會(huì)變得更小,成本更低,速度更快。

[[203006]]
(圖片來自: http://t.cn/RpwYskG )

 

View不像業(yè)務(wù)本身那么穩(wěn)定,細(xì)粒度低成本的快速測(cè)試更劃算些,這也是為什么我們的View都只是接受參數(shù)渲染,這樣你只用測(cè)很少的case就能保證View可以正確渲染。假如你的FSM Model有M種可能性,View顯示的邏輯有N種,如果將兩個(gè)集成在一起測(cè)試可能就需要M×N種Path,如果分開測(cè)就有M+N種。View和Model的邊界清晰時(shí),你的Model測(cè)試不容易被更困難的View測(cè)試干擾,View測(cè)試也減少了混沌程度,需要測(cè)試的情形就減少了。

我們的組件不應(yīng)該只有渲染,還有事件,比如我們封裝個(gè)TextField組件: 

  1. const TextField = ({ label, onChange }) => <label> 
  2.   {label} 
  3.   <input type="text" onChange={onChange} /> 
  4. </label> 

 當(dāng)然我們還可以判等,只要onChange函數(shù)引用相同就好了。 

  1. test('TextField', t => { 
  2.   const onChange = () => {} 
  3.   const actual = renderJSX(<TextField label="Email" onChange={onChange} />) 
  4.   const expected = JSX(<label> 
  5.     Email 
  6.     <input type="text" onChange={onChange}/> 
  7.   </label>) 
  8.   t.is(actual, expected) 
  9. }) 

 當(dāng)然有時(shí)候你的組件更復(fù)雜些,測(cè)試時(shí)并不關(guān)心組件是不是完全按你想要的樣子渲染,可能你想像 jQuery 一樣選擇什么,觸發(fā)什么。這樣可以用更主流的 enzyme 來測(cè)試: 

  1. import {shallow} from 'enzyme' 
  2. import sinon from 'sinon' 
  3.  
  4. test('TextField with enzyme', t => { 
  5.   const onChange = sinon.spy() 
  6.   const wrapper = shallow(<TextField label="Email" onChange={onChange} />) 
  7.   t.true(wrapper.contains(<label>Email</label>), 'should render label'
  8.  
  9.   const event = { target: { value: 'foo@bar.com' } } 
  10.   wrapper.find('input').simulate('change', event) 
  11.   t.true(onChange.calledWith(event)) 
  12. }) 

 這里用的shallow顧名思義,也是向下渲染一層。此外我們還用了spy,這樣測(cè)試就變得有點(diǎn)復(fù)雜了,丟掉了我們之前聲明式的優(yōu)雅,所以組件還是小一點(diǎn)、一下測(cè)完比較好。

還不夠快?Facebook就覺得不夠快,他們覺得View測(cè)試成本比較浪費(fèi),干脆搞了個(gè)Snapshot測(cè)試——意思就是照個(gè)像,只斷言它不變。下次誰改了別的地方不小心影響到這里,就會(huì)掛掉,如果無意的就修好,如果有意的話和git一樣commit一下就修好了: 

  1. import render from 'react-test-renderer' 
  2.  
  3. test('Greeter', t => { 
  4.   const tree = render.create(<Greeter name="React"/>).toJSON() 
  5.   t.snapshot(tree, 'should not change'
  6. }) 

 當(dāng)你修改Greeter的時(shí)候,測(cè)試就會(huì)掛掉,這時(shí)候運(yùn)行:

  1. ava --update-snapshots 

就好了。Facebook自家的 Jest 對(duì)snapshot的支持更好,當(dāng)snapshot不匹配時(shí)按個(gè)y/n就完事了,夠快了吧。要有更快的可能就是不測(cè)了……

小結(jié)

這節(jié)里我們展示了3種測(cè)試View的不同方式,它們都比傳統(tǒng)框架更簡單更快速。我們的思路還是以判等為主,但不同于Model,粒度越大越好。View測(cè)試粒度越小越好,足夠小、足夠冪等之后,其實(shí)不用測(cè)試你也可以發(fā)現(xiàn)組件總是按照預(yù)期工作。相比之下MVVM天然有一種讓View和Model粒度擬合的傾向,很容易讓測(cè)試變得既難測(cè)又缺乏價(jià)值。

[[203007]]

異步Effect測(cè)試

這算個(gè)續(xù)集……異步操作不復(fù)雜的項(xiàng)目可以無視這段,可以選擇性不測(cè)。

React先解決了惡心的DOM問題,把Model的問題留下了。然后Redux把同步邏輯解決了,其實(shí)前端還留下異步操作的大問題沒有解決。這種類似“Unix只做一件事”的哲學(xué)是React全家桶的根基。我們用一個(gè)叫做 Redux-saga 的庫來展現(xiàn)全家桶的異步測(cè)試怎么寫,Redux模仿的目標(biāo)是 Elm architecture,但是簡化掉了Elm的作用模型,只保留了同步模型,Redux-saga其實(shí)就是把Elm的作用模型又拿回來了。

Saga是一種worker模式,很早之前在Java社區(qū)就存在了。Redux-saga抽象出來多種通用的作用比如call / takeEvery等等,然后有了這些作用,我們又可以愉快地判等了。比如:

 

  1. import { takeEvery, put, call, fork, cancel } from 'redux-saga/effects' 
  2.  
  3. function *account() { 
  4.   yield call(takeEvery, 'login/REQUESTED', login) 
  5. function *login({ namepassword }) { 
  6.   try { 
  7.     const { token } = yield call(fetch'/login', { method: 'POST', body: { namepassword } }) 
  8.     yield put({ type: 'login/SUCCEEDED', token }) 
  9.   } 
  10.   catch (error) { 
  11.     yield put ({ type: 'login/FAILED', error }) 
  12.   } 

 

這段代碼乍看起來很丑,這是因?yàn)樗殉绦蚶锼挟惒讲僮魅技性谧约荷砩狭恕F渌糠侄伎梢蚤_心地發(fā)同步事件了,此外有了Saga之后Redux終于有了“用事件觸發(fā)事件”的機(jī)制了,只用redux,應(yīng)用復(fù)雜到一定程度你一定會(huì)想這個(gè)問題的。

這是個(gè)最普通的API處理saga,一個(gè)account worker看到每個(gè)’login/REQUESTED’就會(huì)forward給login worker(takeEvery),讓它繼續(xù)管下面的事。然后login worker拿到消息就會(huì)去發(fā)請(qǐng)求(call),之后傻傻地等著回復(fù),或者是出錯(cuò)。最后它會(huì)發(fā)出和結(jié)果相關(guān)的事件。用這個(gè)方式你可以輕松解決瘋狂難度的異步問題。

 

  1. test('account saga', t => { 
  2.   const gen = account() 
  3.   t.deepEqual(gen.next().value, call(takeEvery, 'login/REQUESTED', login)) 
  4. }) 
  5.  
  6. test('login saga', t => { 
  7.   const gen = login({ name'John'password'super-secret-123'}) 
  8.  
  9.   const request = gen.next().value 
  10.   t.deepEqual(request, call(fetch'/login', { method: 'POST', body: { name'John'password'super-secret-123'} })) 
  11.   const response = gen.next({ token: 'non-human-readable-token' }).value 
  12.   t.deepEqual(response, put({ type: 'login/SUCCEEDED', token: 'non-human-readable-token' })) 
  13.   const failure = gen.throw('You code just exploded!').value 
  14.   t.deepEqual(failure, put({ type: 'login/FAILED', error: 'You code just exploded!'})) 
  15. }) 

 

你看我們的測(cè)試連異步操作都還可以無恥地判等。call就是以某些參數(shù)調(diào)用某個(gè)函數(shù),put就是發(fā)事件。

可以試著把fetch覆蓋成空函數(shù),你可以發(fā)現(xiàn)實(shí)際上副作用根本沒發(fā)生,“fetch到底是個(gè)啥”對(duì)測(cè)試一點(diǎn)影響都沒有。你可能發(fā)現(xiàn)了,其實(shí)saga就是用數(shù)據(jù)結(jié)構(gòu)表示作用,而不著急執(zhí)行,在這里又走回冪等的老路了。這和React Virtual DOM的思路異曲同工。

結(jié)語

首先是文章開頭提到的TL;DR的內(nèi)容。函數(shù)是個(gè)好東西,測(cè)函數(shù)不等同“測(cè)1+1=2”這種沒營養(yǎng)的單元,函數(shù)是可以包含很大上下文的。這種輸入輸出的模型既簡單又有效。

我們消滅了mock,減少了依賴,并發(fā)了測(cè)試,加快了速度,降低了門檻,減少了測(cè)試路徑等等。如果你的React項(xiàng)目原來在TDD的邊緣搖擺不定,現(xiàn)在是時(shí)候入一發(fā)這種唯快不破了。

全家桶讓Model/View/Async這三者之間的邊界變得清晰,任由業(yè)務(wù)變更,它們之間的職責(zé)是不會(huì)互相替代的,這樣你測(cè)它們的時(shí)候才更容易。后端之所以測(cè)試穩(wěn)定是因?yàn)橛蠥PI。所以想讓前端好測(cè)也是一樣的思路。

文中好多次提到“冪等”這個(gè)概念,冪等可以讓你減少測(cè)試的case,寫代碼更有底氣。拋開測(cè)試不談,代碼冪等的地方越多,程序越可控可預(yù)期。其實(shí)仔細(xì)思考一下我們的實(shí)際項(xiàng)目,大部分業(yè)務(wù)都是非常確定的,并沒有什么隨機(jī)因素。為什么最后還是會(huì)出現(xiàn)很多隨機(jī)現(xiàn)象呢?

聲明優(yōu)于命令,描述發(fā)生什么、想要什么比親自指導(dǎo)具體步驟好。

消息機(jī)制優(yōu)于調(diào)用機(jī)制。Smalltalk > Simula。其實(shí)RESTful API一定程度上也是消息。簡單的對(duì)象直接互相作用是完全沒問題的,人作為復(fù)雜對(duì)象主要通過語言媒介來交流,聽到內(nèi)容思考其中的含義,而不是靠肢體接觸,或者像連體嬰兒那樣共享器官。所以才有一句俗語叫“你的對(duì)象都想成長為Actor”。

從View的幾種測(cè)試?yán)镂覀円部梢钥吹剑瑴y(cè)試并不是只有測(cè)或者不測(cè)這兩種選擇,我們老提測(cè)試金字塔,意思是測(cè)試可多可少,不同層級(jí)的測(cè)試保持正金字塔形狀比較健康,像今天我們說的就可以大幅加寬你測(cè)試金字塔的底座。所以你的項(xiàng)目有可能測(cè)試過少,也可能測(cè)試過度,所以時(shí)間可以動(dòng)態(tài)調(diào)整。

沒用全家桶的項(xiàng)目可以把“大Model小View”的思想拿走,這樣更容易于專注價(jià)值。盡量抽出Model層,不要把邏輯寫在VM里,看那樣似省事,行數(shù)在測(cè)試?yán)锒歼€回來了。

責(zé)任編輯:未麗燕 來源: ThoughtWorks洞見
相關(guān)推薦

2017-09-13 15:05:10

React前端單元測(cè)試

2017-01-14 23:42:49

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

2016-09-21 15:35:45

Javascript單元測(cè)試

2021-10-12 19:16:26

Jest單元測(cè)試

2022-03-15 11:55:24

前端單元測(cè)試

2009-09-01 10:20:06

protected方法單元測(cè)試

2016-09-14 21:55:33

前端測(cè)試Karma

2022-10-26 08:00:49

單元測(cè)試React

2017-02-21 10:30:17

Android單元測(cè)試研究與實(shí)踐

2017-04-07 13:45:02

PHP單元測(cè)試數(shù)據(jù)庫測(cè)試

2016-09-26 16:42:19

JavaScript前端單元測(cè)試

2017-01-16 12:12:29

單元測(cè)試JUnit

2017-01-14 23:26:17

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

2017-03-30 07:56:30

測(cè)試前端代碼

2020-08-18 08:10:02

單元測(cè)試Java

2020-03-19 14:50:31

Reac單元測(cè)試前端

2017-03-23 16:02:10

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

2021-05-05 11:38:40

TestNGPowerMock單元測(cè)試

2023-07-26 08:58:45

Golang單元測(cè)試

2020-05-07 17:30:49

開發(fā)iOS技術(shù)
點(diǎn)贊
收藏

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

久久久免费在线观看| 中国色在线观看另类| 欧美激情国内偷拍| 99久久国产精| 欧美美女福利视频| 亚洲自拍偷拍图区| 久久久久久久久一区二区| 久草热在线观看| 大黄网站在线观看| 久久蜜桃一区二区| 亚洲在线观看视频| 天天干天天干天天干天天| 久久精品久久久| 亚洲精品av在线| av中文字幕网址| 国产午夜视频在线观看| 国产在线视频不卡二| 欧美一区二区色| 一级欧美一级日韩片| 国产精品亚洲成在人线| 午夜精品久久久久久久99水蜜桃| 亚洲va韩国va欧美va精四季| 欧美熟女一区二区| 欧美91福利在线观看| 亚洲精品自在久久| 国产51自产区| 国产精品中文| 亚洲欧美日韩国产成人精品影院| 久久久久se| 性生活免费网站| 久久99久久久欧美国产| xvideos亚洲人网站| 日韩av在线中文| 亚洲天堂手机| 国产日韩欧美一区二区三区乱码| 日本三级韩国三级久久| 久久久久亚洲天堂| 五月久久久综合一区二区小说| 精品夜色国产国偷在线| 性猛交╳xxx乱大交| 欧美成年网站| 91麻豆精品国产综合久久久久久| av五月天在线| 日韩大尺度黄色| 欧美日韩中文字幕在线视频| 奇米精品在线| 青春有你2免费观看完整版在线播放高清| 国产福利精品导航| 亚洲综合在线中文字幕| 国产又粗又猛视频| 亚洲国产一区二区精品专区| 亚洲精品一区中文字幕乱码| 制服丝袜在线第一页| 极品美女一区| 日韩理论片一区二区| 亚洲电影一二三区| 日本不卡在线| 成人欧美一区二区三区1314| 亚洲精品一区二区三区四区五区| 国产在线网站| 中文字幕欧美国产| 一本一生久久a久久精品综合蜜| 99视频在线观看地址| 国产精品久久久久久久蜜臀| 一本一本a久久| 999精品免费视频| 精品一区二区三区免费看| 3751色影院一区二区三区| 午夜视频在线观| 免费一级欧美在线大片| 精品国产区一区| 亚洲精品中文字幕在线播放| 婷婷亚洲成人| 亚洲性线免费观看视频成熟| 男人的天堂av网| 99久久夜色精品国产亚洲1000部| 久久手机免费视频| 久久久无码精品亚洲国产| 一本久道久久久| 国产国语刺激对白av不卡| 一级黄色片在线看| 黑人巨大精品欧美一区| 国产精品一区二区a| 欧洲一级在线观看| 中文字幕一区二区三区在线观看| 天天想你在线观看完整版电影免费| hd国产人妖ts另类视频| 欧美性猛交xxxx乱大交| 中文字幕 91| caoporn成人免费视频在线| 亚洲精品www| 粉嫩精品久久99综合一区| 欧美/亚洲一区| 奇米4444一区二区三区 | 欧美自拍视频在线| 伊人亚洲综合网| 成人看片黄a免费看在线| 欧洲一区二区日韩在线视频观看免费 | 日韩精品福利片午夜免费观看| aaa在线播放视频| 欧美日韩国产精选| 日本少妇毛茸茸| 五月开心六月丁香综合色啪| 91av在线免费观看| 国产毛片在线视频| 91免费国产在线| 亚洲色图都市激情| 成人精品国产亚洲| 国产丝袜一区二区三区免费视频| 秋霞欧美一区二区三区视频免费| 99视频一区| 亚洲xxxx在线| wwwxxx在线观看| 亚洲大片在线观看| 三区视频在线观看| 欧美日韩伦理| 欧洲中文字幕国产精品| 精品人妻一区二区三区浪潮在线| 欧美极品美女视频| 激情综合在线观看| 哺乳一区二区三区中文视频| 日韩中文字在线| 潘金莲一级淫片aaaaaa播放| 波多野结衣一区二区三区| 一级黄色录像免费看| 澳门av一区二区三区| 亚洲国产91精品在线观看| 午夜少妇久久久久久久久| 美国欧美日韩国产在线播放| 欧美极品色图| 中文字幕在线官网| 精品性高朝久久久久久久| 国产在线观看99| 国产精品白丝jk白祙喷水网站| 一区精品在线| 欧美风情在线视频| 中文字幕在线观看亚洲| 精品人妻无码一区| 免费精品视频| 国产精品美乳一区二区免费| 亚州男人的天堂| 精品久久久久久久久久ntr影视| 极品人妻一区二区| 首页亚洲中字| 欧美激情精品久久久久久久变态 | av剧情在线观看| 日韩一区二区三区av| 三上悠亚在线观看视频| 精品一区二区在线观看| 中文字幕久久综合| 成人免费91| 久久国产精品影视| 精品国自产在线观看| 亚洲激情中文1区| 手机看片国产精品| 亚洲图片在线| 久久精品综合一区| 国产在线|日韩| 色av吧综合网| 精品人妻一区二区三区三区四区 | 怡红院av久久久久久久| 久久久久久久久久久久久女国产乱| 国产免费毛卡片| 精品一区亚洲| 国产一区二中文字幕在线看| 国产人成网在线播放va免费| 日韩三级av在线播放| 久久久久久久9999| 91蜜桃网址入口| 美女网站免费观看视频| 香港欧美日韩三级黄色一级电影网站| 亚洲最大福利视频网站| 1234区中文字幕在线观看| 国产丝袜一区视频在线观看| 亚洲天堂国产精品| 一区二区三区自拍| www.久久国产| 麻豆精品视频在线| 久久这里只有精品23| 亚洲视频分类| 91丨九色丨国产在线| av成人影院在线| 色哟哟网站入口亚洲精品| a天堂视频在线| 欧美性猛交xxxx黑人| 老司机成人免费视频| 亚洲欧美日韩国产综合精品二区| 欧美一级片免费观看| 亚洲精品成a人ⅴ香蕉片| 久久久久久久久久久免费| 精品av中文字幕在线毛片| 制服丝袜国产精品| 日韩欧美大片在线观看| 国产成人亚洲综合a∨婷婷图片| 一二三四视频社区在线| 日韩大片在线播放| 国产在线精品一区二区中文| 日本不卡影院| 亚洲色图17p| 五月婷婷激情视频| 亚洲视频你懂的| 丰满少妇一区二区| 国产成人激情av| 精品亚洲一区二区三区四区| 亚洲午夜黄色| www亚洲国产| 尤物tv在线精品| 99国产在线视频| 久久电影天堂| 日韩美女在线观看一区| 亚洲第一图区| 精品国产一区二区在线| 欧美孕妇孕交| 精品嫩草影院久久| 国产乱淫a∨片免费观看| 亚洲图片你懂的| 99久久精品免费视频| 成人精品鲁一区一区二区| 色18美女社区| 蜜臀av性久久久久av蜜臀妖精| 日韩少妇内射免费播放18禁裸乳| 欧美精品播放| 99精品视频网站| 四虎成人精品永久免费av九九| 久久综合九色欧美狠狠| 国产精品17p| 91久久爱成人| 日韩不卡在线视频| 久久久免费电影| 国产福利在线播放麻豆| 色噜噜狠狠狠综合曰曰曰88av| 男人的天堂在线| 亚洲精品丝袜日韩| 天天射天天操天天干| 欧美精品一区二区久久婷婷| 亚洲成人一级片| 欧美一卡2卡3卡4卡| 国产精品自拍电影| 51精品久久久久久久蜜臀| 一级黄色短视频| 欧美日韩成人综合在线一区二区| 欧美日韩a v| 欧美亚洲综合另类| 怡红院男人的天堂| 欧美日韩成人高清| 一区二区三区播放| 欧美一级夜夜爽| 亚洲AV无码乱码国产精品牛牛| 日韩欧美国产成人一区二区| 欧美一二三区视频| 性久久久久久久| 国产欧美日韩另类| 欧美性色视频在线| 欧美影院久久久| 欧美在线观看在线观看| 亚洲精品视频在线播放 | 亚洲视频精品在线| 成人在线观看一区| 日韩在线观看视频免费| 国产精品久久麻豆| 欧美极品欧美精品欧美视频| 182在线视频观看| 日本高清久久天堂| 成人交换视频| 97久久天天综合色天天综合色hd| 亚洲图色一区二区三区| 国产欧美一区二区在线播放| 丝袜久久网站| 在线观看日韩片| 欧美日韩视频一区二区三区| 青青青国产在线观看| 日日夜夜免费精品| 婷婷激情小说网| 成人一级片在线观看| 亚洲第一香蕉网| 亚洲色图.com| www.国产成人| 欧美色倩网站大全免费| www.好吊色| 亚洲欧美日韩第一区| 麻豆传媒视频在线| 97视频在线播放| 欧美美女福利视频| 久久99精品久久久久久久久久| 国产在线观看91一区二区三区| 婷婷五月色综合| 精品一区二区无码| 91成人福利社区| 亚洲成人在线免费| 中文字幕在线观看免费视频| 91国产视频在线观看| 国产高潮流白浆喷水视频| 日韩精品在线播放| 老司机av在线免费看| 2019亚洲男人天堂| 成人网av.com/| 蜜桃网站成人| 综合久久十次| 国产真人无码作爱视频免费| 粉嫩一区二区三区在线看| 久久久久久国产免费a片| 亚洲尤物在线视频观看| 中文字幕制服诱惑| 日韩成人在线视频观看| 国产视频在线播放| 国产精品高清在线| 国产区精品视频在线观看豆花| 在线观看一区二区三区三州 | 91麻豆成人久久精品二区三区| 人人干在线观看| 在线一区二区三区四区五区| 亚洲黄色网址大全| 五月婷婷综合网| 99国产精品99| 色av中文字幕一区| 浪潮色综合久久天堂| 国产伦精品一区二区三区高清版| 日韩精品一区二区三区免费观看| 少妇无码av无码专区在线观看| 国产一区二区三区香蕉| 少妇太紧太爽又黄又硬又爽小说| 激情成人在线视频| 黄色片一区二区| 超碰精品一区二区三区乱码| 日本黄色一区| 日本婷婷久久久久久久久一区二区| 亚洲国产高清视频| 国产精品19p| 亚洲欧美日韩一区二区三区在线观看| 黄色网址中文字幕| 亚洲色图在线观看| 亚洲天堂av影院| 美女一区视频| 香蕉成人久久| 精品无码人妻一区| 欧美丝袜第一区| 日韩亚洲视频在线观看| 91精品91久久久久久| 精品国产影院| 欧美精品一区二区三区三州| 粉嫩aⅴ一区二区三区四区| 久久精品黄色片| 日韩三级精品电影久久久| 亚洲羞羞网站| 成人免费看片网址| 精品电影一区| 黄色激情在线观看| 五月天欧美精品| 手机看片福利在线观看| 国产亚洲美女久久| 69堂精品视频在线播放| 日本视频一区在线观看| 奇米色777欧美一区二区| 永久免费av无码网站性色av| 欧美性生活影院| 国产三级在线播放| 鬼打鬼之黄金道士1992林正英| 自拍偷拍欧美| 国产精品麻豆入口| 疯狂欧美牲乱大交777| 国产在线一在线二| 国产在线精品一区免费香蕉 | 美日韩精品免费视频| 欧美影院视频| 无码中文字幕色专区| 久久综合狠狠综合久久综合88| 中文字幕天堂在线| 操日韩av在线电影| 巨人精品**| 草草草在线视频| 国产精品夫妻自拍| 国精品人妻无码一区二区三区喝尿 | 久久99久久精品| 麻豆亚洲av熟女国产一区二| 亚洲国产高潮在线观看| 欧美日韩成人影院| 欧美爱爱视频网站| av一区二区不卡| 在线观看免费视频a| 欧美国产日韩一区二区在线观看| 福利片一区二区| 午夜国产一区二区三区| 亚洲精品国产第一综合99久久| 四虎精品在永久在线观看| 国产精品一区av| 蜜桃一区二区三区| 思思久久精品视频| 污片在线观看一区二区| 99re在线视频| 狠狠色综合网站久久久久久久| 奇米影视一区二区三区| 久草国产在线观看| 国产一区二区三区久久精品| 国产一精品一av一免费爽爽| 日本www在线播放| 亚洲少妇最新在线视频| 欧美偷拍视频| 91亚洲精品丁香在线观看| 久久亚洲欧洲| 国产大片中文字幕|