前端工程師在GitHub上持續整合與部署(CI/CD)

Photo by kazuend on Unsplash


前言

現在的前端工程師需要實現的功能越來越複雜,複雜度越高代表出錯的機率也會跟著提升,那麼…有沒有什麼方法可以改善呢?當然有啦!就是在每次發布新版程式時讓測試程式自動幫我們完成這些重複性極高的工作!

👇 接下來要透過下面幾個步驟實現持續整合與部署的工作 👇

環境設定

請先確認環境中已安裝

  1. Git
  2. Node.js

從範例開始

這裡有一個簡單的網站並附上部分測試程式,建議使用 test-app-starter 來快速體驗整個測試流程,這個專案是用 create-react-app 建立的,但是這篇教學中所有功能在 React, Angular, Vue 皆可以使用。

test-app-starter 點選 Fork 一份到自己的帳號底下操作,步驟上的修改可以參考 test-app-starter 的 demo branch

點選 Fork

GITHUB_USER_NAME = 你的 GitHub 帳號

複製 test-app-starter 到本機

$ git clone https://github.com/<GITHUB_USER_NAME>/test-app-starter.git

切換到 test-app-starter 目錄

$ cd test-app-starter

👋 Windows 環境請先在 test-app-starter 目錄執行底下指令確保可以執行 shell script : 來源

👋 請先確認已安裝 Git

X86

$ npm config set script-shell "C:\\Program Files (x86)\\git\\bin\\bash.exe"

X64

$ npm config set script-shell "C:\\Program Files\\git\\bin\\bash.exe"

安裝依賴套件

$ npm install

啟動 test-app-starter 網站

$ npm start

接著應該可以看到網站在 localhost:3000 上執行

running

Lint test

程式碼風格測試用來統一協作著的撰寫風格,開發者們有一致的撰寫風格可以讓接手的工程師或是協作的工程師更好上手,這個網站已經用 StandardJS 的風格撰寫,所以接下來只需要設定好相關設定就可以進行程式碼風格測試了。

安裝 StandardJS 到專案中

$ npm install --save-dev standard snazzy

package.jsonscripts 增加 lint 的腳本與 standard 的設定

"scripts": {
  "lint": "standard | snazzy"
},
"standard": {
  "ignore": [
    "build/*",
    "registerServiceWorker.js"
  ],
  "env": {
    "browser": true,
    "jest": true
  },
  "parser": "babel-eslint",
  "globals": [
    "actor",
    "Feature",
    "Scenario"
  ]
}

執行 lint 腳本

$ npm run lint

接著應該可以看到…什麼都沒發生 😅,但是這就是風格一致的意思…

沒有消息就是好消息

lint success

所以稍微修改一下 lint 的腳本,讓執行結果明確一點

package.jsonscripts 修改 lint 的腳本

"scripts": {
  "lint": "if standard | snazzy; then echo '💯 Lint perfect'; else echo '⁉️ Lint get error please run `npm run lint` check again';exit 1; fi"
}

再次檢執行

$ npm run lint

執行成功後應該在終端機可以看到 「💯 Lint perfect」

lint success with emoji

展示:Update for lint test

Unit test

單元測試用來測試 function 回傳的資料是否和預期的相同,在開發的時候可以快速檢查多種情況下輸出是否符合預期,在既有的 function 增減功能時也能幫助判斷修改後能否兼容舊的程式。

測試就是最好的文件

當一個專案越來越大有明確的測試才能讓別人清楚知道這個 function 到底在幹嘛 🤔

安裝依賴套件

$ npm install --save-dev enzyme enzyme-adapter-react-16 enzyme-to-json @types/jest 

package.jsonscripts 修改 test 腳本,增加 jest 的設定

"scripts": {
  "test": "if CI=true react-scripts test --coverage --env=jsdom; then echo '✅ Unit test run success'; else echo '❌ Unit test run failure'; exit 1; fi"
},
"jest": {
  "snapshotSerializers": [
    "enzyme-to-json/serializer"
  ],
  "collectCoverageFrom": [
    "src/**/*.js",
    "!src/__tests__/**/*",
    "!src/__e2e__/**/*",
    "!src/(App|index|serviceWorker|setupTests).js"
  ],
  "coverageReporters": [
    "text",
    "lcov"
  ]
}

