# 先说一下示例怎么运行,先确定本机安装好 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 中웹 응용 프로그램이 점점 더 복잡해짐에 따라 많은 회사가 프론트 엔드 장치 테스트에 점점 더 많은 관심을 기울이고 있습니다. 우리가 보는 대부분의 자습서는 단위 테스트의 중요성과 일부 대표적인 테스트 프레임 워크 API를 사용하는 방법에 대해 이야기하지만 실제 프로젝트에서 단위 테스트를 시작하는 방법은 무엇입니까? 테스트 사례는 어떤 특정 컨텐츠에 포함되어야합니까?
이 기사는 실제 애플리케이션 시나리오에서 시작하여 설계 패턴 및 코드 구조에서 포함 해야하는 단위 테스트 및 특정 테스트 사례를 작성하는 방법을 분석합니다. 나는 당신이 보는 어린이 신발에서 무언가를 얻을 수 있기를 바랍니다.
이 프로젝트는 react Technology Stack을 사용하며 사용 된 주요 프레임 워크에는 react , redux , react-redux , redux-actions , reselect , redux-saga , seamless-immutable , antd 있습니다.

UI 계층 에서이 응용 프로그램 시나리오는 주로 두 부분으로 구성됩니다.
여기에서 어린이 신발이 보이면 다음과 같이 말할 수 있습니다. 이러한 간단한 인터페이스와 비즈니스 논리가 여전히 실제 시나리오입니까? 여전히 Shenma Unit 테스트를 작성해야합니까?
기사의 읽기 경험과 길이를 보장하기 위해 걱정하지 마십시오. 문제를 명확하게 설명 할 수있는 간단한 장면은 좋은 장면입니다. 천천히 내려다보세요.
이 시나리오 설계 및 개발에서 우리는 redux 일원 데이터 흐름 및 react-redux 의 모범 사례를 엄격히 준수하며 redux-saga 사용하여 비즈니스 흐름을 처리하고, 상태 캐시를 처리하기 위해 reselect 및 실제 프로젝트와 다르지 않은 fetch 통해 백엔드 인터페이스를 호출합니다.
계층화 된 설계 및 코드 구성은 다음과 같습니다.

