自动化测试之

单元测试

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,188