執行腳本

$ npm run test

執行成功後應該在終端機可以看到 「✅ Unit test run success」

unit success

展示:Update for unit test

Functional test + Visual test

功能測試用來測試網站在操作時能不能如預期執行,例如一個查詢介面,查詢時查詢按鈕變成 disabled ,查詢成功時顯示查詢結果且按鈕變回 enabled 。

視覺測試用來檢查網站跟上一次的樣式有沒有產生變化,這樣可以避面調整某一個 CSS 的時候突然影響到其中一頁沒有預想到的畫面造成畫面不如預期…🤭

安裝依賴套件

$ npm install @applitools/eyes.webdriverio codeceptjs selenium-standalone webdriverio@4.14.1

package.jsonscripts 增加3個腳本

scripts: {
  "functional::local": "npm run start:selenium && codeceptjs run --steps --config=./codecept/local.config.js",
  "install:selenium": "selenium-standalone install",
  "start:selenium": "selenium-standalone start > /dev/null 2>&1 &",
  "kill:selenium": "lsof -t -i :4444 | xargs kill"
}

執行底下指令安裝 selenium 的依賴套件

$ npm run install:selenium

test-app-starter 根目錄新增 codecept 目錄並新增4個檔案

  1. ./codecept/local.config.js

    exports.config = {
      name: 'test-app',
      tests: '../src/__e2e__/src/**/**.js',
      output: '../report',
      helpers: {
        EyesHelper: { require: './helper/eyesHelper.js' },
        WebDriverIO: {
          url: 'http://localhost:3000',
          browser: 'chrome',
          waitForTimeout: 300000
        }
      },
      include: {
        I: './actor/steps_file.js'
      },
      bootstrap: false,
      coloredLogs: true
    }
  2. ./codecept/commonData.js

    const now = new Date()
    const startDate = `${now.getFullYear()}/${now.getMonth() + 1}/${now.getDate()}`
    const startTime = `${now.getHours()}:${now.getMinutes()}:${now.getSeconds()}`
    const {
      PROJECT_NAME = 'Test app',
      SAUCE_USERNAME,
      SAUCE_ACCESS_KEY,
      EYES_KEY,
      BASE_URL = 'https://sky172839465.github.io/test-app',
      TRAVIS_BUILD_NUMBER = `local ${startDate} ${startTime}`,
      TRAVIS_JOB_NUMBER = ''
    } = process.env
    
    module.exports = {
      PROJECT_NAME,
      SAUCE_USERNAME,
      SAUCE_ACCESS_KEY,
      EYES_KEY,
      BASE_URL,
      TRAVIS_BUILD_NUMBER,
      TRAVIS_JOB_NUMBER
    }
  3. ./codecept/actor/steps_file.js

    module.exports = () => {
      return actor({
        say: (message) => {
          console.log(message)
        }
      })
    }
  4. ./codecept/helper/eyesHelper.js

    const codecept = require('codeceptjs')
    const { Eyes, Target } = require('@applitools/eyes.webdriverio')
    const Helper = codecept.helper
    const {
      EYES_KEY,
      PROJECT_NAME
    } = require('../commonData')
    
    class EyesHelper extends Helper {
      constructor (config) {
        super(config)
        this.eyes = new Eyes()
        this.eyes.setApiKey(EYES_KEY)
        this.browser = null
        this.suiteTitle = null
        this.scenarioTitle = null
        this.isNewTest = true
        this.isEyesOpen = false
        this.step = 0
      }
    
      _getBrowser () {
        return this.helpers['WebDriverIO'].browser
      }
    
      _beforeSuite ({ title }) {
        this.suiteTitle = title
      }
    
      _before ({ title }) {
        this.scenarioTitle = title
        this.isNewTest = true
        this.step = 0
      }
    
      _afterSuite () {
        this.suiteTitle = null
      }
    
      async _after () {
        this.scenarioTitle = null
        if (this.isEyesOpen) {
          this.isEyesOpen = false
          try {
            await this.eyes.close()
          } finally {
            await this.eyes.abortIfNotClosed()
          }
        }
      }
    
      async screenShotForVisualTest (appName = `${this.suiteTitle}:${this.scenarioTitle}`, stepName = `Step:${this.step}`) {
        if (this.isNewTest) {
          this.browser = this._getBrowser()
          await this.eyes.open(
            this.browser,
            appName,
            PROJECT_NAME
          )
          this.isEyesOpen = true
          this.isNewTest = false
        }
        await this.eyes.check(stepName, Target.window())
        this.step += 1
      }
    }
    
    module.exports = EyesHelper
  5. local.config.js : 在本機的 codeceptjs 的設定

  6. commonData.js : 放共用的變數

  7. steps_file.js : functional test 中使用的語意 API , I.xxx 擴充功能可以在這邊新增

  8. eyesHelper : 視覺測試 Applitools 的設定,執行 functional test 時如果想將當時的結果截圖下來交給 Applitools 作比較只要用已經改寫成語意API的 I.screenShotForVisualTest() 即可將圖片截下來上傳到 Applitools 的雲端做比較

