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

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

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

好的測(cè)試來(lái)自于好的代碼讲婚,如果說(shuō)有藝術(shù)尿孔,那也是代碼的藝術(shù)。

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

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

最好的單元是返回簡(jiǎn)單數(shù)據(jù)結(jié)構(gòu)的函數(shù):函數(shù)是最基本的抽象泽示,可大可小缸血,不需要mock,只依靠傳參械筛。簡(jiǎn)單數(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)容哄孤,所以不存在門(mén)檻。

為什么不談TDD吹截?

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

為什么談測(cè)試苍匆?

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

第一難學(xué)棚菊,第二難寫(xiě)浸踩。寫(xiě)測(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ī),與其說(shuō)服別人己莺,不如從簡(jiǎn)化測(cè)試開(kāi)始奏甫。

(圖片來(lái)自:http://t.cn/Rpw9WKg

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

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

前端不說(shuō)套路不固定,測(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,五花八門(mén)离咐。 但得益于JavaScript本身谱俭,前端測(cè)試其實(shí)是可以非常高效的。

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

為什么談React全家桶接谨?

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

測(cè)試工具

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

(圖片來(lái)自:http://t.cn/RpwCke3

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

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

yarn add ava
ava --watch

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

import test from 'ava'

test(t => {
  t.is(1 + 1, 2)
})

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

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

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

const createStore = reducer => {
  let state, listeners = []

  const dispatch = action => {
    state = reducer(state, action)
    listeners.forEach(listeners => listeners())
  }

  return {
    getState() { return state },
    subscribe(listener) {
      listeners.push(listener)
      return () => { listeners = listeners.filter(l => l !== listener)}
    },
    dispatch,
  }
}

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

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

export const parkingLot = (state = [], action) => {
  switch (action.type) {
    case 'parkingLot/PARK':
      return [action.car, ...state]
    case 'parkingLot/PICK':
      const [_, ...rest] = state
      return rest
    default: return state
  }
}

Reducer是這么用的:

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

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

test('parking lot', t => {
  const initial = parkingLot(undefined, {})
  t.deepEqual(initial, [], 'should be empty when init')

  const parked = parkingLot(initial, { type: 'parkingLot/PARK', car: 'Tesla Model S' })
  t.deepEqual(parked, ['Tesla Model S'], 'should park Model S in lot')

  const picked = parkingLot(parked, { type: 'parkingLot/PICK' })
  t.deepEqual(picked, [], 'should remove the car')
})

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

(圖片來(lái)自:http://t.cn/RpwS3AK

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

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

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

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

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

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

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

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

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

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

最簡(jiǎn)單的測(cè)試還是判等饱搏,我們用一個(gè)叫jsx-test-helpers的庫(kù)來(lái)幫我們渲染:

import { renderJSX, JSX } from 'jsx-test-helpers'

const Paragraph = ({ children }) => <p>{children}</p>
const Greeter = ({ name }) => <Paragraph>Greetings {name}!</Paragraph>

test('Greeter', t => {
  t.is(renderJSX(<Greeter name="React"/>), 
       JSX(<Paragraph>Greetings React!</Paragraph>), 
       'should render greeting text with name')
})

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

(圖片來(lái)自: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筝尾,如果分開(kāi)測(cè)就有M+N種捡需。View和Model的邊界清晰時(shí),你的Model測(cè)試不容易被更困難的View測(cè)試干擾筹淫,View測(cè)試也減少了混沌程度站辉,需要測(cè)試的情形就減少了。

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

const TextField = ({ label, onChange }) => <label>
  {label}
  <input type="text" onChange={onChange} />
</label>

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

test('TextField', t => {
  const onChange = () => {}
  const actual = renderJSX(<TextField label="Email" onChange={onChange} />)
  const expected = JSX(<label>
    Email
    <input type="text" onChange={onChange}/>
  </label>)
  t.is(actual, expected)
})

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

import {shallow} from 'enzyme'
import sinon from 'sinon'

test('TextField with enzyme', t => {
  const onChange = sinon.spy()
  const wrapper = shallow(<TextField label="Email" onChange={onChange} />)
  t.true(wrapper.contains(<label>Email</label>), 'should render label')

  const event = { target: { value: 'foo@bar.com' } }
  wrapper.find('input').simulate('change', event)
  t.true(onChange.calledWith(event))
})

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

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

import render from 'react-test-renderer'

test('Greeter', t => {
  const tree = render.create(<Greeter name="React"/>).toJSON()
  t.snapshot(tree, 'should not change')
})

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

ava --update-snapshots

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

小結(jié)

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

異步Effect測(cè)試

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

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

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

import { takeEvery, put, call, fork, cancel } from 'redux-saga/effects'

function *account() {
  yield call(takeEvery, 'login/REQUESTED', login)
}
function *login({ name, password }) {
  try {
    const { token } = yield call(fetch, '/login', { method: 'POST', body: { name, password } })
    yield put({ type: 'login/SUCCEEDED', token })
  }
  catch (error) {
    yield put ({ type: 'login/FAILED', error })
  }
}

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

這是個(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è)方式你可以輕松解決瘋狂難度的異步問(wèn)題一也。

test('account saga', t => {
  const gen = account()
  t.deepEqual(gen.next().value, call(takeEvery, 'login/REQUESTED', login))
})

test('login saga', t => {
  const gen = login({ name: 'John', password: 'super-secret-123'})

  const request = gen.next().value
  t.deepEqual(request, call(fetch, '/login', { method: 'POST', body: { name: 'John', password: 'super-secret-123'} }))
  const response = gen.next({ token: 'non-human-readable-token' }).value
  t.deepEqual(response, put({ type: 'login/SUCCEEDED', token: 'non-human-readable-token' }))
  const failure = gen.throw('You code just exploded!').value
  t.deepEqual(failure, put({ type: 'login/FAILED', error: 'You code just exploded!'}))
})

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

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

結(jié)語(yǔ)

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

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

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

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

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

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

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

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

最后編輯于
?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請(qǐng)聯(lián)系作者
  • 序言:七十年代末,一起剝皮案震驚了整個(gè)濱河市注竿,隨后出現(xiàn)的幾起案子茄茁,更是在濱河造成了極大的恐慌魂贬,老刑警劉巖,帶你破解...
    沈念sama閱讀 207,113評(píng)論 6 481
  • 序言:濱河連續(xù)發(fā)生了三起死亡事件裙顽,死亡現(xiàn)場(chǎng)離奇詭異付燥,居然都是意外死亡,警方通過(guò)查閱死者的電腦和手機(jī)锦庸,發(fā)現(xiàn)死者居然都...
    沈念sama閱讀 88,644評(píng)論 2 381
  • 文/潘曉璐 我一進(jìn)店門(mén)机蔗,熙熙樓的掌柜王于貴愁眉苦臉地迎上來(lái),“玉大人甘萧,你說(shuō)我怎么就攤上這事萝嘁。” “怎么了扬卷?”我有些...
    開(kāi)封第一講書(shū)人閱讀 153,340評(píng)論 0 344
  • 文/不壞的土叔 我叫張陵牙言,是天一觀(guān)的道長(zhǎng)。 經(jīng)常有香客問(wèn)我怪得,道長(zhǎng)咱枉,這世上最難降的妖魔是什么? 我笑而不...
    開(kāi)封第一講書(shū)人閱讀 55,449評(píng)論 1 279
  • 正文 為了忘掉前任徒恋,我火速辦了婚禮蚕断,結(jié)果婚禮上,老公的妹妹穿的比我還像新娘入挣。我一直安慰自己亿乳,他們只是感情好,可當(dāng)我...
    茶點(diǎn)故事閱讀 64,445評(píng)論 5 374
  • 文/花漫 我一把揭開(kāi)白布径筏。 她就那樣靜靜地躺著葛假,像睡著了一般。 火紅的嫁衣襯著肌膚如雪滋恬。 梳的紋絲不亂的頭發(fā)上聊训,一...
    開(kāi)封第一講書(shū)人閱讀 49,166評(píng)論 1 284
  • 那天,我揣著相機(jī)與錄音恢氯,去河邊找鬼带斑。 笑死,一個(gè)胖子當(dāng)著我的面吹牛勋拟,可吹牛的內(nèi)容都是我干的遏暴。 我是一名探鬼主播,決...
    沈念sama閱讀 38,442評(píng)論 3 401
  • 文/蒼蘭香墨 我猛地睜開(kāi)眼指黎,長(zhǎng)吁一口氣:“原來(lái)是場(chǎng)噩夢(mèng)啊……” “哼朋凉!你這毒婦竟也來(lái)了?” 一聲冷哼從身側(cè)響起醋安,我...
    開(kāi)封第一講書(shū)人閱讀 37,105評(píng)論 0 261
  • 序言:老撾萬(wàn)榮一對(duì)情侶失蹤杂彭,失蹤者是張志新(化名)和其女友劉穎墓毒,沒(méi)想到半個(gè)月后,有當(dāng)?shù)厝嗽跇?shù)林里發(fā)現(xiàn)了一具尸體亲怠,經(jīng)...
    沈念sama閱讀 43,601評(píng)論 1 300
  • 正文 獨(dú)居荒郊野嶺守林人離奇死亡所计,尸身上長(zhǎng)有42處帶血的膿包…… 初始之章·張勛 以下內(nèi)容為張勛視角 年9月15日...
    茶點(diǎn)故事閱讀 36,066評(píng)論 2 325
  • 正文 我和宋清朗相戀三年,在試婚紗的時(shí)候發(fā)現(xiàn)自己被綠了团秽。 大學(xué)時(shí)的朋友給我發(fā)了我未婚夫和他白月光在一起吃飯的照片主胧。...
    茶點(diǎn)故事閱讀 38,161評(píng)論 1 334
  • 序言:一個(gè)原本活蹦亂跳的男人離奇死亡,死狀恐怖习勤,靈堂內(nèi)的尸體忽然破棺而出踪栋,到底是詐尸還是另有隱情,我是刑警寧澤图毕,帶...
    沈念sama閱讀 33,792評(píng)論 4 323
  • 正文 年R本政府宣布夷都,位于F島的核電站,受9級(jí)特大地震影響予颤,放射性物質(zhì)發(fā)生泄漏囤官。R本人自食惡果不足惜,卻給世界環(huán)境...
    茶點(diǎn)故事閱讀 39,351評(píng)論 3 307
  • 文/蒙蒙 一蛤虐、第九天 我趴在偏房一處隱蔽的房頂上張望党饮。 院中可真熱鬧,春花似錦驳庭、人聲如沸刑顺。這莊子的主人今日做“春日...
    開(kāi)封第一講書(shū)人閱讀 30,352評(píng)論 0 19
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽(yáng)。三九已至荞驴,卻和暖如春不皆,著一層夾襖步出監(jiān)牢的瞬間,已是汗流浹背熊楼。 一陣腳步聲響...
    開(kāi)封第一講書(shū)人閱讀 31,584評(píng)論 1 261
  • 我被黑心中介騙來(lái)泰國(guó)打工霹娄, 沒(méi)想到剛下飛機(jī)就差點(diǎn)兒被人妖公主榨干…… 1. 我叫王不留,地道東北人鲫骗。 一個(gè)月前我還...
    沈念sama閱讀 45,618評(píng)論 2 355
  • 正文 我出身青樓犬耻,卻偏偏與公主長(zhǎng)得像,于是被迫代替她去往敵國(guó)和親执泰。 傳聞我的和親對(duì)象是個(gè)殘疾皇子枕磁,可洞房花燭夜當(dāng)晚...
    茶點(diǎn)故事閱讀 42,916評(píng)論 2 344

推薦閱讀更多精彩內(nèi)容