React元件單元測試

銅板街技術發表於2019-02-14

React元件自動化測試

1. 為什麼要進行自動化測試

當我們編寫一個元件的時候,要怎麼保證元件功能能達到預期呢?你可能回答:我可以人工測試。但是當經歷三四次迭代,當有多人協調開發,當進行重構的時候,如何能快速的驗證元件是否依然正確執行?這正是需要自動化測試的原因。無論經歷多少次迭代,好的自動化測試都能保證你的元件能夠正確執行。

A component that is untestable or hard to test is most likely badly designed.

經過測試的元件是可靠的,可測的元件的架構是合理的。如果一個元件難以下手編寫測試用例,只能證明這個元件的設計是糟糕的。因此,編寫測試用例的同時,可以幫助元件開發者發現問題,調整程式碼使架構更加合理。

React元件單元測試

2. 自動化測試的基礎內容

(1) 自動化測試分類

按照測試方法分類:黑盒測試和白盒測試

React元件單元測試

黑盒測試也稱功能測試,在測試中,把程式看作一個不能開啟的黑盒子,在完全不考慮程式內部結構和內部特性的情況下,在程式介面進行測試。使用者對內部邏輯並不可見。

白盒測試又稱結構測試、透明盒測試、邏輯驅動測試或基於程式碼的測試。白盒測試是一種測試用例設計方法,盒子指的是被測試的軟體,白盒指的是盒子是可視的,你清楚盒子內部的東西以及裡面是如何運作的。”白盒"法全面瞭解程式內部邏輯結構、對所有邏輯路徑進行測試。

(2) 測試金字塔

該概念是Mike Cohn 在他的著作《Succeeding with Agile》一書中提出的。

React元件單元測試

測試金字塔中提到的兩件事:

  • 編寫不同粒度的測試
  • 層次越高,你寫的測試應該越少

(3) 單元測試

指對軟體中的最小可測試單元進行檢查和驗證。單元測試作為測試金字塔最底層,粒度最小,測試速度最快,屬於白盒測試。

大多數單元測試包括四個主體:測試套件describe、測試用例it、判定條件expect、斷言結果toEqual。

(4) 測試覆蓋率