👋 因為 Applitools 是一個服務,所以需要先到 Applitools 註冊一個帳號取得 EYES_TOKEN 才能上傳到自己的帳號內做比對,建議直接用 GitHub 帳號註冊並登入

applitools register

接著點 My API Key 取得 EYES_TOKEN

applitools token

儲存 EYES_TOKEN

# mac
$ export EYES_KEY=EYES_TOKEN
# windows
$ set EYES_KEY=EYES_TOKEN

👋 確定 localhost:3000 網站有啟動,否則記得先執行 npm start

👋 請確認執行底下腳本的終端機有 EYES_KEY SAUCE_USERNAME SAUCE_ACCESS_KEY 這幾個環境變數,可以用 echo $EYES_KEY 在終端機檢查

$ npm run functional::local

如果遇到 No Java runtime present,請按照上面的說明安裝 Java

執行成功後在終端機應該可以看到

functional success

Applitools 重新整理後應該可以看到 functional test 中,執行3次 I.screenShotForVisualTest() 的截圖,2個 New 以及2個 passed ,因為這是第一次截圖所以這次的截圖被當成基準,接著再跑一次, New 就會消失,代表這次是跟上次的基準比對過的 🎉

第一次截圖是「基準」 第一次截圖是「基準」

第二次截圖會和「基準」比較 第二次截圖會和「基準」比較

展示:Update for functional & visual test

Compatibility test

兼容性測試在前端是最不想面對的一件事 🤐 ,不用兼容是最好的,但是萬一要做到就會變得很麻煩,不同瀏覽器、不同瀏覽器版本、不同作業系統…光用想的就一個頭兩個大 🤯 ,因此我們才需要兼容性測試!

Sauce Labs 是一個雲端服務,提供我們各種作業系統、版本、瀏覽器,我們只需要把「功能測試」和「視覺測試」丟到上面讓他自己跑就能知道自己的瀏覽器兼容到什麼程度了! Sauce Labs 還會幫我們錄下測試的過程喔!

這時會遇到2種狀況 :

  1. 本機開發時網站在 localhost 就可能需要檢驗網站有沒有正確在各種平台執行
  2. 部署到線上後要測試線上的網站有沒有跟本機一樣正常

首先我們先在本機進行測試,需要先到 Sauce Labs 註冊一個帳號,並同意 Sauce Labs 存取帳號權限

👋 記得去申請 Open Sauce 方案,只要把專案標示成 open source 就可以申請了, Open Sauce 的帳號可以獲得沒有限制的使用所有功能

saucelabs register

進入設定頁取得 USER_NAME (USERNAME) 及 SAUCE_TOKEN (Access Key)

saucelabs settings

儲存 USER_NAMESAUCE_TOKEN

