摘要:?jiǎn)卧獪y(cè)試針對(duì)程序模塊進(jìn)行測(cè)試。是開源的單元測(cè)試工具。一個(gè)好的單元測(cè)試應(yīng)該具備的條件安全重構(gòu)已有代碼單元測(cè)試一個(gè)很重要的價(jià)值是為重構(gòu)保駕護(hù)航。斷言外部依賴單元測(cè)試的一個(gè)重要原則就是無(wú)依賴和隔離。
前端測(cè)試金字塔
對(duì)于一個(gè) Web 應(yīng)用來(lái)說(shuō),理想的測(cè)試組合應(yīng)該包含大量單元測(cè)試(unit tests),部分快照測(cè)試(snapshot tests),以及少量端到端測(cè)試(e2e tests)。參考測(cè)試金字塔,我們構(gòu)建了前端應(yīng)用的測(cè)試金字塔。
單元測(cè)試
針對(duì)程序模塊進(jìn)行測(cè)試。模塊是軟件設(shè)計(jì)中的最小單位,一個(gè)函數(shù)或者一個(gè) React 組件都可以稱之為一個(gè)模塊。單元測(cè)試運(yùn)行快,反饋周期短,在短時(shí)間內(nèi)就能夠知道是否破壞了代碼,因此在測(cè)試組合中占據(jù)了絕大部分。
快照測(cè)試
對(duì)組件的 UI 進(jìn)行測(cè)試。傳統(tǒng)的快照測(cè)試會(huì)拍攝組件的圖片,并且將它和之前的圖片進(jìn)行對(duì)比,如果兩張圖片不匹配則測(cè)試失敗。Jest 的快照測(cè)試不會(huì)拍攝圖片,而是將 React 樹序列化成字符串,通過(guò)比較兩個(gè)字符串來(lái)判斷 UI 是否改變。因?yàn)槭羌兾谋镜膶?duì)比,所以不需要構(gòu)建整個(gè)應(yīng)用,運(yùn)行速度自然比傳統(tǒng)快照測(cè)試更快。
E2E 測(cè)試
相當(dāng)于黑盒測(cè)試。測(cè)試者不需要知道程序內(nèi)部是如何實(shí)現(xiàn)的,只需要根據(jù)業(yè)務(wù)需求,模擬用戶的真實(shí)使用場(chǎng)景進(jìn)行測(cè)試。
測(cè)試種類 | 技術(shù)選型 |
---|---|
單元測(cè)試 | Jest + Enzyme |
快照測(cè)試 | Jest |
E2E 測(cè)試 | jest-puppeteer |
Jest?是 Facebook 開源的測(cè)試框架。它的功能很強(qiáng)大,包含了測(cè)試執(zhí)行器、斷言庫(kù)、spy、mock、snapshot 和測(cè)試覆蓋率報(bào)告等。
Enzyme?是 Airbnb 開源的 React 單元測(cè)試工具。它擴(kuò)展了 React 官方的 TestUtils,通過(guò)類 jQuery 風(fēng)格的 API?對(duì) DOM 進(jìn)行處理,減少了很多重復(fù)代碼,可以很方便的對(duì)渲染出來(lái)的結(jié)果進(jìn)行斷言。
jest-p[uppeteer]()?是一個(gè)同時(shí)包含 Jest 和 Puppeteer 的工具。Puppeteer 是谷歌官方提供的 Headless?Chrome Node API,它提供了基于?DevTools Protocol?的上層 API 接口,用來(lái)控制?Chrome 或者 Chromium。有了?Puppeteer,我們可以很方便的進(jìn)行端到端測(cè)試。
測(cè)試本質(zhì)上是對(duì)代碼的保護(hù),保證項(xiàng)目在迭代的過(guò)程中正常運(yùn)行。當(dāng)然,寫測(cè)試也是有成本的,特別是復(fù)雜邏輯,寫測(cè)試花的時(shí)間,可能不比寫代碼少。所以我們要制定合理的測(cè)試策略,有針對(duì)性的去寫測(cè)試。至于哪些代碼要測(cè),哪些代碼不測(cè),總的來(lái)說(shuō)遵循一個(gè)原則:投入低,收益高?!竿度氲汀故侵笢y(cè)試容易寫,「收益高」是測(cè)試的價(jià)值高。換句話說(shuō),就是指測(cè)試應(yīng)該優(yōu)先保證核心代碼邏輯,比如核心業(yè)務(wù)、基礎(chǔ)模塊、基礎(chǔ)組件等,同時(shí),編寫測(cè)試和維護(hù)測(cè)試的成本也不宜過(guò)高。當(dāng)然,這是理想情況,在實(shí)際的開發(fā)過(guò)程中還是要進(jìn)行權(quán)衡。
單元測(cè)試基于 React 和 Redux 項(xiàng)目的特點(diǎn),我們制定了下面的測(cè)試策略:
分類 | 哪些要測(cè)? | 哪些不測(cè)? |
---|---|---|
組件 |
有條件渲染的組件(如 if-else 分支,聯(lián)動(dòng)組件,權(quán)限控制組件等) 有用戶交互的組件(如 Click、提交表單等) * 邏輯組件(如高階組件和 Children Render 組件) |
connect 生成的容器組件 純組合子組件的 Page 組件 純展示的組件 組件樣式 |
Reducer | 有邏輯的 Reducer。如合并、刪除? state。 | 純?nèi)≈档?reducer 不測(cè)。比如 (_, action) => action.payload.data? |
Middleware | 全測(cè) | 無(wú) |
Action Creator | 無(wú) | 全不測(cè) |
方法 |
validators formatters * 其他公有方法 |
私有方法 |
公用模塊 | 全測(cè)。比如處理 API 請(qǐng)求的模塊。 | 無(wú) |
Note: 如果使用了 TypeScript,類型約束可以替代部分函數(shù)入?yún)⒑头祷刂殿愋偷臋z查。快照測(cè)試
Jest 的 snapshot 測(cè)試雖然運(yùn)行起來(lái)很快,也能夠起到一定保護(hù) UI 的作用。但是它維護(hù)起來(lái)很困難(大量依賴人工對(duì)比),并且有時(shí)候不穩(wěn)定(UI 無(wú)變化但 className 變化仍然會(huì)導(dǎo)致測(cè)試失敗)。因此,個(gè)人不推薦在項(xiàng)目中使用。但是為了應(yīng)付測(cè)試覆蓋率,以及「給自己信心」,也可以給以下部分添加?snapshot 測(cè)試:
Page 組件:一個(gè) page 對(duì)應(yīng)一個(gè) snapshot。
純展示的公用 UI 組件。
快照測(cè)試可以等整個(gè) Page 或者 UI 組件構(gòu)建完成之后再添加,以保證穩(wěn)定。
E2E 測(cè)試覆蓋核心的業(yè)務(wù) flow。
一個(gè)好的單元測(cè)試應(yīng)該具備的條件? 安全重構(gòu)已有代碼單元測(cè)試一個(gè)很重要的價(jià)值是為重構(gòu)保駕護(hù)航。當(dāng)輸入不變時(shí),當(dāng)且僅當(dāng)「被測(cè)業(yè)務(wù)代碼功能被改動(dòng)了」時(shí),測(cè)試才應(yīng)該掛掉。也就是說(shuō),無(wú)論怎么重構(gòu),測(cè)試都不應(yīng)該掛掉。
在寫組件測(cè)試時(shí),我們常常遇到這樣的情況:用 css class 選擇器選中一個(gè)節(jié)點(diǎn),然后對(duì)它進(jìn)行斷言,那么即使業(yè)務(wù)邏輯沒有發(fā)生變化,重命名這個(gè) class 時(shí)也會(huì)使測(cè)試掛掉。理論上來(lái)說(shuō),這樣的測(cè)試并不算一個(gè)「好的測(cè)試」,但是考慮到它的業(yè)務(wù)價(jià)值,我們還是會(huì)寫一些這樣的測(cè)試,只不過(guò)寫測(cè)試的時(shí)候需要注意:使用一些不容易發(fā)生變化的選擇器,比如 component name、arial-label 等。
保存業(yè)務(wù)上下文我們經(jīng)常說(shuō)測(cè)試即文檔,沒錯(cuò),一個(gè)好的測(cè)試往往能夠非常清晰的表單業(yè)務(wù)或代碼的含義。
快速回歸快速回歸是指測(cè)試運(yùn)行速度快,且穩(wěn)定。要想運(yùn)行速度快,很重要的一點(diǎn)是 mock 好外部依賴。至于怎么具體怎么 mock 外部依賴,后面會(huì)詳細(xì)說(shuō)明。
單元測(cè)試怎么寫? 定義測(cè)試名稱建議采用?BDD?的方式,即測(cè)試要接近自然語(yǔ)言,方便團(tuán)隊(duì)中的各個(gè)成員進(jìn)行閱讀。編寫測(cè)試用例的時(shí)候,可以參考 AC,試著將 AC 的 Give-When-Then 轉(zhuǎn)化成測(cè)試用例。
GIVEN: 準(zhǔn)備測(cè)試條件,比如渲染組件。
WHEN:在某個(gè)具體的場(chǎng)景下,比如點(diǎn)擊 button。
THEN:斷言
describe("add user", () => { it("when I tap add user button, expected dialog opened with 3 form fields", () => { // Given: in profile page. // Prepare test env, like render component etc. // When: button click. // Simulate button click // Then: display `add user` form, which contains username, age and phone number. // Assert form fields length to equal 3 }); });Mock 外部依賴
單元測(cè)試的一個(gè)重要原則就是無(wú)依賴和隔離。也就是說(shuō),在測(cè)試某部分代碼時(shí),我們不期望它受到其他代碼的影響。如果受到外部因素影響,測(cè)試就會(huì)變得非常復(fù)雜且不穩(wěn)定。
我們寫單元測(cè)試時(shí),遇到的最大問題就是:代碼過(guò)于復(fù)雜。比如當(dāng)頁(yè)面有 API 請(qǐng)求、日期、定時(shí)器或 redux conent 時(shí),寫測(cè)試就變得異常困難,因?yàn)槲覀冃枰ù罅繒r(shí)間去隔離這些外部依賴。
隔離外部依賴需要用到測(cè)試替代方法,常見的有 spies、stubs 和 mocks。很多測(cè)試框架都實(shí)現(xiàn)了這三種方法,比如著名的 Jest 和 Sinon。這些方法可以幫助我們?cè)跍y(cè)試中替換代碼,減少測(cè)試編寫的復(fù)雜度。
spiesspies 本質(zhì)上是一個(gè)函數(shù),它可以記錄目標(biāo)函數(shù)的調(diào)用信息,如調(diào)用次數(shù)、傳參、返回值等等,但不會(huì)改變?cè)己瘮?shù)的行為。Jest 中的?mock function?就是 spies,比如我們常用的?jest.fn()?。
// Example: onSubmit() { // some other logic here this.props.dispatch("xxx_action"); } // Example Test: it("when form submit, expected dispatch function to be called", () => { const mockDispatch = jest.fn(); mount(); // simlate submit event here expect(mockDispatch).toBeCalledWith("xxx_action"); expect(mockDispatch).toBeCalledTimes(1); });
spies 還可以用于替換屬性方法、靜態(tài)方法和原型鏈方法。由于這種修改會(huì)改變?cè)紝?duì)象,使用之后必須調(diào)用 restore 方法予以還原,因此使用的時(shí)候要特別小心。
// Example: const video = { play() { return true; }, }; // Example Test: test("plays video", () => { const spy = jest.spyOn(video, "play"); const isPlaying = video.play(); expect(spy).toHaveBeenCalled(); expect(isPlaying).toBe(true); spy.mockRestore(); });stubs
stubs 跟 spies 類似,但與 spies 不同的是,stubs 會(huì)替換目標(biāo)函數(shù)。也就是說(shuō),如果使用 spies,原始的函數(shù)依然會(huì)被調(diào)用,但使用 stubs,原始的函數(shù)就不會(huì)被執(zhí)行了。stubs 能夠保證明確的測(cè)試邊界。它可以用于以下場(chǎng)景:
替換讓測(cè)試變得復(fù)雜或慢的外部函數(shù),如 ajax。
測(cè)試異常條件,如拋出異常。
Jest 中也提供了類似的 API [](https://jestjs.io/docs/en/jes...[]()jest.spyOn().mockImplementation(),如下:
const spy = jest.fn(); const payload = [1, 2, 3]; jest .spyOn(jQuery, "ajax") .mockImplementation(({ success }) => success(payload)); jQuery.ajax({ url: "https://example.api", success: data => spy(data) }); expect(spy).toHaveBeenCalledTimes(1); expect(spy).toHaveBeenCalledWith(payload);mocks
mocks 是指用自定義對(duì)象代替目標(biāo)對(duì)象。我們不僅可以 mock API 返回值和自定義類,還可以 mock npm 模塊等等。
// mock middleware api const mockMiddlewareAPI = { dispatch: jest.fn(), getState: jest.fn(), }; // mock npm module `config` jest.mock("config", () => { return { API_BASE_URL: "http://base_url", }; });
使用 mocks 時(shí),需要注意:
如果 mock 了某個(gè)模塊的依賴,需要等 mock 完成了之后再 require 這個(gè)模塊。
有如下代碼:
// counter.ts let count = 0; export const get = () => count; export const inc = () => count++; export const dec = () => count--;
錯(cuò)誤做法:
// counter.test.ts import * as counter from "../counter"; describe("counter", () => { it("get", () => { jest.mock("../counter", () => ({ get: () => "mock count", })); expect(counter.get()).toEqual("mock count"); // 測(cè)試失敗,此時(shí)的 counter 模塊并非 mock 之后的模塊。 }); });
正確做法:
describe("counter", () => { it("get", () => { jest.mock("../counter", () => ({ get: () => "mock count", })); const counter = require("../counter"); // 這里的 counter 是 mock 之后的 counter expect(counter.get()).toEqual("mock count"); // 測(cè)試成功 }); });
多個(gè)測(cè)試有共享狀態(tài)時(shí),每次測(cè)試完成之后需要重置模塊 jest.resetModules()?。它會(huì)清空所有 required 模塊的緩存,保證模塊之間的隔離。
錯(cuò)誤的做法:
describe("counter", () => { it("inc", () => { const counter = require("../counter"); counter.inc(); expect(counter.get()).toEqual(1); }); it("get", () => { const counter = require("../counter"); // 這里的 counter 和上一個(gè)測(cè)試中的 counter 是同一份拷貝 expect(counter.get()).toEqual(0); // 測(cè)試失敗 console.log(counter.get()); // ? 輸出: 1 }); });
正確的做法:
describe("counter", () => { afterEach(() => { jest.resetModules(); // 清空 required modules 的緩存 }); it("inc", () => { const counter = require("../counter"); counter.inc(); expect(counter.get()).toEqual(1); }); it("get", () => { const counter = require("../counter"); // 這里的 counter 和上一個(gè)測(cè)試中的 counter 是不同的拷貝 expect(counter.get()).toEqual(0); // 測(cè)試成功 console.log(counter.get()); // ? 輸出: 0 }); });
修改代碼,從一個(gè)外部模塊 defaultCount 中獲取?count 的默認(rèn)值。
// defaultCount.ts export const defaultCount = 0; // counter.ts import {defaultCount} from "./defaultCount"; let count = defaultCount; export const inc = () => count++; export const dec = () => count--; export const get = () => count;
測(cè)試代碼:
import * as counter from "../counter"; // 首次導(dǎo)入 counter 模塊 console.log(counter); describe("counter", () => { it("inc", () => { jest.mock("../defaultCount", () => ({ defaultCount: 10, })); const counter1 = require("../counter"); // 再次導(dǎo)入 counter 模塊 counter1.inc(); expect(counter1.get()).toEqual(11); // 測(cè)試失敗 console.log(counter1.get()); // 輸出: 1 }); });
再次 require counter 時(shí),發(fā)現(xiàn)模塊已經(jīng)被 require 過(guò)了,就直接從緩存中獲取,所以 counter1 使用的還是counter 的上下文,也就是 defaultCount = 0。而調(diào)用?resetModules() 會(huì)清空 cache,重新調(diào)用模塊函數(shù)。
在上面的代碼中,注釋掉 1,2 行,測(cè)試也會(huì)成功。大家可以想想為什么?編寫測(cè)試 組件測(cè)試
要對(duì)組件進(jìn)行測(cè)試,首先要將組件渲染出來(lái)。Enzyme 提供了三種渲染方式: 淺渲染、全渲染以及靜態(tài)渲染。
shallow 方法會(huì)把組件渲染成 Virtual DOM 對(duì)象,只會(huì)渲染組件中的第一層,不會(huì)渲染它的子組件,因此不需要關(guān)心 DOM 和執(zhí)行環(huán)境,測(cè)試的運(yùn)行速度很快。
淺渲染對(duì)上層組件非常有用。上層組件往往包含很多子組件(比如 App 或 Page 組件),如果將它的子組件全部渲染出來(lái),就意味著上層組件的測(cè)試要依賴于子組件的行為,這樣不僅使測(cè)試變得更加困難,也大大降低了效率,不符合單元測(cè)試的原則。
淺渲染也有天生的缺點(diǎn),因?yàn)樗荒茕秩疽患?jí)節(jié)點(diǎn)。如果要測(cè)試子節(jié)點(diǎn),又不想全渲染怎么辦呢?shallow?還提供了一個(gè)很好用的接口 .dive,通過(guò)它可以獲取 wrapper 子節(jié)點(diǎn)的 React DOM 結(jié)構(gòu)。
示例代碼:
export const Demo = () => ();
使用 shallow?后得到如下結(jié)構(gòu):
使用 .dive()?后得到如下結(jié)構(gòu):
mount 方法會(huì)把組件渲染成真實(shí)的 DOM 節(jié)點(diǎn)。如果你的測(cè)試依賴于真實(shí)的 DOM 節(jié)點(diǎn)或者子組件,那就必須使用 mount 方法。特別是大量使用 Child Render 的組件,很多時(shí)候測(cè)試會(huì)依賴 Child Render 里面的內(nèi)容,因此需要需要用全渲染,將子組件也渲染出來(lái)。
全渲染方式需要瀏覽器環(huán)境,不過(guò) Jest 已經(jīng)提供了,它的默認(rèn)的運(yùn)行環(huán)境 jsdom?,就是一個(gè) JavaScript 瀏覽器環(huán)境。需要注意的是,如果多個(gè)測(cè)試依賴了同一個(gè) DOM,它們可能會(huì)相互影響,因此在每個(gè)測(cè)試結(jié)束之后,最好使用 .unmount()?進(jìn)行清理。
將組件渲染成靜態(tài)的 HTML 字符串,然后使用 Cheerio?對(duì)其進(jìn)行解析,返回一個(gè) Cheerio 實(shí)例對(duì)象,可以用來(lái)分析組件的 HTML 結(jié)構(gòu)。
我們常常會(huì)用到條件渲染,也就是在滿足不同條件時(shí),渲染不同組件。比如:
?
import React, { ReactNode } from "react"; const Container = ({ children }: { children: ReactNode }) =>{children}; const CompA = ({ children }: { children: ReactNode }) =>{children}; const List = () =>List Component; interface IDemoListProps { list: string[]; } export const DemoList = ({ list }: IDemoListProps) => (); {list.length > 0 ? : null}
對(duì)于條件渲染,這里提供了兩種思路:
測(cè)試是否渲染了正確節(jié)點(diǎn)
一般的做法是將?DemoList 組件渲染出來(lái),再根據(jù)不同的條件,去檢查是否渲染出了正確的節(jié)點(diǎn)。
describe("DemoList", () => { it("when list length is more than 0, expected to render List component", () => { const wrapper = shallow(); expect( wrapper .dive() .find("List") .exists(), ).toBe(true); }); it("when list length is more than 0, expected to render null", () => { const wrapper = shallow( ); expect( wrapper .dive() .find("[aria-label="container"]") .children().length, ).toBe(0); }); });
公用組件 + 只測(cè)判斷條件
我們可以抽象一個(gè)公用組件
我們可以為這個(gè)組件添加測(cè)試,確保在不同的條件下顯示正確的節(jié)點(diǎn)。既然這個(gè)邏輯得已經(jīng)得到了保證,使用
export const shouldShowBtn = (a: string, b: string, c: string) => a === b || b === c;
describe("should show button or not", () => { it("should show button", () => { expect(shouldShowBtn("x", "x", "x")).toBe(true); }); it("should hide button", () => { expect(shouldShowBtn("x", "y", "z")).toBe(false); }); });
對(duì)于有權(quán)限控制的組件,一個(gè)小的配置改變也會(huì)導(dǎo)致整個(gè)渲染的不同,而且人工測(cè)試很難發(fā)現(xiàn),這種配置多一個(gè) prop 檢查會(huì)讓代碼更加安全。
常見的有點(diǎn)擊事件、表單提交、validate 等。
點(diǎn)擊事件 click。
onSubmit?。主要是測(cè)試 onSubmit?方法被調(diào)用之后是否發(fā)生了正確的行為,如 dispatch action 。
validate?。 主要是測(cè)試 error message 是否按正確的順序顯示。
Action Creator 測(cè)試action creator 的實(shí)現(xiàn)和測(cè)試都非常簡(jiǎn)單,這里就不舉例了。但要注意的是,不要將計(jì)算邏輯放到 aciton creator 中。
錯(cuò)誤的方式:
// action.ts export const getList = createAction("@@list/getList", (reqParams: any) => { const params = formatReqParams({ ...reqParams, page: reqParams.page + 1, startDate: formatStartDate(reqParams.startDate) endDate: formatStartDate(reqParams.endDate) }); return { url: "/api/list", method: "GET", params, }; });
正確的方式:
// action.ts export const getList = createAction("@@list/getList", (params: any) => { return { url: "/api/list", method: "GET", params, }; }); // 調(diào)用 action creator 時(shí),先把值計(jì)算好,再傳給 action creator。 // utils.ts const formatReqParams = (reqParams: any) => { return formatReqParams({ ...reqParams, page: reqParams.page + 1, startDate: formatStartDate(reqParams.startDate) endDate: formatStartDate(reqParams.endDate) }); }; // page.ts getFeedbackList(formatReqParams({}));Reducer 測(cè)試
Reducer 測(cè)試主要是測(cè)試「根據(jù) Action 和 State 是否生成了正確的 State」。因?yàn)?reducer 是純函數(shù),所以測(cè)試非常好寫,這里就不細(xì)講了。?
Middleware 測(cè)試測(cè)試 middleware 最重要的就是 mock 外部依賴,其中包括 middlewareAPI?和 next?。
Test Helper:
class MiddlewareTestHelper { static of(middleware: any) { return new MiddlewareTestHelper(middleware); } constructor(private middleware: Middleware) {} create() { const middlewareAPI = { dispatch: jest.fn(), getState: jest.fn(), }; const next = jest.fn(); const invoke$ = (action: any) => this.middleware(middlewareAPI)(next)(action); return { middlewareAPI, next, invoke$, }; } }
Example Test:
it("should handle the action", () => { const { next, invoke$ } = MiddlewareTestHelper.of(testMiddleware()).create(); invoke$({ type: "SOME_ACTION", payload: {}, }); expect(next).toBeCalled(); });測(cè)試異步代碼
默認(rèn)情況下,一旦到達(dá)運(yùn)行上下文底部,jest測(cè)試立即結(jié)束。為了解決這個(gè)問題,我們可以使用:
done() 回調(diào)函數(shù)
return promise
async/await
錯(cuò)誤的方式:
test("the data is peanut butter", () => { function callback(data) { expect(data).toBe("peanut butter"); } fetchData(callback); });
正確的方式:
test("the data is peanut butter", done => { function callback(data) { expect(data).toBe("peanut butter"); done(); } fetchData(callback); });
test("the data is peanut butter", () => { expect.assertions(1); return fetchData().then(data => { expect(data).toBe("peanut butter"); }); });
test("the data is peanut butter", async () => { const data = await fetchData(); expect(data).toBe("peanut butter"); });執(zhí)行測(cè)試
采用「紅 - 綠」的方式,即先讓測(cè)試失敗,再修改代碼讓測(cè)試通過(guò),以確保斷言被執(zhí)行。
快照測(cè)試怎么寫?通過(guò) redux-mock-store,將組件需要的全部數(shù)據(jù)準(zhǔn)備好(給 mock store 準(zhǔn)備 state),再進(jìn)行測(cè)試。
從測(cè)試的角度反思應(yīng)用設(shè)計(jì)「好測(cè)試」的前提是要有「好代碼」。因此我們可以從測(cè)試的角度去反思整個(gè)應(yīng)用的設(shè)計(jì),讓組件的「可測(cè)試性」更高。
單一職責(zé)。 一個(gè)組件只干一類事情,降低復(fù)雜度。只要每個(gè)小的部分能夠被正確驗(yàn)證,組合起來(lái)能夠完成整體功能,那么測(cè)試的時(shí)候,只需要專注于各個(gè)小的部分即可。
良好的復(fù)用。 即復(fù)用邏輯的同時(shí),也復(fù)用了測(cè)試。
保證最小可用,再逐漸增加功能。 也就是我們平時(shí)所說(shuō)的 TDD。
...
Debugconsole.log(wrapper.debug());參考文章?
譯-Sinon入門:利用Mocks,Spies和Stubs完成javascript測(cè)試
使用Jest進(jìn)行React單元測(cè)試
對(duì) React 組件進(jìn)行單元測(cè)試
How to Rethink Your Testing
使用Enzyme測(cè)試React(Native)組件
Node.js模塊化機(jī)制原理探究
單元測(cè)試的意義、做法、經(jīng)驗(yàn)
React 單元測(cè)試策略及落地?
文章版權(quán)歸作者所有,未經(jīng)允許請(qǐng)勿轉(zhuǎn)載,若此文章存在違規(guī)行為,您可以聯(lián)系管理員刪除。
轉(zhuǎn)載請(qǐng)注明本文地址:http://systransis.cn/yun/8902.html
摘要:的組件開發(fā)一直處在一個(gè)比較尷尬的處境。目錄包含了當(dāng)前組件的源碼,是組件開發(fā)最主要的目錄。許多的開發(fā)者對(duì)于依然持懷疑態(tài)度。 React Native的組件開發(fā)一直處在一個(gè)比較尷尬的處境。在官方未給予相關(guān)示例與腳手架的情況下,社區(qū)中依然誕生了許許多多的React Native組件。因?yàn)槿鄙偈纠c規(guī)范,很多組件庫(kù)僅含有一個(gè)index.js文件。這種基礎(chǔ)的目錄結(jié)構(gòu)也導(dǎo)致了一些顯而易見的問題,例...
摘要:特意對(duì)前端學(xué)習(xí)資源做一個(gè)匯總,方便自己學(xué)習(xí)查閱參考,和好友們共同進(jìn)步。 特意對(duì)前端學(xué)習(xí)資源做一個(gè)匯總,方便自己學(xué)習(xí)查閱參考,和好友們共同進(jìn)步。 本以為自己收藏的站點(diǎn)多,可以很快搞定,沒想到一入?yún)R總深似海。還有很多不足&遺漏的地方,歡迎補(bǔ)充。有錯(cuò)誤的地方,還請(qǐng)斧正... 托管: welcome to git,歡迎交流,感謝star 有好友反應(yīng)和斧正,會(huì)及時(shí)更新,平時(shí)業(yè)務(wù)工作時(shí)也會(huì)不定期更...
摘要:以下內(nèi)容來(lái)自我特別喜歡的一個(gè)頻道這是一個(gè)年你成為前端,后端或全棧開發(fā)者的進(jìn)階指南你不需要學(xué)習(xí)所有的技術(shù)成為一個(gè)開發(fā)者這個(gè)指南只是通過(guò)簡(jiǎn)單分類列出了技術(shù)選項(xiàng)我將從我的經(jīng)驗(yàn)和參考中給出建議首選我們會(huì)介紹通用的知識(shí)最后介紹年的的一些趨勢(shì)基礎(chǔ)前端開 以下內(nèi)容來(lái)自我特別喜歡的一個(gè)Youtube頻道: Traversy Media 這是一個(gè)2019年你成為前端,后端或全棧開發(fā)者的進(jìn)階指南: 你...
摘要:前端每周清單年度總結(jié)與盤點(diǎn)在過(guò)去的八個(gè)月中,我?guī)缀踔蛔隽藘杉拢ぷ髋c整理前端每周清單。本文末尾我會(huì)附上清單線索來(lái)源與目前共期清單的地址,感謝每一位閱讀鼓勵(lì)過(guò)的朋友,希望你們能夠繼續(xù)支持未來(lái)的每周清單。 showImg(https://segmentfault.com/img/remote/1460000010890043); 前端每周清單年度總結(jié)與盤點(diǎn) 在過(guò)去的八個(gè)月中,我?guī)缀踔蛔隽?..
摘要:感謝王下邀月熊分享的前端每周清單,為方便大家閱讀,特整理一份索引。王下邀月熊大大也于年月日整理了自己的前端每周清單系列,并以年月為單位進(jìn)行分類,具體內(nèi)容看這里前端每周清單年度總結(jié)與盤點(diǎn)。 感謝 王下邀月熊_Chevalier 分享的前端每周清單,為方便大家閱讀,特整理一份索引。 王下邀月熊大大也于 2018 年 3 月 31 日整理了自己的前端每周清單系列,并以年/月為單位進(jìn)行分類,具...
閱讀 3580·2023-04-25 20:09
閱讀 3770·2022-06-28 19:00
閱讀 3115·2022-06-28 19:00
閱讀 3129·2022-06-28 19:00
閱讀 3230·2022-06-28 19:00
閱讀 2917·2022-06-28 19:00
閱讀 3104·2022-06-28 19:00
閱讀 2703·2022-06-28 19:00