傳統的測試覆蓋方法常見的有以下幾種:

  • 函式覆蓋(Function Coverage)
  • 語句覆蓋(Statement Coverage)
  • 決策覆蓋(Decision Coverage)
  • 條件覆蓋(Condition Coverage
3. 前端自動化單元測試工具

Jest

Jest是一個輕量級的JavaScript測試框架,可以應用於Babel, TypeScript, Node, React, Angular, Vue等多種技術棧。

const sum = require('./sum');

test('adds 1 + 2 to equal 3', () => {
  expect(sum(1, 2)).toBe(3);
});
複製程式碼

Enzyme

翻譯為“溶解酶”,作為單元測試滲透於程式碼各個細節。一般使用 Enzyme 中的 mount 或 shallow 方法,將目標元件轉化為一個 ReactWrapper物件,並在測試中呼叫其各種方法:

import React from 'react';
import { expect } from 'chai';
import { render } from 'enzyme';

import Foo from './Foo';

describe('<Foo />', () => {
  it('renders three `.foo-bar`s', () => {
    const wrapper = render(<Foo />);
    expect(wrapper.find('.foo-bar')).to.have.lengthOf(3);
  });

  it('renders the title', () => {
    const wrapper = render(<Foo title="unique" />);
    expect(wrapper.text()).to.contain('unique');
  });
});
複製程式碼
4. 編寫有價值的React元件單元測試用例

React元件分為四種:

  • 展示型業務元件
  • 容器型業務元件
  • 通用 UI 元件
  • 功能型元件

根據業務場景,本文主要針對功能性元件進行闡述。

功能型元件,指的是跟業務無關的另一類元件:它是功能型的,更像是底層支撐著業務元件運作的基礎元件,比如文字框元件、按鈕元件等。功能性元件,更注重邏輯性,UI比較沒有那麼偏重。功能性元件一般包括JS跟CSS兩部分內容,CSS部分不作為測試的重點。

功能性元件必須測試的三部分:

  • Props傳入;
  • 元件分支渲染邏輯;
  • 事件呼叫和引數傳遞。

以上三部分測過後,將會達到較高的測試覆蓋率。

本文以Karma+Webpack+Mocha+Chai+Sion+istanbul-instrumenter-loader解決方案作為示例演示如何覆蓋以上功能性元件測試的三部分。

首先建立一個簡單的Input元件,包含label跟input兩個標籤,可以定義label跟input的值。

import React from 'react';
import PropTypes from 'prop-types';

export default class TextInput extends React.Component {
  static propTypes = {
    label: PropTypes.string,
    defaultValue: PropTypes.string,
    onChange: PropTypes.func
  }

  static defaultProps = {
    label: '',
    defaultValue: ''
  }

  onChange(e) {
    var val = e.target.value;

    if (this.props.onChange) {
      this.props.onChange(val);
    }
  }


  render() {
    const {label, defaultValue, onChange} = this.props;
    return (
      <div>
        {
          label ? (<label>{label}</label>) : null
        }
        <input defaultValue={this.props.defaultValue} onChange={this.onChange.bind(this)} ></input>
      </div>
    )
  }
}

複製程式碼

首先,測試Props是否正確傳入。

it('Validate attributes of the TextInput', () => {
  const props = {
    label: '測試',
    defaultValue: '測試值'
  }
  const wrapper = mount(<TextInput {...props}/>);
  expect(wrapper.find('label').text()).to.equal('測試');
  expect(wrapper.find('input').prop('defaultValue')).to.equal('測試值');
});
複製程式碼

其次,測元件分支渲染。

 it('Can not render label When label is null', () => {
    const wrapper = mount(<TextInput />);
    expect(wrapper.find('label').length).to.be.empty;
  });
  it('Render label When label is not null', () => {
    const wrapper = mount(<TextInput label='up'/>);
    expect(wrapper.find('label').length).not.to.be.empty;
  });
複製程式碼

最後,測事件呼叫。

 it('Validate onChange event of the TextInput', () => {
    var temp = '';
    const props = {
      onChange: function(value) {
        temp = value;
      }
    }
    const wrapper = mount(<TextInput {...props} value='測試值'/>);
    var input = wrapper.find('input');
    input.simulate('change', { target: { value: 'Changed' } });
    expect(temp).to.equal('Changed');
  });
複製程式碼

最後,跑一遍所有的測試用例,均是通過的。然後看下測試覆蓋率,分支覆蓋率為75%。

React元件單元測試

進一步開啟詳情檢視,原來是有一部分程式碼僅有if而沒有else,所以這部分未達到100%是可以忽略的。

React元件單元測試

如果十分介意,可以在if前加入/* istanbul ignore else */。

 onChange(e) {
    var val = e.target.value;
    /* istanbul ignore else  */
    if (this.props.onChange) {
      this.props.onChange(val);
    }
  }
複製程式碼

這樣就可以達到100%了。

React元件單元測試

總結

本文上半部分主要闡述了自動化測試的一些基礎知識,下半部分通過一個textinput元件例項,闡述瞭如何編寫有價值的測試用例,達到對元件程式碼的全覆蓋。

參考

React 單元測試策略及落地

測試金字塔實戰

前端自動化測試概覽

對 React 元件進行單元測試

Web 前端單元測試到底要怎麼寫

7 architectural attributes of a reliable React component

測試覆蓋(率)到底有什麼用?

作者簡介

南溪,銅板街前端開發工程師,2016年4月加入團隊,目前主要負責資金端交易側 H5專案開發。

React元件單元測試

更多精彩內容,請掃碼關注 “銅板街技術” 微信公眾號。

相關文章