# 先说一下示例怎么运行,先确定本机安装好 node 环境
# 安装项目依赖
npm install
# 首先启动 webpack-dev-server
npm run start-dev
# 上一个运行完毕后不要关闭,开一个新的命令行,启动 node server 服务
npm run start-server
#上述两个启动好后打开浏览器访问 http://localhost:3000 即可
# 跑测试用例
npm test
# 生成测试覆盖报告,跑完后看 coverage 子目录下的内容
npm run test-coverage
# 以上脚本定义都在 package.json 中Da Webanwendungen immer komplexer werden, achten viele Unternehmen mehr und mehr Aufmerksamkeit auf Front-End-Unit-Tests. Die meisten Tutorials, die wir sehen, werden über die Wichtigkeit von Unit -Tests sprechen und wie einige repräsentative Testframework -APIs verwendet werden. Welchen spezifischen Inhalt sollten Testfälle enthalten?
Dieser Artikel beginnt mit einem realen Anwendungsszenario, analysiert, welche Unit -Tests aus dem Entwurfsmuster und der Codestruktur enthalten sind und wie bestimmte Testfälle geschrieben werden. Ich hoffe, Sie können etwas aus den Kinderschuhen gewinnen, die Sie sehen.
Das Projekt verwendet react Technology Stack, und die verwendeten Hauptgerechtigkeiten umfassen: react , redux , react-redux , redux-actions , reselect , redux-saga , seamless-immutable , antd .

Aus der UI -Ebene besteht dieses Anwendungsszenario hauptsächlich aus zwei Teilen:
Wenn Sie hier einige Kinderschuhe sehen, können Sie sagen: Schneiden! Ist eine so einfache Oberfläche und Geschäftslogik immer noch ein echtes Szenario? Müssen Sie noch Shenma -Unit -Tests schreiben?
Machen Sie sich keine Sorgen, um sicherzustellen, dass die Leserfahrung und Länge des Artikels sichergestellt werden, eine einfache Szene, die das Problem eindeutig erklären kann, ist eine gute Szene, oder? Schauen Sie langsam nach unten.
In diesem Szenariodesign und -entwicklung halten wir uns streng an die besten Praktiken des redux -Einweg-Datenflusss und react-redux ein und verwenden redux-saga , um den Geschäftsfluss zu verarbeiten, den staatlichen Cache zu reselect und Backend-Schnittstellen über fetch zu rufen, was sich nicht von Realprojekten unterscheidet.
Die geschichtete Design- und Codeorganisation lauten wie folgt:

