自动化测试之
单元测试
Why
当产品变得更大更复杂
导致模块间依赖增加
How
一个简单的测试
import test from 'ava'
test(t => {
t.is(1 + 1, 2)
})
yarn add ava
ava --watch
一段代码(方法,一系列操作)的返回值是否符合我们的预期
以React为例
Redux测试 (Model测试)
Redux就是用一堆Reducer函数来reduce所有事件用来做全局Store的状态机(FSM)
Like VUEX
Reducer
export const engineerManage = (state = [], action) => {
switch (action.type) {
case 'engineer/ADD':
return [...state, action.engineer]
case 'engineer/DEL':
return _.drop(state, action.engineer);
default: return state
}
}
工程师管理系统有增加和删除工程师两个功能
Redux 测试
test('parking lot', t => {
const initial = engineerManage(undefined, {})
t.deepEqual(initial, [], 'should be empty when init')
const add = engineerManage(initial, { type: 'engineer/ADD', engineer: '潘嘉慧' })
t.deepEqual(add, ['潘嘉慧'], 'should add engineer')
const del = engineerManage(add, { type: '赵楠' })
t.deepEqual(del, ['潘嘉慧'], 'shouldn't delete any engineer')
})
Model层的测试非常简单和机械化
简单到我们可以自动生成
(redux-devtools写完实现,在浏览器里打开,反过来还可以自动生成各种框架的测试代码,粘贴回来就行了)
组件测试(View测试)
React -> View Lib
渲染和捕捉事件
渲染和以Virtual DOM 的模式封装了恶心的浏览器基础设置,使得我们可以以函数和数据结构描述组件
一个简单的React组件
const Greeter = ({ name }) =>
<p>你好 {name}!</p>
render(
<Greeter name="React"/>,
document.body
)
import { renderJSX, JSX } from 'jsx-test-helpers'
const Paragraph = ({ children }) => <p>{children}</p>
const Greeter = ({ name }) => <Paragraph>你好 {name}!</Paragraph>
test('Greeter', t => {
t.is(renderJSX(<Greeter name="皓哥"/>),
JSX(<Paragraph>你好 皓哥!</Paragraph>),
'should render greeting text with name')
})
jsx-test-helpers
针对View事件的测试
const TextField = ({ label, onChange }) => <label>
{label}
<input type="text" onChange={onChange} />
</label>
test('TextField', t => {
const onChange = () => {}
const actual = renderJSX(<TextField label="Email" onChange={onChange} />)
const expected = JSX(<label>
Email
<input type="text" onChange={onChange}/>
</label>)
t.is(actual, expected)
})
一个简单的输入框组件
注入按钮点击事件
细粒度的测试成本更加低廉
FSM Model 有M种可能
View 的显示逻辑有N个
将两个集成到一起测试就有M * N 种测试路径
而将两种测试拆开只有M + N 种路径
好的测试即简单的判等
异步Effect测试
React -> DOM
Redux -> 同步Model
异步 -> ?
redux-saga
redux-saga is a library that aims to make application side effects (i.e. asynchronous things like data fetching and impure things like accessing the browser cache) easier to manage, more efficient to execute, simple to test, and better at handling failures. —— 来自官网
一段简单的异步请求处理
import { takeEvery, put, call, fork, cancel } from 'redux-saga/effects'
function *account() {
yield call(takeEvery, 'login/REQUESTED', login)
}
function *login({ name, password }) {
try {
const { token } = yield call(fetch, '/login', {
method: 'POST',
body: { name, password }
})
yield put({ type: 'login/SUCCEEDED', token })
} catch (error) {
yield put ({ type: 'login/FAILED', error })
}
}
test('account saga', t => {
const gen = account()
t.deepEqual(gen.next().value, call(takeEvery, 'login/REQUESTED', login))
})
test('login saga', t => {
const gen = login({ name: 'John', password: 'super-secret-123'})
const request = gen.next().value
t.deepEqual(request, call(fetch, '/login', {
method: 'POST',
body: { name: 'John', password: 'super-secret-123'}
}))
const response = gen.next({ token: 'non-human-readable-token' }).value
t.deepEqual(response, put({
type: 'login/SUCCEEDED',
token: 'non-human-readable-token'
}))
const failure = gen.throw('You code just exploded!').value
t.deepEqual(failure, put({
type: 'login/FAILED',
error: 'You code just exploded!'
}))
})
THANKS
[React全家桶与前端单元测试艺术] https://mp.weixin.qq.com/s/1V6x2_zEisceJcmbAvjoPA
Unit Test
By georgezou
Unit Test
- 1,294