單元測試文件必須擁有良好的結構和格式;
測試用例的分組名稱和用例名稱必須清晰易懂;
測試用例必須能描述測試目標的行爲;
優先測試代碼邏輯(過程)而非執行結果;
單元測試的各項覆蓋率指標必須在95%以上;vue
Jest:https://facebook.github.io/jestgit
編寫單元測試所涉及的文件應存放於如下兩個目錄:github
應按照以下結構編寫測試文件,注意其中的空行:json
/* eslint global-require: 0 */ const thirdPartyModule = require('thrid-party-module') describe('@zpfe/module-name' () => { const mocks = {} beforeAll(() => {}) beforeEach(() => {}) test('描述行爲', () => { mocks.fake.mockReturnValue('控制模擬行爲的代碼置於最上方') const target = require('../target.js') const result = target.foo('執行目標待測功能') expcet(result).toBe('斷言置於最下方') }) })
保證每一個describe內部只有mock對象、生命週期鉤子函數和test函數,將模擬對象都添加到mocks對象的適當位置,將初始化操做都添加到適當的生命週期函數中。koa
常量mocks的結構以下:函數
const mocks = { zpfe: { // @zpfe模塊,如有,將包名轉換爲駝峯式以便訪問,好比:koaMiddleware log: { info: jest.fn() } }, dependencies: { thirdPartyModule1: { // 第三方依賴模塊,如有 }, files: { // 本地依賴文件 router: jest.fn() }, others: { // 公共假對象 ctx: jest.fn() } }
請注意,mocks對象的價值在於保存模擬依賴項及部分複用對象,請勿添加不涉及模擬也沒有被複用的內容。單元測試
在beforeAll中設置依賴模擬,好比:測試
beforeAll(() => { jest.mock('@zpfe/log', () => mocks.zpfe.log) jest.mock('../router.js', () => mocks.files.router) jest.spyOn(console, 'log') })
在beforeEach中進行每一個單元測試運行前須要的重置工做,好比:ui
beforeEach(() => { process.env.NODE_ENV = 'production' })
若模塊包含多個文件,則每一個文件對應專門的測試文件,其describe應這樣寫:eslint
describe('@zpfe/module-name: file-name' () => {})
提倡在每一個test函數中require目標文件,若綜合評估以後,能肯定將require目標文件的代碼提取到生命週期鉤子函數中也不會產生干擾或混亂,則能夠考慮提取,好比:
describe('@zpfe/module-name' () => { let moduleName beforeEach(() => { moduleName = require('../target.js') }) })
請用空行分隔test函數內不一樣目的的代碼塊(好比模擬、執行目標、和斷言)。
請勿在測試中編寫try...catch...,應明確斷言是否拋出異常,並根據須要斷言拋出的錯誤信息及日誌記錄狀況。
describe表示分組,其名稱應屬於下列幾種狀況之一:
test表示測試用例,其名稱應當明確表示其行爲,好比:當 disabled 屬性被設置爲非 Boolean 類型時,拋出異常。不容許將describe的命名規則應用到test。
良好的命名有助於組織測試用例,使其更能充當文檔之用。當某個測試用例失敗時,良好的結構和命名能讓讀者快速瞭解其影響範圍,好比:
[FAILED] a-input > props -> disabled -> 當傳入非 Boolean 類型的值時,拋出異常
請查閱Jest文檔,以詳細瞭解Jest所提供的各種模擬API。
若在mocks對象中初始化了實現,又須要在測試用例當中臨時修改其實現,能夠這樣作:
const mocks = { others: { foo: jest.fn(() => 'foo') } } test('demo', () => { mocks.others.foo.mockImplementationOnce(() => 'bar') })
mockImementOnce會臨時修改默認實現,且只生效一次,故不會影響其餘測試用例。
若須要在測試用例當中臨時修改模擬函數的實現,且模擬函數會被屢次調用,就應該使用另一種方式實現,好比:
const mocks = { others: { foo: jest.fn() } } beforeEach(() => { mocks.others.foo.mockImplementation(() => 'foo') }) test('demo', () => { mocks.others.foo.mockImplementation(() => 'bar') })
即在mocks對象中只定義模擬函數,不定義具體實現,在beforeEach鉤子函數中定義具體實現,使得每一個測試用例都會從新初始化該實現,接着在具體測試用力中使用mockImementation完全替換掉默認實現。
請查閱Jest文檔,以詳細瞭解Jest所提供的各種斷言API。
若須要斷言調用函數時的參數傳遞,可以使用:
expect(mocks.zpfe.log.info).toHaveBeenCalledWith('觀察C ZooKeeper客戶端')
若須要部分匹配參數,可以使用:
expect(mocks.zpfe.log.info).toHaveBeenCalledWith(expect.stringContaining('觀察'), expect.objectContaining({ key: 'value' }))
在VS Code中,打開測試文件,選中調試配置【調試 Jest 測試】,按【F5】便可。
@vue/test-utils:https://vue-test-utils.vuejs.org
單元測試文件在__tests__目錄內的組織形式應與目標文件在src目錄保持一致,並按照以下順序結構組織組件的單元測試文件:
describe('組件:a-component-name', () => { const mocks = {} beforeAll() // 僅針對 props 定義進行基礎測試,不測試 props 如何使用 describe('props', () => { describe('prop-name', () => { test('類型應爲 xxx') test('默認值應爲 xxx') test('有效性校驗') }) }) // 僅針對可被用戶使用的嵌套組件族進行嵌套校驗測試 describe('受限嵌套', () => { test('當父組件不爲 xxx 時,拋出異常') test('當子組件不爲 xxx 時,拋出異常') }) // 僅針對 slots 渲染位置進行基礎測試 describe('slots', () => { test('default') test('named-slot') }) // 根據實際狀況,結合 props 和 slots 進行各類場景下的渲染測試 describe('render', () => { test('使用 prop-name 來渲染 xxx') }) // 測試全部公開方法,不測試私有方法 describe('methods', () => { describe('method-name', () => { test('行爲') }) }) // 觸發並測試全部事件是否正常觸發 // 若 props 中包含 value,則 events 中必須包含 input describe('events', () => { describe('event-name', () => { test('當 xxx 時,觸發此事件') }) }) // 測試UI交互是否能正常響應(忽略與 events 測試雷同,則可忽略) describe('交互', () => { test('當點擊 xxx 時, 如此這般') }) }
按照以下規則掛載組件:
在掛載時傳遞 props;
掛載產生的對象應命名爲 e;
若組件須要使用原生DOM方法,請啓用 attachToDocument;
好比:
const target = mount(ComponentName, { propsData: { foo: 'bar' }, attachToDocument: true })
除非互相依賴的組件之間定義了嵌套校驗,不然優先考慮模擬子組件來進行父組件的測試。好比:
const target = mount(APaginationWithJumper, { stubs: { 'dependent-component': true } } // 經過 target.find('dependent-component-stub').vm來模擬或控制其行爲