# mac
$ export SAUCE_USERNAME=USER_NAME SAUCE_ACCESS_KEY=SAUCE_TOKEN
# windows
$ set SAUCE_USERNAME=USER_NAME
$ set SAUCE_ACCESS_KEY=SAUCE_TOKEN

安裝 Sauce Labs 的依賴套件

$ npm install --save-dev codeceptjs-saucehelper saucelabs

接著我們要寫一些 Sauce Labs 的設定

  1. ./codecept/sauce.config.js

    const {
      PROJECT_NAME,
      SAUCE_USERNAME,
      SAUCE_ACCESS_KEY,
      BASE_URL,
      TRAVIS_JOB_NUMBER
    } = require('./commonData')
    const getBrowserConfig = browserName => ({
      'tunnel-identifier': TRAVIS_JOB_NUMBER,
      name: PROJECT_NAME,
      build: TRAVIS_JOB_NUMBER ? `build-${TRAVIS_JOB_NUMBER}` : `local-${Date.now()}`
    })
    
    exports.config = {
      name: 'test-app',
      tests: '../src/__e2e__/src/**/**.js',
      output: '../report',
      helpers: {
        SauceHelper: { require: './helper/sauceHelper.js' },
        EyesHelper: { require: './helper/eyesHelper.js' },
        WebDriverIO: {
          url: BASE_URL,
          user: SAUCE_USERNAME,
          key: SAUCE_ACCESS_KEY,
          browser: 'chrome',
          desiredCapabilities: {
            platform: 'Windows 10',
            ...getBrowserConfig('Windows Chrome')
          },
          windowSize: 'maximize',
          waitForTimeout: 30000
        }
      },
      include: {
        I: './actor/steps_file.js'
      },
      bootstrap: false,
      coloredLogs: true,
      timeout: 10000,
      smartWait: true
    }

Platform config: https://wiki.saucelabs.com/display/DOCS/Platform+Configurator#/

  1. ./codecept/helper/sauceHelper.js

    const codecept = require('codeceptjs')
    const Helper = codecept.helper
    const SauceLabs = require('saucelabs')
    const {
      SAUCE_USERNAME,
      SAUCE_ACCESS_KEY
    } = require('../commonData')
    const Acct = new SauceLabs({
      username: SAUCE_USERNAME,
      password: SAUCE_ACCESS_KEY
    })
    
    // https://github.com/puneet0191/codeceptjs-saucehelpe
    class SauceHelper extends Helper {
      _updateSauceJob (sessionId, data) {
        const sauceUrl = `⚡️ Test finished. Link to job: https://saucelabs.com/jobs/${sessionId} ⚡️\n\n`
        const {
          platform = 'unknown',
          browserName = 'unknown'
        } = this.helpers['WebDriverIO'].browser.desiredCapabilities
        const newData = {
          ...data,
          name: `(${platform}:${browserName}) ${data.name}`,
          public: 'public'
        }
        console.log(sauceUrl)
        Acct.updateJob(sessionId, newData, this._callback)
      }
    
      _callback (error, response, body) {
        if (!error && response.statusCode === 200) {
          console.log(body)
        }
      }
    
      _passed (test) {
        console.log('Test has Passed')
        const sessionId = this._getSessionId()
        this._updateSauceJob(sessionId, { 'passed': true, 'name': test.title })
      }
    
      _failed (test, error) {
        console.log('Test has failed')
        const sessionId = this._getSessionId()
        this._updateSauceJob(sessionId, { 'passed': false, 'name': test.title })
      }
    
      _getSessionId () {
        if (this.helpers['WebDriver']) {
          return this.helpers['WebDriver'].browser.sessionId
        }
        if (this.helpers['Appium']) {
          return this.helpers['Appium'].browser.sessionId
        }
        if (this.helpers['WebDriverIO']) {
          return this.helpers['WebDriverIO'].browser.requestHandler.sessionID
        }
        throw new Error('No matching helper found. Supported helpers: WebDriver/Appium/WebDriverIO')
      }
    }
    
    module.exports = SauceHelper

再來寫執行這些設定的腳本

