年輕時,我不寫單元測試

2014_發表於2018-12-15

在一個多人協作的大型專案中,我們在開發的過程中可能經常會面臨到這樣的問題:

  • 哎,這次我沒有改動到這裡啊,這怎麼會有bug呢
  • 哎,怎麼新加了個功能原來的功能受影響了呢
  • 哎,這裡的樣式為什麼亂掉了

當我們被提出這些bug的時候,我們是二臉懵逼的,因為這不符合一個程式設計師的預期!!!
那麼我們如何能夠避免以上的問題,從而將經歷投入到更多的開發(寫bug)中去呢?
筆者在這裡試著歸納了一下解決問題的辦法

樣式問題需要制定相應的規範

  • 不能使用css,只能用less來書寫(大哥,都2888年了還不用less或scss或stylus嗎)
  • 使用less的類模組化寫法
  • 命名風格採用BEM

(推薦)

.app{ 
width: 100%;
.center{
height: 100%
}
}複製程式碼

(不推薦)

.app{ 
width: 100%;

}.center{
height: 100%
}複製程式碼

原有功能的可用性呢?

其實之前就已經簡單的瞭解過了單元測試,但當時對於單元測試我是持有一種很否定的態度的,因為他太過於雞肋,都是測試一些很基礎的功能,但是當筆者被這次重構折磨之後,有重新思考了下如何能夠保證程式碼的健壯性,抱著這個態度,筆者又去調研了下單元測試到底能夠做什麼。

再說它能夠做什麼之前,我們先來說說它是什麼?

從字面解析來看,那就是把你的程式碼,拆分成一個一個的單元,然後針對不同的單元,編寫不同的測試用例。

那這時候我們就會有一個問題了,那如果單元測試通過了,那到底能不能就不需要測試同學再測試就直接上線了呢?

按照我們的理想情況,如果我們的測試用例覆蓋率達到了5個9以上,那應該是可以直接釋出了,但是這個時候其實我們的內心還是會有一些疑慮,就是,那一個一個的模組都已經通過了,那整合在一起會不會有問題呢?這其實也是筆者到現在還不確定的問題。(獲取整合測試能夠解決?如果你已經有了答案,歡迎下方指正)

基於此,筆者希望在前端編寫測試用例能夠實現以下的目標:

  • 先保證一個一個的模組基礎功能正常
  • 增加新功能時,原有功能不受影響

本著實現以上的要求,筆者下來介紹下具體的使用,關於不同測試框架的重點,這篇文章就不詳細展開了,最終結合我們的專案,最終採用了facebook的jest+enzyme。重點將展開以下兩種react元件型別測試。

展示型元件測試

展示型元件測試,意思就是要確保每一次的修改都是符合預期的,這裡筆者要著重介紹下jest框架裡面的snapshot功能。

shapshot就是會對元件進行一次快照記錄當前的狀態,每一次run jest的時候,對比上一次,看看是否有變化。那最完美的情況就是,我們將所有的css樣式打包,然後渲染出元件ui,對比上一次的紀錄,看看是否有修改,但是很可惜,目前shapshot生成的快照檔案裡面只有class,並沒有相關樣式,除非你把所有樣式寫成style內聯,那麼他就能記錄下你的style樣式。看似好像沒有達到我們的目標。

但是仔細想想,這其實就違背了我們單元測試的初衷,筆者這裡也大膽猜測下,jest官方在實現這個功能的時候,應該也只是想記錄下一步一步的事件後,當前元件的html結構,對比上一次的快照,來看功能是否符合預期。程式碼的話比較簡單:

describe('XJLayerCard', () =>
{
it('renders correctly', () =>
{
const wrapper = Enzyme.render(<
XJLayerCard {...mockData
}/>
);
expect(toJson(wrapper)).toMatchSnapshot();

})
})複製程式碼

功能型元件測試

功能性元件測試,就是要覆蓋到一個元件的基礎功能,能夠確保每一個修改之後,跑完單元測試,能夠確定之前的功能正常。

一開始我覺得單元測試很雞肋的原因也是沒有深入瞭解它,這次發現就算是和業務結合很緊密的元件,也能夠模擬正常的操作,這裡就貼一個和redux結合的元件來舉例

import React from 'react';
import Enzyme from 'enzyme';
import AppInput from '../AppInput';
import toJson from 'enzyme-to-json'import initialStore from '@/../__mocks__/store.js';
import appInfoData from '@/../__mocks__/appInfo.js'import {
Provider
} from 'react-redux';
import configureStore from '@/entries/maker/redux/store';
import {
updateAppInfo
} from '@/entries/maker/redux/action';
import moxios from 'moxios';
import instance from '@/api/instance'import Adapter from 'enzyme-adapter-react-16';
Enzyme.configure({
adapter: new Adapter()
});
let store;
let wrapper;
beforeEach(() =>
{
moxios.install(instance);
store = configureStore(initialStore) wrapper = Enzyme.mount( <
Provider store={store
}>
<
AppInput onChange={(obj) =>
{
store.dispatch(updateAppInfo(obj))
}
}/>
<
/Provider>
)
})afterEach(function () {
moxios.uninstall(instance);

})describe('demo', () =>
{
it('renders correctly', () =>
{
expect(toJson(wrapper)).toMatchSnapshot();

});
it('click div, select show', () =>
{
// 渲染選項框正常 expect(wrapper.find('.xj-appinput-item').length).toEqual(2);
// 一開始沒有輸入框 expect(wrapper.find('input').length).toEqual(0);
wrapper.find('.xj-appinput-value-wrapper').at(0).simulate('click');
expect(wrapper.find('input').length).toEqual(1);

});
it('updateAppInfo action', (done) =>
{
// 攔截請求 moxios.stubRequest('/app/get', {
status: 200, responseText: 'success'
});
// 點選下拉框中的第一個,會觸發action wrapper.find('.xj-appinput-item').at(0).simulate('click');
moxios.wait(() =>
{
// mock資料 let request = moxios.requests.mostRecent();
request.respondWith({
status: 200, response: appInfoData
}).then((res) =>
{
// input輸入框消失 expect(wrapper.find('input').length).toEqual(0);
// 選中的app展示出來 expect(wrapper.find('.xj-appinput-wrapper').length).toEqual(1);
expect(toJson(wrapper)).toMatchSnapshot();
done();

}).catch(err =>
{
console.log(err)
})
});

})
})複製程式碼

可以看到,這裡的測試內容是結合了redux,axios庫。那其實整個流程就是初始化這個元件,看看渲染的html結構是否符合預期,然後點選下拉框,選中其中第一個,發起請求,拉回詳細資料,再觀察元件是否展示正常,編寫完測試用例後,就已經用程式碼模擬了整個手工操作,怎麼樣,是不是很強大?

覺得有點幫助的,去點個star吧

來源:https://juejin.im/post/5c14ea256fb9a049fc036c00

相關文章