# Unit Test (單元測試) -T [toc] - 藉由檢查每一個 function 回傳的物件,可以確定程式是不是有跑在預期的結果裡面 - 可以避免之後改版不小心改動到原本正常的 function - 主要是測試 function 的邏輯 - 固定 data 是否可以得到相同輸出 - 3A - Arrange - 準備測試會用到的 object - Account - 用 object 做一些邏輯得出結果 - Assert - 確認結果是否和預期一樣 - SUT(System Under Test) - 待測的系統,通常是 class - 每個 function 個別測試 - 若有 function 中用了另一個 function (ex. call library),就兩個 function 分開測試 - 降低耦合 : 避免測試 A function 時呼叫 B function,但 B function 是有問題的 - mock vs stub vs spy - stub - 當要驗證的 function 需要第三方的回傳資料(沒有帶參數) - ex. call db data - 就製作假資料取代第三方的回傳資料 - 實際方法 - 可以用物件導向的方式,把會使用到的第三方物件用建構子的方式帶入 class - 測試時再將帶入的第三方物件改為自己設定的 - ![](https://hackmd.io/_uploads/Hyr93P8gp.png) - ![](https://hackmd.io/_uploads/Sk9unP8gp.png) - mock - 當要驗證的 function 需要引入第三方 function 並依照驗證 function <b>帶入的參數</b>做事情 - 偽造第三方 function 並檢查帶入第三方物件的參數是否正確(檢查互動的方式是否正確) - 最後<b>是檢查第三方 function</b> 的資料是否正確 - ex. call function 寫入 log 檔 - ![](https://hackmd.io/_uploads/H19cEoDfp.png) - 原本的第三方物件 - ![](https://hackmd.io/_uploads/HJtUfFLeT.png) - 偽造假的第三方物件 - 偽造的要可以回傳需驗證 function 的 value - stub vs mock - 差別在於驗證原本的物件 or 假的第三方物件 - ![](https://hackmd.io/_uploads/ryRifFIxT.png) - ref - https://hackmd.io/@AlienHackMd/HycK5pEpo - https://ithelp.ithome.com.tw/articles/10263479 - spy - 驗證原本物件對其他物件的改變是否正確 - ![](https://hackmd.io/_uploads/BJ-PIKIgp.png) - ref - https://medium.com/starbugs/unit-test-%E4%B8%AD%E7%9A%84%E6%9B%BF%E8%BA%AB-%E6%90%9E%E4%B8%8D%E6%B8%85%E6%A5%9A%E7%9A%84dummy-stub-spy-mock-fake-94be192d5c46 - 總結 - stub - 偽造 SUT 所需的第三方資料 - 驗證整個 SUT 的回傳結果 - mock - 偽造 SUT 傳入參數的第三方模組 - 驗證偽造的第三方模組收到的資料 - spy - 驗證被 SUT 改動到的模組被改動後的資料 - NodeJS - repo : 可用 `mocha` + `supertest` + `chai` - `index.js` ```js= process.env.NTBA_FIX_319 = 1; const express = require('express') const app = express() const http = require('http'); const server = http.createServer(app); // 要測試的 api app.get('/test', async function (req, res) { res.json({suc:'test suc'}); res.end; return; }); app.get('/test/good', async function (req, res) { res.json({suc:'test good'}); res.end; return; }); server.listen(3001, () => { console.log('listening on *:3001'); }); module.exports = app // 這邊要 export 給 test 的 script ``` - `test.js` ```js= // supertest 可以讓專案不用另外跑起來也可以測試 api,它會幫你跑 const request = require('supertest') const app = require('./index') const { expect } = require("chai") // dedscribe 為一個測試區塊,通常裡面會有多個 it,代表實際要測試的更小的測試區塊 describe('GET /test', () => { it('should respond "test good"', (done) => { request(app) .get('/test/good') .end((err, res) => { const text = res.text; // chai 的語法,讓我們比較好描述要測試的東西 expect(text).to.be.equal('{"suc":"test good"}') done(); }) }), it('should respond "test suc"', (done) => { request(app) .get('/test') .expect(200, '{"suc":"test suc"}', done) }) }) ``` - chai 用法 - ![](https://hackmd.io/_uploads/HJ5LY1-f6.png) - `package.json` 要加上啟動 test ```json= "scripts": { "start": "node index.js", "test": "mocha test --exit " } ``` - `--exit` : 測試完就結束 script,最好都要加 - 開始測試 : `npm test` - ![](https://hackmd.io/_uploads/ByRuX1WfT.png) - gitlab ci - `.gitlab-ci.yaml` ```yml= image: node:latest stages: - build - test - deploy default: tags: - linux before_script: - now_time=$(date) - echo "${now_time}" - echo "started by ${GITLAB_USER_NAME}" - chmod 0777 ./node_modules/.bin/mocha build-job: stage: build script: - echo "running scripts in the build job" - npm install test-job1: stage: test script: - echo "running scripts in the test job 1" - npm test dependencies: - build-job test-job2: stage: test script: - echo "running scripts in the test job 2" deploy-job: stage: deploy script: - echo "running scripts in the deploy job" ``` - pipeline - ![](https://hackmd.io/_uploads/rkkV81bzp.png) - test-job1 - ![](https://hackmd.io/_uploads/HyYLUkbfT.png) - ref - https://medium.com/@stupidcoding/%E5%9C%A8node-js%E5%AF%AB%E6%B8%AC%E8%A9%A6-mocha-chai%E6%96%B7%E8%A8%80%E5%BA%AB-supertest%E6%A8%A1%E6%93%AC%E9%80%A3%E7%B7%9A-sinon%E6%9B%BF%E8%BA%AB-nyc%E7%B5%B1%E8%A8%88%E8%A6%86%E8%93%8B%E7%8E%87-f736c423b893 - https://medium.com/cubemail88/node-js-%E7%94%A8-mocha-%E5%81%9A%E5%96%AE%E5%85%83%E6%B8%AC%E8%A9%A6-16dd9125e632 ### 整合測試 - 整合和其他模組間的測試 - ex. db 和 web application ### end-to-end 測試 - 以客戶角度對整個系統測試 - 可以是人工也可以自動 ### CI/CD - Continuous Integration (持續整合) 與 Continuous Deployment (持續部署) - CI - 系統 環境安裝 + 單元測試 - CD - 系統 環境安裝 + 單元測試 + 自動部屬 - 作用 - push code 之後將剩下作業自動化 - 通常包含:建置(安裝環境)、測試、部屬 - 優點 - 自動化單元測試、部屬 - 可以快速更新新版本(有小問題可以馬上更新),減少環境安裝、部屬時間 - 降低操作錯誤性 - 流程通常會分 3 個 `stage` (也可以有多個) 1. `build` - 環境安裝 - 也可以省略此直接用該環境的 image - ex. `image: lorisleiva/laravel-docker:latest` 2. `test` - 單元測試 - 利用 `dependencies` 取得 `build` 安裝的環境 - 所以可以測試不同環境(ex. php5, php6) 3. `deploy` - 部屬到正式或測試環境 - 只要有其中一個有問題,就不會往下執行 - 建置成功才跑測試,測試成功才做佈署,這樣的先後關係在 GitLab CI 稱之為 `pipeline` - ![](https://hackmd.io/_uploads/r1N5PNIlT.png) - ![](https://hackmd.io/_uploads/B1lpJplMT.png) - 如果不分 `stage` 就會由上而下執行且不管上面有沒有成功下面的還是會執行 - `.yml` - 實際在哪個 stage 要執行那些指令是以 `job` 為單位 - 每個 `job` 都是獨立的 container - 所以如果要 `job` 間要共用一些環境,就要用 `dependencies` - `runner` : `job` 的執行器 - 預設每個 job 的 runner 各自獨立 - 可以用 gitlab 上提供的,也可以用自己架的 - 自己架 - 要提供 ssh 金鑰登入 - gitlab 上提供的 shared runners - settings -> CI/CD - ![](https://hackmd.io/_uploads/r1lYRhgfa.png) - runners -> expand - gitlab 提供的 runner, 每個 runner 都有 tag, 去區分不同環境的 container - ![](https://hackmd.io/_uploads/ryDMJaDMa.png) - ![](https://hackmd.io/_uploads/rk7rkaDzp.png) - `tag` : 指定哪一種類的 runner - `only` : 指定 branch - 通常 `deploy` stage 會指定 master branch(只有 master branch 的 code 會被 deploy 到正式環境) - `before_script` : `job` 執行 `script` 前會執行的指令 - `script` : `job` 真正執行的指令 - ex. - ![](https://hackmd.io/_uploads/HyrVtrIl6.png) - https://ithelp.ithome.com.tw/articles/10187654 - ![](https://hackmd.io/_uploads/HkfHKH8xT.png) - https://nick-chen.medium.com/%E6%95%99%E5%AD%B8-gitlab-ci-%E5%85%A5%E9%96%80%E5%AF%A6%E4%BD%9C-%E8%87%AA%E5%8B%95%E5%8C%96%E9%83%A8%E7%BD%B2%E7%AF%87-ci-cd-%E7%B3%BB%E5%88%97%E5%88%86%E4%BA%AB%E6%96%87-cbb5100a73d4 ```yml= stages: - build - test - deploy default: tags: - windows before_script: - Set-Variable -Name "time" -Value (date -Format "%H:%m") - echo ${time} - echo "started by ${GITLAB_USER_NAME}" build-job: stage: build script: - echo "running scripts in the build job" test-job1: stage: test script: - echo "running scripts in the test job 1" test-job2: stage: test script: - echo "running scripts in the test job 2" deploy-job: stage: deploy script: - echo "running scripts in the deploy job" ``` - https://editor.leonh.space/2022/gitlab-ci/ - 查看此次 commit 的 pipeline 1. 點進 repo 的 branch 2. Build -> Pipelines (此 repo 的所有 branch 的 pipeline 都會在這) - ![](https://hackmd.io/_uploads/r1D7xU8g6.png) - 查看單一 pipeline 中各別 job 執行狀況 1. 點進 repo 的 branch 2. Build -> Jobs - ![](https://hackmd.io/_uploads/BydFRSUga.png) - 點進 job 看 log - ![](https://hackmd.io/_uploads/SJXuRBLeT.png) - ex. nodejs ```yml= image: node:latest stages: - build - test - deploy default: tags: - linux before_script: - now_time=$(date) - echo now_time - echo "started by ${GITLAB_USER_NAME}" build-job: stage: build script: - echo "running scripts in the build job" - npm install - node index.js test-job1: stage: test script: - echo "running scripts in the test job 1" - curl 127.0.0.1:3001 dependencies: - build_job test-job2: stage: test script: - echo "running scripts in the test job 2" deploy-job: stage: deploy script: - echo "running scripts in the deploy job" ``` - ref - https://gitlab.com/gitlab-org/gitlab/-/blob/master/lib/gitlab/ci/templates/Nodejs.gitlab-ci.yml