package.jsonscripts 增加3個新的腳本

  1. functional::online
  2. functional::online:localhost
  3. start::sauce_connect
    "scripts": {
      "functional::online": "if codeceptjs run --steps --config=./codecept/sauce.config.js; then echo '🎊 Functional test run success'; else echo '💔 Functional test run failure'; exit 1; fi",
      "functional::online:localhost": "export BASE_URL=http://localhost:3000/; npm run functional::online",
      "start::sauce_connect": "bin/sc -u $SAUCE_USERNAME -k $SAUCE_ACCESS_KEY"
    }

最後再下載一個 Sauce Labs 提供的 👉 sauce connect 👈 ,用來在本機起一個連線讓 Sauce Labs 可以透過這個連線來測試你 localhost 的網站 🤩

下載回來的 sauce connect 解壓所後會有一個 bin 資料夾,把 bin 資料夾放到test-app-start 根目錄即可

接著就執行腳本看看結果吧!

先把 sauce connect 的連線起起來

$ npm run start::sauce_connect

等到看到 you may start your tests 的文字,代表連上 Sauce Labs 可以開始測試了,確認網站在 localhost:3000 可以正常運作後執行底下腳本

$ npm run functional::online:localhost

sauce connect 成功連線後在 Sauce Labs 的 Tunnels 上會看到一個連線

sauce connect success

執行成功後在終端機應該可以看到 「🎊 Functional test run success」

注意到這個了嗎 👇👇👇?可以直接連到 Sauce Labs 看測試結果!

「⚡️ Test finished. Link to job: https://saucelabs.com/jobs/36b1290a5c1e4f7ea93d53535bdc32ee ⚡️」

functional result with link

進入連結後可以直接看到測試的過程

functional result in saucelabs

另外可以看到 Sauce Labs 的 Dashboard 上整個測試執行成功

functional result in dashboard

這樣本機連線 Sauce Labs 的測試就完成了,上面提到的狀況2,需要將網站部署到線上才能執行,因此會等到最終設定好持續整合時再進行展示。

展示:Update for compatibility test

Deploy

自動部署最好的地方就是不用擔心少抓一個檔案、多刪一個檔案之類的情況,畢竟眼殘手賤的情況實在太多了…🙄

首先我們需要取得存取 GitHub 的金鑰,這樣 GitHub 才會允許我們對 GitHub Pages 進行修改,這裡有官方產生 GITHUB_TOKEN 的文件可以參考

首先進入產生 GITHUB_TOKEN 的頁面點右上角「Generate new token」

generate github token

接著輸入描述與勾選 repo 選項,描述是為了讓自己知道這個金鑰的功能, repo 是這個 GITHUB_TOKEN 被賦予的存取權限,輸入完成後按底下的「Generate token」

new github token

產生出來的 GITHUB_TOKEN 記得馬上存起來 ✍️,因為之後就看不到了,只能刪除這個 GITHUB_TOKEN 或重新產生一個

record github token

GITHUB_USER_NAME = 你的 GitHub 帳號

GITHUB_REPO_REF = github.com/<GITHUB_USER_NAME>/test-app-starter.git

儲存 GITHUB_TOKENGITHUB_REPO_REF

# mac
$ export GH_TOKEN=GITHUB_TOKEN GH_REF=GITHUB_REPO_REF
# windows
$ set GH_TOKEN=GITHUB_TOKEN GH_REF=GITHUB_REPO_REF

package.json

  1. 新增 homepage 設定
  2. scripts 修改 build 並新增 deploy::prod 腳本
"homepage": "https://<GITHUB_USER_NAME>.github.io/test-app-starter/",
"scripts": {
  "build": "if react-scripts build; then echo '😏 Build success'; else echo '😨 Build failure'; exit 1; fi",
  "deploy::prod": "npm run build && if bash ./ghpage-deploy.sh; then echo '🤗 Deploy success'; else echo '😱 Deploy failure'; exit 1; fi"
}

新增一個檔案到 test-app-starter 根目錄

./ghpage-deploy.sh