중간 store 의 내용은 모두 redux 와 관련이 있으며 이름을 보면 의미를 알아야합니다.
특정 코드는 여기를 참조하십시오.
먼저 어떤 테스트 프레임 워크 및 도구가 사용되는지에 대해 이야기 해 봅시다. 주요 내용에는 다음이 포함됩니다.
jest , 테스트 프레임 워크enzyme , 반응 UI 층 테스트를 전문으로합니다sinon 에는 독립적 인 가짜, 스파이, 스터브 및 모의 도서관이 있습니다.nock , HTTP 서버 시뮬레이션위의 사용 및 구성에 익숙하지 않은 어린이 신발이있는 경우 공식 문서를 읽으십시오.이 문서는 튜토리얼보다 낫습니다.
다음으로 특정 테스트 사례 코드를 작성하기 시작합니다. 다음은 각 레벨에 대한 코드 스 니펫과 구문 분석을 제공합니다. 그런 다음 actions 부터 시작하겠습니다.
기사를 가능한 한 짧고 명확하게하기 위해 다음 코드 스 니펫은 각 파일의 전체 내용이 아니며 전체 내용이 여기에 있습니다.
사업에서 나는 redux-actions 사용하여 action 생성했습니다. 여기서는 도구 모음을 예로 사용합니다. 먼저 비즈니스 코드를 살펴 보겠습니다.
import { createAction } from 'redux-actions' ;
import * as type from '../types/bizToolbar' ;
export const updateKeywords = createAction ( type . BIZ_TOOLBAR_KEYWORDS_UPDATE ) ;
// ... actions 테스트의 경우 주로 생성 된 action 객체가 올바른지 확인합니다.
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 ) ;
} ) ;
// ...
} ) ;이 테스트 사례의 논리는 매우 간단합니다. 먼저, 우리가 기대하는 결과를 구축 한 다음 비즈니스 코드를 호출 한 다음 마지막으로 비즈니스 코드의 운영 결과가 기대치와 일치하는지 확인하십시오. 이것은 테스트 사례 작성의 기본 루틴입니다.
우리는 시험 사례를 작성할 때 유스 케이스 단일 책임을 유지하려고 노력하고 있으며 너무 많은 비즈니스 범위를 다루지 않습니다. 많은 테스트 사례가있을 수 있지만 각각 복잡하지 않아야합니다.
다음은 redux-actions 의 handleActions 사용하여 reducer 작성하는 reducers 입니다. 예를 들어 테이블은 다음과 같습니다.
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
) ;여기서 상태 객체는
seamless-immutable
reducer 의 경우 주로 두 가지 측면을 테스트합니다.
action.type 의 경우, 유형, 현재 상태를 반환 할 수 있는지 여부.위의 두 가지 점에 대한 테스트 코드는 다음과 같습니다.
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 ) ;
} ) ;
// ...
} ) ;여기서 테스트 사례의 논리도 매우 간단하며 위의 예상 결과를 주장하는 일상입니다. 아래는 선택기의 일부입니다.
selector 의 기능은 해당 비즈니스의 상태를 얻는 것입니다. state 변경되지 않을 때 재 계산을 방지하기 위해 캐시에 reselect 하여 재 제거됩니다. 먼저 테이블의 선택기 코드를 살펴 보겠습니다.
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 } ) ;
} ) ;여기의 호출기의 매개 변수는 프로젝트에서 균일하게 설정되므로 재 선출은이 작업을 잘 수행합니다. 비즈니스 상태가 변경되지 않은 상태로 유지되면 마지막 캐시로 직접 돌아갑니다. 페이징 장치의 기본 설정은 다음과 같습니다.
export const pagination = {
size : 'small' ,
showTotal : ( total , range ) => ` ${ range [ 0 ] } - ${ range [ 1 ] } / ${ total } ` ,
pageSizeOptions : [ '15' , '25' , '40' , '60' ] ,
showSizeChanger : true ,
showQuickJumper : true
} ;그러면 우리의 테스트는 주로 두 가지 측면입니다.
테스트 코드는 다음과 같습니다.
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 ) ;
} ) ;
} ) ;테스트 케이스가 여전히 매우 간단합니까? 이 속도를 유지하십시오. 약간 복잡한 부분 인 Sagas 부분에 대해 이야기합시다.
여기서는 redux-saga 사용하여 비즈니스 흐름을 처리합니다. 비즈니스 흐름은 구체적으로 API라고 불리는 데이터를 비동기로 요청하고 성공적인 결과 및 오류 결과를 처리합니다.
일부 어린이 신발은 너무 복잡하다고 생각할 수도 있습니다. redux-thunk 비동기 적으로 요청을 사용하는 것이 끝나지 않습니까? 걱정하지 마십시오. 참을성있게 읽은 후에 이해할 것입니다.
여기서 redux-saga 의 작업 방법을 간단히 소개해야합니다. SAGA는 es6 발전기 기능 - 발전기로, 우리는 다양한 선언 effects 생성하는 데 사용되는데, 이는 비즈니스를 주도하기 위해 redux-saga 엔진에 의해 소화되고 처리됩니다.
여기서 우리는 테이블 데이터를 얻기위한 비즈니스 코드를 살펴 봅니다.
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 ( ) ) ;
}
} redux-saga 에 익숙하지 않은 경우 코드의 특정 글쓰기에 너무주의를 기울이지 마십시오. 의견을 보면이 비즈니스의 특정 단계를 이해해야합니다.
state 에서 API를 호출 할 때 필요한 매개 변수 부품 (검색 키워드, 페이지)을 가져 오면 현재 선택기가 여기에서 호출됩니다.그렇다면 어떻게 특정 테스트 사례를 작성해야합니까? 우리는 이러한 종류의 비즈니스 코드에는 API 또는 다른 계층의 통화가 포함된다는 것을 알고 있습니다. 단위 테스트를 작성하려면 API 레이어가 실제로 호출되는 것을 방지하기 위해 모의를 수행해야합니다. 이 사가에 대한 테스트 케이스를 작성하는 방법을 살펴 보겠습니다.
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 ) ;
} ) ; 이 테스트 사례는 이전 테스트보다 조금 더 복잡합니다. 사가를 먼저 테스트하는 원리에 대해 이야기 해 봅시다. 앞에서 언급했듯이 Saga는 실제로 다양한 선언 effects 반환 한 다음 실제로 엔진에 의해 실행됩니다. 따라서 테스트의 목적은 effects 생성이 기대에 부응하는지 여부를 확인하는 것입니다. effect 가 마법의 일입니까? 실제로 문자 그대로의 대상입니다!
비즈니스 코드에서 동일한 방식으로 이러한 문자 그대로의 객체를 생성 할 수 있습니다. 문자 그대로의 물체에 대한 주장은 매우 간단하며 API 계층을 직접 호출하지 않고 조롱 할 필요가 없습니다! 이 테스트 사례의 단계는 발전기 기능을 사용하여 다음 effect 단계별로 생성 한 다음 주장하고 비교하는 것입니다.
위의 주석 3과 4에서 볼 수 있듯이
redux-saga또한 분기 중단 점을 쉽게 처리하기위한 일부 도우미 기능을 제공합니다.
이것이 또한 redux-saga 선택한 이유입니다. 테스트에 강력하고 도움이됩니다.
다음은 API 레이어 관련입니다. 앞에서 언급했듯이 fetch 사용하여 배경 요청을 호출했습니다. getJSON() 및 postJSON() 각각 get 및 post 요청에 해당하는 통화 및 결과 처리를 단순화하기 위해 두 가지 방법을 캡슐화했습니다. API 계층 코드를 살펴 보겠습니다.
import { fetcher } from '@/utils/fetcher' ;
export function getBizTableData ( payload ) {
return fetcher . postJSON ( '/api/biz/get-table' , payload ) ;
}비즈니스 코드는 간단하므로 테스트 사례는 간단합니다.
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 ) ;
} ) ;
} ) ; API 계층은 도구 라이브러리를 직접 호출하므로 여기서는 sinon.stub() 사용하여 도구 라이브러리를 교체하여 테스트 목적으로 달성합니다.
다음으로 캡슐을 캡슐화 한 페치 도구 라이브러리를 테스트하십시오. 여기에서는 isomorphic-fetch 사용하므로 테스트를 위해 서버를 시뮬레이션하기 위해 nock 선택하여 주로 일반 액세스 및 서버 예외를 시뮬레이션하는 것 등을 테스트합니다. 예제 조각은 다음과 같습니다.
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 ) ;
} ) ;
// ...
} ) ; 기본적으로 복잡한 것은 없습니다. 가장 중요한 것은 Fetch가 약속의 귀환이며, 다양한 비동기식 테스트 jest 은 매우 잘 충족 될 수 있습니다.
나머지는 UI와 관련이 있습니다.
컨테이너 구성 요소의 주요 목적은 상태와 동작을 전달하는 것입니다. 도구 모음에서 컨테이너 구성 요소 코드를보십시오.
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 ) ; 그런 다음 테스트 사례의 목적은 이것을 확인하는 것입니다. 여기에서는 redux-mock-store 사용하여 Redux 매장을 시뮬레이션합니다.
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));
});
});
매우 간단하기 때문에 할 말이 없습니다.
여기서 우리는 테이블 구성 요소를 예제로 취하며 테스트 케이스가 어떻게 작성되는지 직접 살펴 보겠습니다. 일반적으로, 우리는 주로 UI 구성 요소의 다음 측면을 테스트합니다.
테스트 사례 코드는 다음과 같습니다.
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}});
});
});
설계 계층화의 합리성 덕분에 테스트 목적을 달성하기 위해 구성 props 사용하기가 쉽습니다. enzyme 와 sinon 결합한 테스트 케이스는 여전히 간단한 리듬을 유지합니다.
위는이 시나리오에 대한 완전한 테스트 사례 작성 아이디어 및 샘플 코드입니다. 이 기사에 언급 된 아이디어와 방법은 Vue 및 Angular 프로젝트에도 사용될 수 있습니다. 완전한 코드 내용이 여기에 있습니다 (더 중요한 말을 할 것입니다. 도움이 쉽다고 생각합니다).
마지막으로, 우리는 적용 범위를 사용하여 사용 사례의 적용 범위가 충분한 지 확인할 수 있습니다 (일반적으로 실제 상황에 따라 의도적으로 100%를 추구 할 필요가 없습니다).

