# 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
- 測試時再將帶入的第三方物件改為自己設定的
- 
- 
- mock
- 當要驗證的 function 需要引入第三方 function 並依照驗證 function <b>帶入的參數</b>做事情
- 偽造第三方 function 並檢查帶入第三方物件的參數是否正確(檢查互動的方式是否正確)
- 最後<b>是檢查第三方 function</b> 的資料是否正確
- ex. call function 寫入 log 檔
- 
- 原本的第三方物件
- 
- 偽造假的第三方物件
- 偽造的要可以回傳需驗證 function 的 value
- stub vs mock
- 差別在於驗證原本的物件 or 假的第三方物件
- 
- ref
- https://hackmd.io/@AlienHackMd/HycK5pEpo
- https://ithelp.ithome.com.tw/articles/10263479
- spy
- 驗證原本物件對其他物件的改變是否正確
- 
- 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 用法
- 
- `package.json` 要加上啟動 test
```json=
"scripts": {
"start": "node index.js",
"test": "mocha test --exit "
}
```
- `--exit` : 測試完就結束 script,最好都要加
- 開始測試 : `npm test`
- 
- 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
- 
- test-job1
- 
- 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`
- 
- 
- 如果不分 `stage` 就會由上而下執行且不管上面有沒有成功下面的還是會執行
- `.yml`
- 實際在哪個 stage 要執行那些指令是以 `job` 為單位
- 每個 `job` 都是獨立的 container
- 所以如果要 `job` 間要共用一些環境,就要用 `dependencies`
- `runner` : `job` 的執行器
- 預設每個 job 的 runner 各自獨立
- 可以用 gitlab 上提供的,也可以用自己架的
- 自己架
- 要提供 ssh 金鑰登入
- gitlab 上提供的 shared runners
- settings -> CI/CD
- 
- runners -> expand
- gitlab 提供的 runner, 每個 runner 都有 tag, 去區分不同環境的 container
- 
- 
- `tag` : 指定哪一種類的 runner
- `only` : 指定 branch
- 通常 `deploy` stage 會指定 master branch(只有 master branch 的 code 會被 deploy 到正式環境)
- `before_script` : `job` 執行 `script` 前會執行的指令
- `script` : `job` 真正執行的指令
- ex.
- 
- https://ithelp.ithome.com.tw/articles/10187654
- 
- 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 都會在這)
- 
- 查看單一 pipeline 中各別 job 執行狀況
1. 點進 repo 的 branch
2. Build -> Jobs
- 
- 點進 job 看 log
- 
- 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