👉 <YOUR_EMAIL> 記得替換成自己的信箱 ex: test-app@gmail.com 👈

cd build
git init
git config user.name "Travis CI"
git config user.email "<YOUR_EMAIL>"
git add .
git commit -m "Deploy to GitHub Pages"
git push --force --quiet "https://${GH_TOKEN}@${GH_REF}" master:gh-pages > /dev/null 2>&1

執行後會把現有網站壓縮打包,然後上傳到 GitHub Pages 上,這裡的腳本上傳到 GitHub Pages 的位置就是 <GITHUB_USER_NAME>.github.io/test-app-starter

$ npm run deploy::prod

執行成功後在終端機應該可以看到「🤗 Deploy success」

我的 GITHUB_USER_NAME 是 yusong-demo 所以我的網站就在 👉 https://yusong-demo.github.io/test-app-starter

deploy to gh page

展示:Update for deploy

Continuous integration

終於走到最後一步,持續整合,讓我們迅速解決他吧 😏

Travis CI 是一套持續整合的工具,他可以讓你連結 GitHub 帳號後選擇指定的專案給他掛上一個事件監聽,這個事件監聽可以在 master 分支被 merge 的時候觸發、可以在 PR (pull request)發出來的時候觸發,觸發之後就執行上面一拖拉庫的腳本,這樣我們就可以保證每次發布新版本的時候,該測的東西都測了,這樣上線還有問題就能先推給後端處理了 😈

首先我們要到 Travis CI 註冊帳號,記得要用 GitHub 帳號註冊, Travis CI 才能取得你的帳號並監聽指定的專案

travis ci register

註冊完成後,進入 Travis CI 專案列表的頁面,把 test-app-starter 啟動並點選 test-app-starter 這個選項就會進入這個專案的建置頁面

點選開關啟動這個專案

進入 test-app-starter 專案建置頁面,點Settings , 設定環境變數 (Environment Variables)

點 Settings

需要設定的環境變數即前面步驟有 export / set 的都需要,這邊整理一份給大家核對一下

  • EYES_KEY
  • GH_REF
  • GH_TOKEN
  • SAUCE_ACCESS_KEY
  • SAUCE_USERNAME

最後我們要新增 Travis CI 的設定檔在 test-app-starter 的根目錄底下,他的格式是 yml ,其實這種格式跟 JSON 差不多,不請楚的話上網查一下馬上就瞭解了

./.travis.yml

language: node_js
node_js:
  - stable
addons:
  sauce_connect: true
cache:
  directories:
    - node_modules
install:
  - npm install
script:
  - echo "npm test temporarily disabled"
  - npm run lint
  - npm run test
  - npm run deploy::prod
  - npm run functional::online
true:
  branch: master

把 test-app-starter 所有的變更 commit 並 push 到自己的 GitHub 上,接著 Travis CI 就會因為 master push 觸發,開始執行 yml 中的設定

travis ci running

執行成功後在 Travis CI 的終端機畫面上應該會看到跟這個 demo 一樣的結果

travis ci success

展示:Update for continuous integration

🎉 🍻恭喜你完成了這一系列的測試 🍻 🎉

之後每次我們只要 push 新的 code 到 master branch 就會觸發 Lint test 、 Unit test 、 Functional test 、 Compatibility test 以及部署到線上環境,這樣一來新版網站上線將會因為經過眾多測試的洗禮變得更穩定且更專注於開發上 (理想上 😅)。

Bonus 🎁

那麼…做了這麼多自動測試,難道都只有開發者知道嗎?有沒有什麼辦法讓其他使用者一看就能安心的方式呢?當然有啦!

status banner

compatibility banner

Badge 這是常常可以在熱門 Open source 中看到的圖示,他清楚的點出了這個專案受過哪些測試或是他們符合哪些規範,接下來我們就將上面測試的結果都加上 badge 美化一下 README.md 吧 😋

JavaScript Style Guide

[![JavaScript Style Guide][standard-image]][standard-url]

