CI / Unit Test 初體驗

前言

之前就一直想做一個簡單的 CI 來維持程式碼品質,這次剛好開始寫 NCU+的專案,就順便引入單元測試和 CI。這篇文章記錄了一些學習到的觀念和一些設定範例。

正文

單元測試

撰寫測試的 3A 原則

  1. Arrange
    • 初始化測資/設定測試預期值
  2. Act
    • 執行測試程式碼
  3. Assert
    • 檢查測試結果是否符合預期

遵照上面的原則可以大概知道單元測試要如何撰寫,接下來介紹 jest 在這三階段分別要如何應用。

實際案例

這邊使用 jest with ts-jest 來實作單元測試。jest 是一個 JavaScript 的測試框架,但他不支援 TypeScript 的語法,因此要透過 ts-jest 來對 ts 檔案做處理才跑得起來。

設定 jest

jest 的設定可以在package.json裡調整或是另外建一個jest.config.js來設定。以下以package.json的設定舉例:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
{
"dependencies": {
...
},
"jest": {
"testEnvironment": "jsdom",
"globals": {
"ts-jest": {
"tsConfig": "tsconfig.json"
}
},
"moduleFileExtensions": [
"js",
"ts",
"json",
"vue"
],
"transform": {
"^.+\\.ts$": "ts-jest",
"^.+\\.vue$": "@vue/vue3-jest"
},
"moduleNameMapper": {
"@/(.*)$": "<rootDir>/src/$1"
}
}
}

testEnvironment 選擇 jsdom 是因為晚點會對 Vue 的 component 做測試。

globals區塊裡可以針對 ts-jest 的設定進行細項調整。

moduleFileExtensions是設定你的 module 名稱結尾的 extension。

transform可以設定不同檔案的轉換方式,例如 jest 本身是不支援 TypeScript 的,不過經由 ts-jest 轉換後可以被 jest 讀取。Vue 也是同理。

moduleNameMapper可以設定 module 對應到的 mock,也能直接當成 path mapping 用。

撰寫第一個測試

add.ts

1
2
3
function add(a: number, b: number) {
return a + b;
}

add.spec.ts

1
2
3
4
5
6
7
8
9
10
11
12
import { add } from "./add";

describe("add test", () => {
test("1 + 2 should be 3", () => {
// arrange
const a = 1;
const b = 2;
const expected = 3;
// act / assert
expect(add(a, b)).toBe(expected);
});
});

寫完後可以執行jest看看結果如何。

Vue 的 component 測試

component 的測試就沒有上面這麼簡單了,首先我們要搞清楚哪些部份需要撰寫測試、要測試到哪種程度、要如何撰寫...

問題接踵而來,以下一一解決。

該測試哪些部分

關於這部分,其實Vue Test Utils 的文檔有給出很詳細的回答。

他們給出的結論是除了實作細節以外都可以撰寫測試,下面是我把官方文檔這部分翻譯後的內容。

  • 輸入
    • 使用者互動
      • 點擊、輸入等人類操作
    • props
      • component 拿到的參數
    • 資料流
      • api calls 或接受外部訊息等
  • 輸出
    • DOM 元素
      • 有被渲染出來的元素
    • 事件
      • 是否有正確的 emit 出事件
    • 副作用
      • api calls 或是對外部的修改互動等

不測試實作細節是指沒有測試組件的內部函式和資料,只關注於最終組件的表現。這樣的測試在未來組件程式碼有變動時,基本不需要改動,因為內部程式實作改變並不影響最終表現的結果。

該如何撰寫

這部分會用到vue test utils,它可以幫助我們把 vue 的 component mount 起來,還提供 css selector 的方式取得裡面的 dom 元素,並且檢查狀態。

文檔上的例子

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
const Nav = {
template: `
<nav>
<a id="profile" href="/profile">My Profile</a>
<a v-if="admin" id="admin" href="/admin">Admin</a>
</nav>
`,
data() {
return {
admin: false,
};
},
};

test("renders a profile link", () => {
const wrapper = mount(Nav);

// Here we are implicitly asserting that the
// element #profile exists.
const profileLink = wrapper.get("#profile");

expect(profileLink.text()).toEqual("My Profile");
});

toEqual()toBe()的差別可以參考這裡

CI

簡述

CI 是 Continuous Integration 的縮寫,意義為持續整合,通常和 CD 一起出現(持續整合/持續部屬/交付)。CI 透過自動化的流程,維持程式碼的高品質,並搭配 CD 讓開發者在很短的時間內對程式碼做修改且確保程式碼有足夠高的品質可以發布。

市面上有很多工具可以達成 CI/CD 的目的。常見的有 GitHub Actions、GitLab CI/CD pipelines、Travis CI(今年開始沒有免費版了)、Circle CI 等。

之前有看到文章有介紹 Dapper,可以用 Docker 容器自幹 CI/CD。

GitHub Actions

簡述

GitHub Actions 是 GitHub 免費開給開源 repo 的 CI/CD 工具,但是在 private repo 裡面也能夠使用。Free User 每個月有 2000 分鐘的額度,Pro User 則是 3000 分鐘。

下面簡單介紹一下怎麼用 GitHub Actions 對 node 專案做測試。

設定說明

GitHub Actions 的檔案要放在 repo 根目錄的.github/workflows資料夾下。

隨便建個 yml 的檔案,這邊取ci.yml

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
name: CI

on:
push:
branches: [master]

jobs:
build:
runs-on: ubuntu-latest
strategy:
matrix:
node-version: [16]

name: "Build&Test: node-${{ matrix.node_version }}"
steps:
- uses: actions/checkout@v2
- name: Set node version to ${{ matrix.node-version }}
uses: actions/setup-node@v2
with:
node-version: ${{ matrix.node-version }}
cache: "npm"
- name: Install deps
run: npm install
- name: Run unit tests
run: npm run test

最上面的name表示的是這個 workflow 的名稱,點 GitHub Actions 的頁籤就可以看到所有 workflow 的執行結果。

on裡面設定的是 workflow 的觸發條件,push即有人 push 到 repo 的時候觸發,也有pull_requestschedule等可以用。

schedule是一個特別的事件,可以用cron設定觸發的時間 事件清單的官方文件

jobs裡面可以設定要執行的工作,每個設定的工作都會平行執行,如果要指定某個工作的前置工作,可以用needs來指定。

接下來是每個工作的設定。strategy可以調整工作的執行方式,裡面的matrix設定很常用,可以拿來指定工作參數,像是 OS 類型或 node 版本等。利用matrix可以在不同的 OS 和 node 版本下執行測試。

steps用來指定一項工作的步驟,工作在執行時會依序執行每個步驟,如果中間有任何一個步驟失敗,就會中斷執行。

uses可以執行執行別人設定好的工作步驟。actions/checkout@v2是 GitHub 提供的步驟,可以 checkout 到 repo 內,讓 workflow 可以存取。with適用來指定參數的,參數要如何設定要參考提供者給的文件。以這個步驟來說,說明在這裡

run可以執行 shell 指令,像是執行npmrm -rf等都行。

結語

這次只有簡單的玩了一下 CI 和 Unit Test,沒有太過深入的研究,之後會再嘗試導入 CI 到現有的個人專案內。