단위 테스트는 TDD 테스트 중심 개발의 기초입니다. 위의 프로세스에서 우수한 설계 계층 구조가 테스트 케이스를 작성하기 쉽고 단위 테스트는 코드의 품질을 보장하는 것이 아니라 코드 설계의 합리성에 대해 생각하고 국수 코드를 거부 할 수 있습니까?
깨끗한 코드의 결론을 빌리려면 :
2005 년 덴버에서 열린 민첩성 컨퍼런스에 참석하면서 엘리자베스 헤드릭슨 (Elisabeth Hedrickson)은 랜스 암스트롱 (Lance Armstrong)이 판매 한 것과 비슷한 녹색 팔찌를 건네주었습니다. 팔찌는 "테스트에 집착한다"고 말합니다. 나는 기쁨으로 그것을 입히고 자랑스럽게 그것을 유지했다. 1999 년 켄트 벡 (Kent Beck)의 TDD에 대해 알게 된 이후 테스트 중심의 개발에 실제로 집착했습니다.
그러나 이상한 일이 일어났습니다. 나는 스트랩을 제거 할 수 없다는 것을 알았다. 팔찌가 매우 빡빡하기 때문일뿐만 아니라 영적 강화 주문이기도합니다. 그 팔찌는 내 직장 윤리 선언이며 최고의 코드를 작성하기 위해 최선을 다하기위한 헌신의 팁입니다. 이 선언과 약속에 위배되는 것처럼 마치 꺼냅니다.
그래서 여전히 내 손목에 있습니다. 코드를 작성하는 동안 눈 바닥에서 코드를 보았습니다. 깔끔한 코드를 작성하겠다고 약속했다는 사실을 계속 상기시켜주었습니다.