[standard-image]: https://img.shields.io/badge/code_style-standard-brightgreen.svg
[standard-url]: https://standardjs.com
  • Unit test : Jest 執行完會產生一個覆蓋率的資料夾,只要把裡面的東西上傳到 Codecov 就能在每次 Travis CI 執行完產生對應的 coverage badge !

Coverage Status

[![Coverage Status][codecov-image]][codecov-url]

[codecov-image]: https://img.shields.io/codecov/c/github/<CODECOV_USER_NAME>/test-app.svg
[codecov-url]: https://codecov.io/gh/<CODECOV_USER_NAME>/test-app

安裝 Codecov 依賴套件

$ npm install --save-dev codecov

.travis.ymlscript 修改 npm run test 腳本

script:
  - npm run test && codecov

Codecov 註冊帳號 SignIn Codecov 同意存取 GitHub 權限 Accept Codecov 找到 test-app-starter 專案點下去

Select Repo

取得 CODECOV_TOKEN Get Token 把 CODECOV_TOKEN 存到 Travis CI 裡的 Environment Variables

  • Functional test : Sauce Labs 執行完成後會因為我們在 sauceHelper.js 做了更新Job的動作變成 pass / fail ,如果沒有手動更新跑完不論成功失敗都會是 complete,查看測試結果需要申請 Open Sauce 才能讓所有人瀏覽測試結果

Saucelabs Ststus

[![Saucelabs Ststus][sauce-labs-status-image]][sauce-labs-status-url]

[sauce-labs-status-image]: https://saucelabs.com/buildstatus/<SAUCE_USER_NAME>
[sauce-labs-status-url]: https://saucelabs.com/u/<SAUCE_USER_NAME>
  • Compatibility test : 同樣是 Sauce Labs 提供的 badge ,如果測試多個瀏覽器就可以用這個 badge 清楚表達支援哪些瀏覽器

Saucelab Compatibility

[![Saucelab Compatibility][compatibility-image]][compatibility-url]

[compatibility-image]: https://saucelabs.com/browser-matrix/<SAUCE_USER_NAME>.svg
[compatibility-url]: https://saucelabs.com/u/<SAUCE_USER_NAME>
  • CI status:Travis CI 提供的 badge,有這個測試可以一看就知道最後一次跑的測試有沒有成功

Build Status

[![Build Status][travis-image]][travis-url]

[travis-image]: https://img.shields.io/travis/<TRAVIS_USERNAME>/test-app.svg
[travis-url]: https://travis-ci.org/<TRAVIS_USERNAME>/test-app

A short version 😏

建議是按照整個流程走一次看過文章中設定的程式碼怎麼運作,在使用上比較容易內為己用,但是…我只是想要體驗看看有沒有其他懶人包呢?當然有啦!

test-app 已經把上面的設定完全走過一次,只要改成使用這個專案就只需要把該註冊的網頁註冊,該存的 TOKEN 存到 Travis CI 上面就可以順利執行持續整合了!

設定方式就請直接參考專案上的 Quick Start 吧!

結論

這一整套流程雖然很繁瑣,但是長遠來看可以帶來莫大的好處(不用上線怕得要死),但是…總是有個但是,就是開發人員必須遵守紀律好好的撰寫測試,開發時間都不夠了還要寫測試?隕石開發法寫測試?即使有有各式各樣的為難存在,只要想一下「下班時發現上線的網站有問題,請今日修復」,一股難以言喻的動力就產生了 😉,有時間、有興趣、有需求的工程師們都應該來試試看!

最後…這篇文章如果有什麼寫錯或可以改善的地方歡迎各位留言 🙌

Update 2019.03.10 Unit test failed

現在 NodeJS v11.11.x 在執行時會與 Jest v23 產生衝突導致 Travis CI build failing,因此建議先將 .travis.yml 的 NodeJS 降版,等問題解決後再改回最新版的 NodeJS。

language: node_js
node_js:
  - 11.10.1

Tips:在這個問題中可以點 Subscribe 訂閱這個問題的處理進度,才能在解決時收到 mail 通知喔 😉

issue subscribe

展示:Fix unit test fail

© 2022 YuSong Hsu