Der Inhalt im mittleren store hängt alle mit redux zusammen, und Sie sollten die Bedeutung kennen, indem Sie sich den Namen ansehen.
Für bestimmte Codes finden Sie hier.
Lassen Sie uns zunächst darüber sprechen, welche Test -Frameworks und Tools verwendet werden. Der Hauptinhalt umfasst:
jest , Testframeworkenzyme , spezialisiert auf das Testen der React UI -Schichtsinon hat eine unabhängige Bibliothek mit Fälschungen, Spione, Stubs und Mocksnock , simulieren Sie den HTTP -ServerWenn Sie Kinderschuhe haben, die mit der obigen Verwendung und Konfiguration nicht vertraut sind, lesen Sie einfach die offizielle Dokumentation, die besser ist als jedes Tutorial.
Als nächstes schreiben wir einen bestimmten Testfallcode. Im Folgenden werden Code -Snippets und Parse für jede Ebene angegeben. Dann beginnen wir mit actions .
Um den Artikel so kurz und lösch wie möglich zu gestalten, sind die folgenden Code -Snippets nicht der vollständige Inhalt jeder Datei, und der vollständige Inhalt ist hier.
Im Geschäft habe ich redux-actions verwendet, um action zu generieren. Hier verwende ich die Symbolleiste als Beispiel. Schauen wir uns zunächst ein Geschäftscode an:
import { createAction } from 'redux-actions' ;
import * as type from '../types/bizToolbar' ;
export const updateKeywords = createAction ( type . BIZ_TOOLBAR_KEYWORDS_UPDATE ) ;
// ... Für actions Tests überprüfen wir hauptsächlich, ob das generierte action korrekt ist:
import * as type from '@/store/types/bizToolbar' ;
import * as actions from '@/store/actions/bizToolbar' ;
/* 测试 bizToolbar 相关 actions */
describe ( 'bizToolbar actions' , ( ) => {
/* 测试更新搜索关键字 */
test ( 'should create an action for update keywords' , ( ) => {
// 构建目标 action
const keywords = 'some keywords' ;
const expectedAction = {
type : type . BIZ_TOOLBAR_KEYWORDS_UPDATE ,
payload : keywords
} ;
// 断言 redux-actions 产生的 action 是否正确
expect ( actions . updateKeywords ( keywords ) ) . toEqual ( expectedAction ) ;
} ) ;
// ...
} ) ;Die Logik dieses Testfalles ist sehr einfach. Erstellen Sie zunächst ein Ergebnis, das wir erwarten, dann die Geschäftsordnung anrufen und schließlich überprüfen, ob das Betriebsergebnis der Geschäftsordnung mit den Erwartungen übereinstimmt. Dies ist die grundlegende Routine von Schreiben von Testfällen.
Wir versuchen, die Anwendungsfall -Einzelverantwortung beim Schreiben von Testfällen zu behalten und nicht zu viele verschiedene Geschäftsbereiche abzudecken. Es kann viele Testfälle geben, aber jeder sollte nicht kompliziert sein.
Als nächstes kommt es zu reducers , die noch handleActions von redux-actions zum Schreiben reducer verwendet. Hier ist eine Tabelle zum Beispiel:
import { handleActions } from 'redux-actions' ;
import Immutable from 'seamless-immutable' ;
import * as type from '../types/bizTable' ;
/* 默认状态 */
export const defaultState = Immutable ( {
loading : false ,
pagination : {
current : 1 ,
pageSize : 15 ,
total : 0
} ,
data : [ ]
} ) ;
export default handleActions (
{
// ...
/* 处理获得数据成功 */
[ type . BIZ_TABLE_GET_RES_SUCCESS ] : ( state , { payload } ) => {
return state . merge (
{
loading : false ,
pagination : { total : payload . total } ,
data : payload . items
} ,
{ deep : true }
) ;
} ,
// ...
} ,
defaultState
) ;Das Statusobjekt hier verwendet
seamless-immutable
Für reducer testen wir hauptsächlich zwei Aspekte:
action.type , ob der aktuelle Status zurückgegeben werden kann.Hier sind die Testcodes für die oben genannten zwei Punkte:
import * as type from '@/store/types/bizTable' ;
import reducer , { defaultState } from '@/store/reducers/bizTable' ;
/* 测试 bizTable reducer */
describe ( 'bizTable reducer' , ( ) => {
/* 测试未指定 state 参数情况下返回当前缺省 state */
test ( 'should return the default state' , ( ) => {
expect ( reducer ( undefined , { type : 'UNKNOWN' } ) ) . toEqual ( defaultState ) ;
} ) ;
// ...
/* 测试处理正常数据结果 */
test ( 'should handle successful data response' , ( ) => {
/* 模拟返回数据结果 */
const payload = {
items : [
{ id : 1 , code : '1' } ,
{ id : 2 , code : '2' }
] ,
total : 2
} ;
/* 期望返回的状态 */
const expectedState = defaultState
. setIn ( [ 'pagination' , 'total' ] , payload . total )
. set ( 'data' , payload . items )
. set ( 'loading' , false ) ;
expect (
reducer ( defaultState , {
type : type . BIZ_TABLE_GET_RES_SUCCESS ,
payload
} )
) . toEqual ( expectedState ) ;
} ) ;
// ...
} ) ;Die Logik des Testfalles hier ist ebenfalls sehr einfach und es ist immer noch die Routine, das erwartete Ergebnis oben zu behaupten. Unten finden Sie den Teil der Selektoren.
Die Funktion des selector besteht darin, den Status des entsprechenden Unternehmens zu erhalten. Hier wird reselect verwendet, um eine Neuberechnung zu verhindern, wenn state nicht geändert wird. Schauen wir uns zunächst den Selektorcode der Tabelle an:
import { createSelector } from 'reselect' ;
import * as defaultSettings from '@/utils/defaultSettingsUtil' ;
// ...
const getBizTableState = ( state ) => state . bizTable ;
export const getBizTable = createSelector ( getBizTableState , ( bizTable ) => {
return bizTable . merge ( {
pagination : defaultSettings . pagination
} , { deep : true } ) ;
} ) ;Die Parameter des Pager sind hier einheitlich im Projekt festgelegt. Daher macht es diesen Job gut: Wenn der Geschäftsstatus unverändert bleibt, kehrt er direkt zum letzten Cache zurück. Die Standardeinstellungen des Paging -Geräts sind wie folgt:
export const pagination = {
size : 'small' ,
showTotal : ( total , range ) => ` ${ range [ 0 ] } - ${ range [ 1 ] } / ${ total } ` ,
pageSizeOptions : [ '15' , '25' , '40' , '60' ] ,
showSizeChanger : true ,
showQuickJumper : true
} ;Dann sind unsere Tests hauptsächlich zwei Aspekte:
Der Testcode lautet wie folgt:
import Immutable from 'seamless-immutable' ;
import { getBizTable } from '@/store/selectors' ;
import * as defaultSettingsUtil from '@/utils/defaultSettingsUtil' ;
/* 测试 bizTable selector */
describe ( 'bizTable selector' , ( ) => {
let state ;
beforeEach ( ( ) => {
state = createState ( ) ;
/* 每个用例执行前重置缓存计算次数 */
getBizTable . resetRecomputations ( ) ;
} ) ;
function createState ( ) {
return Immutable ( {
bizTable : {
loading : false ,
pagination : {
current : 1 ,
pageSize : 15 ,
total : 0
} ,
data : [ ]
}
} ) ;
}
/* 测试返回正确的 bizTable state */
test ( 'should return bizTable state' , ( ) => {
/* 业务状态 ok 的 */
expect ( getBizTable ( state ) ) . toMatchObject ( state . bizTable ) ;
/* 分页默认参数设置 ok 的 */
expect ( getBizTable ( state ) ) . toMatchObject ( {
pagination : defaultSettingsUtil . pagination
} ) ;
} ) ;
/* 测试 selector 缓存是否有效 */
test ( 'check memoization' , ( ) => {
getBizTable ( state ) ;
/* 第一次计算,缓存计算次数为 1 */
expect ( getBizTable . recomputations ( ) ) . toBe ( 1 ) ;
getBizTable ( state ) ;
/* 业务状态不变的情况下,缓存计算次数应该还是 1 */
expect ( getBizTable . recomputations ( ) ) . toBe ( 1 ) ;
const newState = state . setIn ( [ 'bizTable' , 'loading' ] , true ) ;
getBizTable ( newState ) ;
/* 业务状态改变了,缓存计算次数应该是 2 了 */
expect ( getBizTable . recomputations ( ) ) . toBe ( 2 ) ;
} ) ;
} ) ;Ist der Testfall immer noch sehr einfach? Behalte einfach dieses Tempo. Sprechen wir über den etwas komplizierten Teil, den SAGAS -Teil.
Hier verwende ich redux-saga , um den geschäftlichen Fluss zu verarbeiten, der speziell API bezeichnet, um Daten asynchron, erfolgreiche Ergebnisse und Fehlerergebnisse usw. zu verarbeiten.
Einige Kinderschuhe denken vielleicht so kompliziert. Ist es nicht vorbei, asynchron redux-thunk zu verwenden? Mach dir keine Sorgen, du wirst es verstehen, nachdem du es geduldig gelesen hast.
Hier ist es notwendig, kurz die Arbeitsmethode von redux-saga einzuführen. SAGA ist eine es6 -Generatorfunktion - Generator, mit der wir verschiedene deklarative effects erzeugen, die von redux-saga -Engine für das Geschäft verdaut und verarbeitet werden.
Hier werfen wir einen Blick auf die Geschäftsordnung, um Tabellendaten zu erhalten:
import { all , takeLatest , put , select , call } from 'redux-saga/effects' ;
import * as type from '../types/bizTable' ;
import * as actions from '../actions/bizTable' ;
import { getBizToolbar , getBizTable } from '../selectors' ;
import * as api from '@/services/bizApi' ;
// ...
export function * onGetBizTableData ( ) {
/* 先获取 api 调用需要的参数:关键字、分页信息等 */
const { keywords } = yield select ( getBizToolbar ) ;
const { pagination } = yield select ( getBizTable ) ;
const payload = {
keywords ,
paging : {
skip : ( pagination . current - 1 ) * pagination . pageSize , max : pagination . pageSize
}
} ;
try {
/* 调用 api */
const result = yield call ( api . getBizTableData , payload ) ;
/* 正常返回 */
yield put ( actions . putBizTableDataSuccessResult ( result ) ) ;
} catch ( err ) {
/* 错误返回 */
yield put ( actions . putBizTableDataFailResult ( ) ) ;
}
} Wenn Sie mit redux-saga achten Sie nicht zu sehr auf das spezifische Schreiben des Codes. Sie sollten die spezifischen Schritte dieses Geschäfts verstehen, indem Sie sich die Kommentare ansehen:
state erforderlich ist, und der Selektor wird gerade hier aufgerufen.Wie sollen wir bestimmte Testfälle schreiben? Wir alle wissen, dass diese Art von Geschäftsordnung Anrufe von API oder anderen Ebenen beinhaltet. Wenn Sie Unit -Tests schreiben möchten, müssen Sie einige Mocks durchführen, um zu verhindern, dass die API -Schicht tatsächlich aufgerufen wird. Schauen wir uns an, wie Sie Testfälle für diese Saga schreiben:
import { put , select } from 'redux-saga/effects' ;
// ...
/* 测试获取数据 */
test ( 'request data, check success and fail' , ( ) => {
/* 当前的业务状态 */
const state = {
bizToolbar : {
keywords : 'some keywords'
} ,
bizTable : {
pagination : {
current : 1 ,
pageSize : 15
}
}
} ;
const gen = cloneableGenerator ( saga . onGetBizTableData ) ( ) ;
/* 1. 是否调用了正确的 selector 来获得请求时要发送的参数 */
expect ( gen . next ( ) . value ) . toEqual ( select ( getBizToolbar ) ) ;
expect ( gen . next ( state . bizToolbar ) . value ) . toEqual ( select ( getBizTable ) ) ;
/* 2. 是否调用了 api 层 */
const callEffect = gen . next ( state . bizTable ) . value ;
expect ( callEffect [ 'CALL' ] . fn ) . toBe ( api . getBizTableData ) ;
/* 调用 api 层参数是否传递正确 */
expect ( callEffect [ 'CALL' ] . args [ 0 ] ) . toEqual ( {
keywords : 'some keywords' ,
paging : { skip : 0 , max : 15 }
} ) ;
/* 3. 模拟正确返回分支 */
const successBranch = gen . clone ( ) ;
const successRes = {
items : [
{ id : 1 , code : '1' } ,
{ id : 2 , code : '2' }
] ,
total : 2
} ;
expect ( successBranch . next ( successRes ) . value ) . toEqual (
put ( actions . putBizTableDataSuccessResult ( successRes ) ) ) ;
expect ( successBranch . next ( ) . done ) . toBe ( true ) ;
/* 4. 模拟错误返回分支 */
const failBranch = gen . clone ( ) ;
expect ( failBranch . throw ( new Error ( '模拟产生异常' ) ) . value ) . toEqual (
put ( actions . putBizTableDataFailResult ( ) ) ) ;
expect ( failBranch . next ( ) . done ) . toBe ( true ) ;
} ) ; Dieser Testfall ist etwas komplizierter als der vorherige. Lassen Sie uns zuerst über das Prinzip des Testens von Saga sprechen. Wie bereits erwähnt, gibt SAGA tatsächlich verschiedene deklarative effects zurück und wird dann tatsächlich von der Engine ausgeführt. Der Zweck unseres Tests ist daher zu sehen, ob die Erzeugung von effects den Erwartungen entspricht. Wirkt sich also eine magische Sache effect ? Es ist eigentlich ein wörtliches Objekt!
Wir können diese wörtlichen Objekte auf die gleiche Weise in der Geschäftsordnung erzeugen. Behauptungen von wörtlichen Objekten sind sehr einfach, und es besteht keine Notwendigkeit, sich zu verspotten, ohne die API -Schicht direkt aufzurufen! Der Schritt dieses Testfalls besteht darin, die Generatorfunktion zu verwenden, um die nächste effect Schritt für Schritt zu generieren und dann zu behaupten und zu vergleichen.
Wie aus den obigen Kommentaren 3 und 4 hervorgeht, bietet
redux-sagaauch einige Helferfunktionen, um die Zweig-Haltepunkte problemlos zu verarbeiten.
Dies ist auch der Grund, warum ich mich für redux-saga entschieden habe: Es ist mächtig und förderlich für das Testen.
Als nächstes folgt die API -Schicht. Wie bereits erwähnt, habe ich fetch verwendet, um Hintergrundanfragen aufzurufen. Ich habe zwei Methoden zusammengefasst, um die Anruf- und Ergebnisverarbeitung zu vereinfachen: getJSON() und postJSON() , entsprechend den Anfragen von GET und Post. Schauen wir uns den API -Layer -Code an:
import { fetcher } from '@/utils/fetcher' ;
export function getBizTableData ( payload ) {
return fetcher . postJSON ( '/api/biz/get-table' , payload ) ;
}Die Geschäftsordnung ist einfach, daher sind die Testfälle einfach:
import sinon from 'sinon' ;
import { fetcher } from '@/utils/fetcher' ;
import * as api from '@/services/bizApi' ;
/* 测试 bizApi */
describe ( 'bizApi' , ( ) => {
let fetcherStub ;
beforeAll ( ( ) => {
fetcherStub = sinon . stub ( fetcher ) ;
} ) ;
// ...
/* getBizTableData api 应该调用正确的 method 和传递正确的参数 */
test ( 'getBizTableData api should call postJSON with right params of fetcher' , ( ) => {
/* 模拟参数 */
const payload = { a : 1 , b : 2 } ;
api . getBizTableData ( payload ) ;
/* 检查是否调用了工具库 */
expect ( fetcherStub . postJSON . callCount ) . toBe ( 1 ) ;
/* 检查调用参数是否正确 */
expect ( fetcherStub . postJSON . lastCall . calledWith ( '/api/biz/get-table' , payload ) ) . toBe ( true ) ;
} ) ;
} ) ; Da die API -Ebene die Werkzeugbibliothek direkt aufruft, verwenden wir hier sinon.stub() , um die Werkzeugbibliothek zu ersetzen, um Testzwecke zu erreichen.
Testen Sie als nächstes die von Ihnen eingefasste Fetch -Tool -Bibliothek. Hier verwende ich isomorphic-fetch , daher habe ich nock ausgewählt, um den Server zum Testen zu simulieren, hauptsächlich die Ergebnisse des normalen Zugriffs zu testen und Serverausnahmen zu simulieren usw. Das Beispielfragment lautet wie folgt:
import nock from 'nock' ;
import { fetcher , FetchError } from '@/utils/fetcher' ;
/* 测试 fetcher */
describe ( 'fetcher' , ( ) => {
afterEach ( ( ) => {
nock . cleanAll ( ) ;
} ) ;
afterAll ( ( ) => {
nock . restore ( ) ;
} ) ;
/* 测试 getJSON 获得正常数据 */
test ( 'should get success result' , ( ) => {
nock ( 'http://some' )
. get ( '/test' )
. reply ( 200 , { success : true , result : 'hello, world' } ) ;
return expect ( fetcher . getJSON ( 'http://some/test' ) ) . resolves . toMatch ( / ^hello.+$ / ) ;
} ) ;
// ...
/* 测试 getJSON 捕获 server 大于 400 的异常状态 */
test ( 'should catch server status: 400+' , ( done ) => {
const status = 500 ;
nock ( 'http://some' )
. get ( '/test' )
. reply ( status ) ;
fetcher . getJSON ( 'http://some/test' ) . catch ( ( error ) => {
expect ( error ) . toEqual ( expect . any ( FetchError ) ) ;
expect ( error ) . toHaveProperty ( 'detail' ) ;
expect ( error . detail . status ) . toBe ( status ) ;
done ( ) ;
} ) ;
} ) ;
/* 测试 getJSON 传递正确的 headers 和 query strings */
test ( 'check headers and query string of getJSON()' , ( ) => {
nock ( 'http://some' , {
reqheaders : {
'Accept' : 'application/json' ,
'authorization' : 'Basic Auth'
}
} )
. get ( '/test' )
. query ( { a : '123' , b : 456 } )
. reply ( 200 , { success : true , result : true } ) ;
const headers = new Headers ( ) ;
headers . append ( 'authorization' , 'Basic Auth' ) ;
return expect ( fetcher . getJSON (
'http://some/test' , { a : '123' , b : 456 } , headers ) ) . resolves . toBe ( true ) ;
} ) ;
// ...
} ) ; Grundsätzlich gibt es nichts Kompliziertes. Die Hauptsache ist zu beachten, dass Fetch eine Versprechensrendite ist und verschiedene asynchrone Testlösungen von jest sehr gut erfüllt werden können.
Der Rest hängt mit der Benutzeroberfläche zusammen.
Der Hauptzweck der Containerkomponente besteht darin, Status und Handlungen zu bestehen. Schauen Sie sich den Containerkomponentencode in der Symbolleiste an:
import { connect } from 'react-redux' ;
import { getBizToolbar } from '@/store/selectors' ;
import * as actions from '@/store/actions/bizToolbar' ;
import BizToolbar from '@/components/BizToolbar' ;
const mapStateToProps = ( state ) => ( {
... getBizToolbar ( state )
} ) ;
const mapDispatchToProps = {
reload : actions . reload ,
updateKeywords : actions . updateKeywords
} ;
export default connect ( mapStateToProps , mapDispatchToProps ) ( BizToolbar ) ; Dann besteht der Zweck der Testfälle auch darin, diese zu überprüfen. Hier verwenden wir redux-mock-store um Redux Store zu simulieren:
import React from 'react';
import { shallow } from 'enzyme';
import configureStore from 'redux-mock-store';
import BizToolbar from '@/containers/BizToolbar';
/* 测试容器组件 BizToolbar */
describe('BizToolbar container', () => {
const initialState = {
bizToolbar: {
keywords: 'some keywords'
}
};
const mockStore = configureStore();
let store;
let container;
beforeEach(() => {
store = mockStore(initialState);
container = shallow(<BizToolbar store={store}/>);
});
/* 测试 state 到 props 的映射是否正确 */
test('should pass state to props', () => {
const props = container.props();
expect(props).toHaveProperty('keywords', initialState.bizToolbar.keywords);
});
/* 测试 actions 到 props 的映射是否正确 */
test('should pass actions to props', () => {
const props = container.props();
expect(props).toHaveProperty('reload', expect.any(Function));
expect(props).toHaveProperty('updateKeywords', expect.any(Function));
});
});
Es ist sehr einfach, also gibt es nichts zu sagen.
Hier nehmen wir die Tabellenkomponente als Beispiel an und werden uns direkt ansehen, wie der Testfall geschrieben wird. Im Allgemeinen testen wir hauptsächlich die folgenden Aspekte von UI -Komponenten:
Hier ist der Testfallcode:
import React from 'react';
import { mount } from 'enzyme';
import sinon from 'sinon';
import { Table } from 'antd';
import * as defaultSettingsUtil from '@/utils/defaultSettingsUtil';
import BizTable from '@/components/BizTable';
/* 测试 UI 组件 BizTable */
describe('BizTable component', () => {
const defaultProps = {
loading: false,
pagination: Object.assign({}, {
current: 1,
pageSize: 15,
total: 2
}, defaultSettingsUtil.pagination),
data: [{id: 1}, {id: 2}],
getData: sinon.fake(),
updateParams: sinon.fake()
};
let defaultWrapper;
beforeEach(() => {
defaultWrapper = mount(<BizTable {...defaultProps}/>);
});
// ...
/* 测试是否渲染了正确的功能子组件 */
test('should render table and pagination', () => {
/* 是否渲染了 Table 组件 */
expect(defaultWrapper.find(Table).exists()).toBe(true);
/* 是否渲染了 分页器 组件,样式是否正确(mini) */
expect(defaultWrapper.find('.ant-table-pagination.mini').exists()).toBe(true);
});
/* 测试首次加载时数据列表为空是否发起加载数据请求 */
test('when componentDidMount and data is empty, should getData', () => {
sinon.spy(BizTable.prototype, 'componentDidMount');
const props = Object.assign({}, defaultProps, {
pagination: Object.assign({}, {
current: 1,
pageSize: 15,
total: 0
}, defaultSettingsUtil.pagination),
data: []
});
const wrapper = mount(<BizTable {...props}/>);
expect(BizTable.prototype.componentDidMount.calledOnce).toBe(true);
expect(props.getData.calledOnce).toBe(true);
BizTable.prototype.componentDidMount.restore();
});
/* 测试 table 翻页后是否正确触发 updateParams */
test('when change pagination of table, should updateParams', () => {
const table = defaultWrapper.find(Table);
table.props().onChange({current: 2, pageSize: 25});
expect(defaultProps.updateParams.lastCall.args[0])
.toEqual({paging: {current: 2, pageSize: 25}});
});
});
Dank der Rationalität der Konstruktionsschichtung ist es für uns einfach, props zu verwenden, um Testzwecke zu erreichen. In Kombination von enzyme und sinon halten die Testfälle immer noch einen einfachen Rhythmus.
Das obige ist die vollständige Testfall -Ideen und den Beispielcode für dieses Szenario. Die im Artikel genannten Ideen und Methoden können auch in Vue und Angular verwendet werden. Der vollständige Code -Inhalt ist da (ich werde wichtigere Dinge sagen, ich denke, es ist einfach, mir zu helfen).
Schließlich können wir die Deckungsrate verwenden, um festzustellen, ob das Abdeckungsniveau des Anwendungsfalls ausreicht (im Allgemeinen müssen abhängig von der tatsächlichen Situation nicht absichtlich 100%verfolgt werden):

