iT邦幫忙

2017 iT 邦幫忙鐵人賽
DAY 26
0

前言

測試的種類有許多種,包括單元測試、整合測試。一般會先從單元測試做起,打好根基,也能加強效率高的小範圍的除錯。

在 React Native 中,有很多的理由應該盡量可能的使用 JavaScript 來寫程式碼,避免掉 Objective-C、Swift、Java 的程式碼,測試也是其中一個理由。要跑三個環境的單元測試想來大家都不願意,用 JavaScript 的話就可以統一使用 Jest 即可。

單元測試

為何選擇 Jest?

目前說到單元測試,會想到的不外乎以下三個選擇:

  • Jest
  • Ava
  • Mocha

Mocha

TJ Holowaychuk 開發的 Mocha 曾經是一代霸主,當時想到 JavaScript 單元測試就會直接想到它,兩年前沒什麼其他對手,至今也仍然為這三個之中下載數最高的。

不過現在看來它的原始碼比較老舊,從 v2.0v3.0 花了近兩年時間,目前雖然穩定但看起來也比較沒有改進的動作。

在 Ava 跟 Jest 都已經能平行跑多個測試的情況下,相形失色。

雖然筆者大部分的新專案都使用 Jest,但如果有需要搭配 Karma Runner 跑在瀏覽器中的情況,無法使用 Jest (需要 Node 環境) 時還是會使用 Mocha。

Ava

Ava 則是 Node 界最有生產力的 sindresorhus 最看重的 Project 之一。

在大神加持伴隨著完整內建支援 ES6、平行跑測試、簡潔的 Assertion,乍看之下也是很不錯的工具。筆者只用了不到一個月的時間,雖然覺得略優於 Mocha 但卻也遇到一些問題。

Ava 最大的問題在於為完整社群開發的工具,不似 Jest 有 Facebook 支付全職員工在做事,也不似 Mocha 是 JS Foundation 的 Project 還有眾多的 Backer 與 Sponsor,自願者雖然辛苦但回應速度還是遠不及收錢辦事的開發者,這也是一個殘酷面。

Kent C. Dodds 也發了一篇 Migrating to Jest,寫了一些跟筆者經歷過的很類似的心路歷程,最後都換到 Jest。

Jest

在 2014 年,Jest 還是眾所抱怨的對象,Auto Mock 的功能沒有幫到太多的情況下,測試速度慢、卡住等等的問題很多,但 2016 年,Jest 就像是脫胎換骨了一樣,完全不像是同個東西:

(筆者用過 v0.6 非常的慘,現在的 v18 則很令人滿意)

除了可平行的跑測試,好用的 Watch Mode、jest.fn 產生的 Mock 跟 Assertion 處得相當融洽,更是有特有的 Snapshot Testing,用了就回不去了。

只要是程式都會有一些問題是難免,但 Jest 處理的速度是非常之快。在我曾經發過的這個 Issue 中,Christoph Pojer 處理的速度讓我非常印象深刻,修好到發布新版還不到三個工作天:

跑 Jest 測試

React Native v0.38 之後 react-native init 的時候就會內建 Jest 在專案裡面,該做的設定也都已經自動設定好。

<root>/__tests__ 也能看到兩個預先擺放好的測試檔:

// <root>/__tests__/index.ios.js
import 'react-native';
import React from 'react';
import Index from '../index.ios.js';

// Note: test renderer must be required after react-native.
import renderer from 'react-test-renderer';

it('renders correctly', () => {
  const tree = renderer.create(
    <Index />
  );
});

直接執行:

npm test

就能執行測試囉。

Snapshot Testing

沒有平台依賴性的 JavaScript 程式一般都很好測試,也不會跟在寫 Node 時的單元測試有太大的差別。不過 Component 就不一樣,有平台的依賴性,而寫單元測試時我們應該測試自己寫的程式,不應該去測試到 React Native 又或是其他的套件所寫的程式,所以只要測試 Render 出正確的結構,而不用真的去跑在手機模擬器上。

要測試 Render 出來的東西是否符合預期主要有以下兩種做法:

  1. 使用 react-native-mock 來 Mock react-native 並使用 Shallow Rendering
  2. 使用 react-test-renderer 做 Snapshot Testing

而 Snapshot Testing 會稍微方便一些,當 UI 改來改去時,只要下 jest -u 就能更新 Snapshot 而不需要一再的修改很脆弱的測試

Snapshot Testing 相當簡單,只要用 renderer.create 去 Render 然後呼叫 toJSON 後丟去 toMatchSnapshot Assertion。

import renderer from 'react-test-renderer';

const tree = renderer.create(
  <Intro />
).toJSON();
expect(tree).toMatchSnapshot();

假設有一個 Intro Component:

// __tests__/Intro-test.js
import 'react-native';
import React from 'react';
import Intro from '../Intro';

// Note: test renderer must be required after react-native.
import renderer from 'react-test-renderer';

it('renders correctly', () => {
  const tree = renderer.create(
    <Intro />
  ).toJSON();
  expect(tree).toMatchSnapshot();
});

會產生一個 .js.snap 的檔案,裡面會有剛剛 Value 的 Snapshot:

// __tests__/__snapshots__/Intro-test.js.snap
exports[`Intro renders correctly 1`] = `
<View
  style={
    Object {
      "alignItems": "center",
      "backgroundColor": "#F5FCFF",
      "flex": 1,
      "justifyContent": "center",
    }
  }>
  <Text
    style={
      Object {
        "fontSize": 20,
        "margin": 10,
        "textAlign": "center",
      }
    }>
    Welcome to React Native!
  </Text>
  <Text
    style={
      Object {
        "color": "#333333",
        "marginBottom": 5,
        "textAlign": "center",
      }
    }>
    This is a React Native snapshot test.
  </Text>
</View>
`;

這樣如果有改變的話,都能從 Git 的 Commit 上看到,也能在 Pull Request 進行 Review。

結語

筆者一直認為,寫單元測試是寫出高品質的程式碼與琢磨架構設計的關鍵。唯有測試存在,才能安心的盡己所能去重構,讓程式碼擁有更好的未來性。


上一篇
Day 25:實作 Navigation 與 Drawer
下一篇
Day 27:React Native 除錯
系列文
使用 Modern Web 技術來打造 Native App30

尚未有邦友留言

立即登入留言