Unit-Tests sind die Grundlage für die TDD-Testentwicklung. Aus dem obigen Prozess können wir sehen, dass eine gute Designhierarchie leicht zu schreiben ist, und die Tests in Units können nicht nur die Qualität des Codes sicherstellen: Wird es Sie zwingen, über die Rationalität des Codedesigns nachzudenken und den Noodle -Code abzulehnen?
Um den Abschluss des sauberen Codes auszuleihen:
Während er an der Agility Conference in Denver teilnahm, gab Elisabeth Hedrickson mir ein grünes Armband, das dem von Lance Armstrong verkauft wurde. Das Armband sagt "Test besessen". Ich zog es mit Freude an und hielt es stolz an. Ich war wirklich besessen von der testgetriebenen Entwicklung, seit ich 1999 etwas über TDD von Kent Beck erfahren habe.
Aber etwas Seltsames passierte. Ich konnte den Riemen nicht entfernen. Nicht nur, weil das Armband sehr eng ist, sondern auch ein spiritueller Zauber. Dieses Armband ist eine Erklärung meiner Arbeitsmoral und ein Tipp für mein Engagement, mein Bestes zu geben, um den besten Code zu schreiben. Nehmen Sie es aus, als ob es gegen diese Erklärungen und Versprechen wäre.
Also ist es immer noch an meinem Handgelenk. Beim Schreiben von Code sah ich ihn aus meinem Auge. Es erinnerte mich immer wieder daran, dass ich versprach, ordentliche Code zu schreiben.