# LSA2:自動測試、大量部屬
[TOC]
https://hackmd.io/@ncnu-opensource/book/%2FF40AXYr2QMOrUD8HO12zTg
---
## Repo
主要在練習 DevOps 實作,包含自動化測試、自動建置 Docker image 與自動部署到遠端主機。
- <a href='https://github.com/Anna0131/DevOps-Practice'>GitHub</a>
---
## 1. 什麼是自動化?
自古以來,人類為了不用大量人力去做重複的事情,開始尋求工具與技術的幫忙,好比說水車,就可以不用一直去打水。或是到現在的智慧駕駛、掃地機器人太多太多,都是為了 **”提升生產力、一致性和效率”** 那人類就可以有更多時間去 **”創新”**。
Q:我們為甚麼需要自動化??


>FIXME:+src
### 自動化的好處
將大量動作交給自動化處理,不需要過多人工去做複雜且同樣的事,並且放便管理,就可以將更多時間放在**創新**上
---
### 自動化的應用範圍
- **軟體開發**(CI/CD、自動測試)
- **製造業**(機械手臂、智慧工廠)
- **IT 運維**(自動部署、伺服器管理)
- **數據處理**(自動爬蟲、AI 分析)
## 2. 軟體開發生命週期
在講開發模型以前,要先了解一下軟體開發生命週期(Software Development Life Cycle,SDLC),週期各階段如下圖所示。

<a href='https://ithelp.ithome.com.tw/articles/10281464'>src</a>
- 由明確定義的不同工作階段組成,有助於系統工程師和系統開發人員利用對系統的設計、建構、測試和交付進行計劃。
- 根據艾略特和斯特拉和雷德福(2004)所說,系統開發生命周期「起源於1960年代,用於在大型企業時代大規模地開發功能性商用系統。這些資訊系統的活動將涉及大規模的資料以及資料處理常式」。
:::info
**Josh 的軟體開發故事**
Josh 是個後端工程師,當 Josh 在收到客戶訂單,客戶想請 Josh 做一個購物網站
第一步:定義問題、分析需求:User Story
第二步:設計和製造產品原型:使用者畫面設計、資料庫設計、工具選擇
第三步:軟體開發:Node.js、python:依照一二步開始執行
第四步:測試-單元測試、自動化測試
第五步:部屬-自動化部署,將開發結果上線
第六步:維運-監控軟體,當報錯時進行修復與更新。
:::
## 3. 軟體開發模型
### 瀑布式開發

<a href='https://ithelp.ithome.com.tw/articles/10281464'>src</a>
是最初的開發模型,當時的年代發展沒有現在這麼快速,能夠長時間的規劃開發,每一步做到全面且詳細。
- 優點
1. 通常完成度非常高
1. 瀑布模型適用於需求明確定義且非常好理解的專案。
- 缺點
1. 因為開發時間長,所以假如最終失敗了所損失的成本非常高。
1. 由於開發模型是線性的,客戶只有等到整個專案的開發末期才會看到開發成果,因而增加了開發風險。
1. 由上一個缺點衍生出另一個更明顯的缺點,專案無法適應客戶需求的變化。
- 例子
1. IBM
2. NASA
3. Boeing
:::info
但發展越變越快,過去的開發模型無法應付,而延伸出了..
:::
### 敏捷開發(Agile Development)

<a href='https://ithelp.ithome.com.tw/articles/10281464'>src</a>
敏捷開發是基於傳統、有著更嚴格的軟體開發方法。強調**團隊合作**而非獨自完成。重視**與客戶之間的溝通**
- 優點
1. 持續交付:快速不斷釋出版本,測試市場反應,來進行即時調整與修改
1. 客戶協作:客戶參與公司會議,提供即時回覆,更確保產品符合需求
1. 實際例子:Spotify、Airbnb、Salesforce
- 缺點
測試者必須即時在開發團隊以及維運團隊間變換角色。開發與測試之間出現很大的隔閡。
#### Scrum

<a href='https://soldevelo.com/blog/is-agile-always-the-best-solution-for-software-development-projects/'>src</a>
- 經常和Agile提起的就是Scrum,Scrum可以視為Agile的框架。
- 兩週為一個Sprint (衝刺)
- 每日站立會議 : 專案成員報告昨天對Story做了那些調整、今日對Story做什麼事、哪些項目遇到困難、需要什麼協助。會議不論人數,最長30分鐘內結束。
程式會持續性的整合到主要分支(CI),讓專案保持在最新的狀態
- Srum 角色
- Product Owner:專案負責人,負責開啟專案、主導規劃劇本、管理專案。
- Scrum Master:是最了解敏捷模型的,也可以說他是教練,負責協調溝通、了解衝刺期間發生的阻礙原因,並調理排解,不參與開發。
- Development Team:參與開發的團隊,理論上以能執行專案的最小團隊編制,混合各種類型專長的人才,例如:程式設計師、UI/UX、QA…。
:::info
雖然開發測試流程加快,但當遇到問題還是得回到上一步,所以在人力部屬上始終趕不上整個速度。開發與維運團隊之間也出現了溝通上的隔閡,所以延伸出了...
:::
## 4.DevOps
<!--
想以一個完整開發流程為例子講解 DevOps

-->
- 當時CICD的想發已經逐漸發酵,剛好將敏捷開發的缺點補足

<a href='https://ithelp.ithome.com.tw/articles/10184557'>src</a>
### DevOps 簡介
- DevOps 是一種結合 文化、實務與工具 的方法,旨在提升組織的開發與運維效率,加速應用程式和服務的交付,讓企業能更快適應市場變化並提升競爭力。
### DevOps 運作方式
- 開發與維運團隊 打破傳統隔閡,共同負責應用程式的開發、測試、部署與維運。
- **自動化** 取代手動程序,提高效率與穩定性。
- 強調 **持續整合(CI)與持續交付(CD)**,確保每次變更可快速、安全地部署。
---
### CI/CD
CI/CD 代表持續整合和持續交付/部署,是在簡化和加速軟體開發生命週期,並且大量使用自動化。
CI/CD 能夠將建置好的程式碼進行自動測試、自動交付、自動部屬,大大減少了人力成本,讓開發者能夠更專注於開發,也讓產品保持相同品質。

<a href='https://www.explainthis.io/zh-hant/swe/cicd'>src</a>
- CICD不是一種工具 ! 是一種模式,為了要能夠一直整合一直部屬,所以有很多的工具協助我們達到目的,ex:GitLab、GitHub、Docker、Grafana 等等
- 例子

<a href='https://ithelp.ithome.com.tw/articles/10219083'>src</a>
>開發者將撰寫好的程式碼 push 上 GitLab,將程式更新至 Server 的步驟交由 Jenkins 負責部屬,當 Jenkins 或者 ELK、Grafana 等監控工具發現異常時,可即時傳送至 Telegram 等通訊軟體作為提醒。
---
#### CI(Continuos Integration)
持續整合 所謂的**整合**,是指開發者一直將程式碼 push 到主要的 branch 上,每一次的 push 都會自動執行 Build & Test,確保功能皆可正常運行,這樣也可以及早發現問題。

3.「Source Control」
版本控制(也稱為原始碼控制)是追蹤和管理軟體程式碼變更,可以得知版本之間的差異、修改的內容還有當版本控制做好可以自由切換版本。
- 版本控制工具:Git、SVN、CVS
2.「Build」
開發人員在每一次的 Commit & Push 後,都能夠於統一的環境自動 Build 程式,透過此一步驟可以避免每個開發人員因本機的環境&套件版本不相同,造成 Service 異常。
- Build Automation:Compile
3.「Test」
CI 的自動化測試是為了早點發現在整合進主要的 branch 的時候,會不會有什麼衝突?本身程式有沒有錯誤,以快速為主
- 測試方式:單元測試、整合測試
---
#### CD(Continuous Delivery/Deployment)

<a href='https://www.atlassian.com/continuous-delivery/principles/continuous-integration-vs-delivery-vs-deployment'>src</a>
#### Continuous Delivery
- 整合 (CI) 完之後,要確定開發完的東西可以被部屬,所以需要再經過測試(端對端測試、整合測試),這個步驟會交由 QA(Quality Assurance) 工程師來進行測試,確定沒問題之後,就要安排什麼時候發布。通常由技術長做最終的發布。
#### Continuous Deployment
- 以自動化方式,頻繁而且持續性的,將軟體部署到生產環境中,使軟體產品能夠快速的發展。
#### QA (Quality Assurance)
- SIT (System Integration Test):都弄進來一個相對穩定的環境,可能是main會持續部署到的地方,就是看各個系統整合起來是不是ok的,可能是BD、server等
- UAT (User Acceptence Testing):是以使用者的角度,可能是長官或是技術長或是客戶來進行測試,實際看說開發出的軟體是否是客戶需要的。
## 5. 自動化測試(單元測試、整合測試)
### 為什麼要寫測試?
自動化測試可快速重複執行**回歸測試(regression test)**,幫助開發者**確保改動不會破壞現有功能**,節省手動測試的時間和精力。還可以整合測試到 CI/CD 流程:將「測試全過」作為部署的條件,能促進團隊維護測試,並確保軟體的穩定性。
> 回歸測試(regression test)是軟體測試的一種,用於檢驗軟體原有功能在修改後是否保持完整。
### 測試替身(Test Double)
測試替身能幫助我們:
- **用來減少外部依賴第三方系統**
- 隔離要測的程式碼(避免測到不該測的東西)
- 驗證系統內部的互動是否正確
- 當要測試的函式依賴某個第三方的回傳資料,例如:
- 從資料庫讀取數據
- 取得外部 API 回應
- 依賴某個系統內部的服務
- 測試若直接依賴外部服務,可能讓整體變得:
1. 影響測試效率(ex: 每次測試都連接 DB,會變慢)
2. 難以控制(ex: DB 內的數據不確定)
3. 有副作用(ex: 可能會寫入 DB,影響正式環境)
- 所以用測試替身來「取代這些外部元件」,讓測試變得:
- 更快:因為用假的東西來回應,不會真的去存資料或發送 API。
- 更準:因為可以控制輸入輸出,確保測的是目標 function 而不是別的邏輯。
- 更安全:不會因為測試誤動正式資料。
#### 常見測試替身(Stub、Mock、Spy)
- Stub
- 當要驗證的 function 需要第三方的回傳資料(沒有帶參數),就製作假資料取代第三方的回傳資料
- ex. call DB data
- 實際方法
- 用 dependency injection 的寫法,把會使用到的第三方物件帶入 class
- 測試時再將帶入的第三方物件改為自己設定的
- 
- 
:::info
**Dependency Injection**(依賴注入):
是一種程式設計模式,用來將物件所依賴的資源(例如:服務、資料庫、API 等)從外部傳進來,而不是在物件裡面自己建立。
可降低「耦合度」,且會更容易測試(ex:在測試時注入 Stub 或 Mock 來取代真實物件。
:::
- Mock
- 當你寫測試時,不只是想看函式的結果,還想驗證它有沒有正確地與別人互動(call API、寫 log、存資料),這時候就會用到 Mock。
- 當要驗證的 function 需要引入第三方 function,並依照驗證 function <b>帶入的參數</b>做事情
- 偽造第三方 function 並檢查帶入第三方物件的參數是否正確(檢查互動的方式是否正確)
- 最後<b>是檢查第三方 function</b> 的資料是否正確
- ex. call function 寫入 log 檔
- 
- 原本的第三方物件
- 
- 偽造假的第三方物件
- 偽造的要可以回傳需驗證 function 的 value
> **什麼時候 不適合 mock 資料庫操作(CRUD)?**
> - 資料庫 schema(欄位結構)會常變動的時候。因為你 mock 的資料格式是「寫死的」,但資料庫結構變了,mock 卻沒跟著變。這樣測試會「假的通過」,
> - 想測試資料跟資料之間的關聯性(關聯表、JOIN、外鍵),mock 無法模擬資料表之間的關聯邏輯(例如 join 的效能、外鍵限制),這要靠真實資料庫才測得出來。
> 如果你的資料表結構(也就是 SQL schema)可能會改,比如新增欄位、改欄位名稱、調整資料型別,那你寫死的 mock 資料可能就會「過時」或「錯誤」。
- Spy
- 用來驗證某個物件(SUT)在執行後,是否有「改變其他物件」的狀態,也就是驗證是否有產生預期的副作用(side effect)。
:::info
**什麼是 SUT?**
全名是 System Under Test,也就是「正在被測試的系統」。
通常是你寫的某個 class、某個函式,它是這次測試的主角。
測試寫得好不好,就是看你能不能把 SUT 的邏輯測得又準又穩。
**什麼是 side effect?**
當你呼叫一個 function,除了它原本該做的事之外,還偷偷去改變了其他東西,那就叫做 side effect。
side effect 在測試裡很重要,因為:
• 如果你沒有控制好,測試結果可能不準確
• 它可能會改資料庫、寫 log、發 API、改全域變數…
:::
- 範例:用 Spy 觀察 log 行為
```js=
// 假設這是我們要測的主程式
function notifyUser(logger) {
logger.log('User has been notified!');
}
```
- 測試程式(使用 Jest 框架內建的 spy 功能)
```js=
test('should call logger.log with correct message', () => {
// 建立一個假的 logger 並 spy 它的 log 方法
const logger = {
log: jest.fn()
};
// 呼叫主程式
notifyUser(logger);
// 驗證 logger.log 有沒有被呼叫
expect(logger.log).toHaveBeenCalled();
// 驗證 logger.log 被呼叫時帶的參數是否正確
expect(logger.log).toHaveBeenCalledWith('User has been notified!');
});
```
> 上面示範了用 Spy 觀察某個 function 有沒有被呼叫,以及它被怎麼呼叫的。
這種方式在單元測試中很常見,特別是我們想知道「這段程式有沒有做某件事」,而不是看它的 return 值。
- 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
<!--
FIX:補程式碼
-->
#### 總結(Stub vs Mock vs Spy)
| 類型 | 功能 | 什麼時候用 |
|------|------|-------------|
| **Stub** | 提供預設回傳值,不驗證有沒有被呼叫 | 當你只想控制資料輸入輸出,不管互動 |
| **Mock** | 提供回傳值,同時也驗證方法有沒有被呼叫、呼叫幾次、用什麼參數 | 當你想測行為,例如:某 function 是否真的呼叫了某服務 |
| **Spy** | 觀察原本的方法是否被呼叫,但不改變原來邏輯 | 當你想追蹤某 function 被怎麼用,但保留原本功能 |
- ref
- https://hackmd.io/@AlienHackMd/HycK5pEpo
- https://ithelp.ithome.com.tw/articles/10263479
- https://www.google.com/search?q=vue+%E5%96%AE%E5%85%83+%E6%95%B4%E5%90%88+%E7%AB%AF%E5%B0%8D%E7%AB%AF%E6%B8%AC%E8%A9%A6&rlz=1C1CHZN_zh-TWTW1057TW1057&oq=vue+%E5%96%AE%E5%85%83+%E6%95%B4%E5%90%88+%E7%AB%AF%E5%B0%8D%E7%AB%AF%E6%B8%AC%E8%A9%A6&gs_lcrp=EgZjaHJvbWUyBggAEEUYOTIHCAEQIRigATIHCAIQIRigATIHCAMQIRigAdIBCDk4OTNqMGo3qAIAsAIA&sourceid=chrome&ie=UTF-8
---
### 軟體測試-方法

#### 靜態測試(Static Testing)
不執行程式,只檢查程式碼或文件,找出潛在錯誤。
- 程式碼檢視(Code Review):
- 團隊成員手動檢查程式碼,找出錯誤或最佳化。
- 檢查(Inspection):
- 由專家或工具檢查程式碼是否符合標準,找出問題。
- 靜態分析(Static Analysis):
- 使用工具(如 SonarQube)分析程式碼的語法、結構,找出潛在錯誤。(也可以由開發團隊自訂驗證的規範)
#### 動態測試(Dynamic Testing)
執行程式,測試行為是否正確。
1. 黑箱測試(Black-box Testing)
不看程式碼,只測試輸入與輸出是否符合預期。
- Decision Table(決策表測試):
- 用表格列出輸入情境與預期輸出,確保所有情境都被測試。
- Equivalence Class Testing(等價類測試):
- 將輸入分成幾類,每類選一個代表值測試。
- Boundary Values Testing(邊界值測試):
- 測試邊界條件,例如最大值、最小值,找出潛在錯誤。
2. 白箱測試(White-box Testing)
看程式碼結構,測試內部邏輯是否正確。
- Control Flow(控制流程測試):測試程式中的 if、for、while 等控制流程,確保所有邏輯分支都有被執行。
- Line Coverage(行覆蓋率):測試是否每一行程式碼都被執行過。
- Branch Coverage(分支覆蓋率):測試 if-else 等條件分支是否都被執行過。
- Data Flow(資料流測試):追蹤變數的 宣告、賦值、使用,確保資料的正確性,避免變數未初始化或不合理的資料操作。
#### Validation(驗證) vs Verification(確認)
- Validation(驗證):確保**做對的事情**,也就是確認軟體能真正解決客戶的需求。
> ex: 一個購物網站應該能讓用戶順利購買商品,而不是只符合規格但無法使用。
- Verification(確認):確保**把事情做對**,也就是檢查軟體是否按照規格或設計文件來實作。
> ex: 開發人員要確認功能是否符合需求文件,即使功能實作正確,但如果不是客戶真正需要的,仍然可能無法通過驗證。
#### 開發模式
- TDD(Test-Driven Development):先寫測試,再根據測試規格開發程式,以確保功能正確。
- 在 TDD 裡,我們會寫測試像這樣:`expect(add(1, 2)).toBe(3);`
- BDD(Behavior-Driven Development):是在 TDD 基礎上,強調用自然語言描述需求,使測試更貼近使用者行為。
- 但 BDD 就會變成這樣寫(通常配合像 Cucumber 或 Jest BDD 這類工具):
```
Given 我有兩個數字 1 跟 2
When 我把它們相加
Then 我應該得到 3
```
<!--
> FIX:詳細一點 自然語言 demo差別
-->
#### 單一職責(Single Responsibility)
在軟體測試裡,「單一職責」的意思就是:一個測試只應該測一件事。
這樣做的好處是:
- 測試失敗時,你能馬上知道是哪個功能出問題
- 測試邏輯簡單清楚、不容易牽一髮動全身
- 維護起來輕鬆,debug 時也不會混淆
### Node Test Runner
#### 介紹
- Node.js 在過去很長一段時間都沒有 Node 自己開發的測試框架,所以都要用第三方的框架,例如 Jest or Mocha
- 這會有一些潛在問題,例如若有一些嚴重的 bug 發生,相對來說修補的速度可能會比較慢
- 所以在 2022 年,node 有了自己開發的測試框架 <a href='https://nodejs.org/api/test.html'>Node Test Runner</a>
- node v18 以上可以使用
#### 範例
- 有一 formatter 程式,將 bytes 轉換成 format 後的格式 :
```js=
function formatFileSize(sizeBytes) {
if (sizeBytes === 0) {
return '0B';
}
const sizeName = ['B', 'KB', 'MB', 'GB', 'TB'];
const i = Math.floor(Math.log(sizeBytes) / Math.log(1024));
const p = Math.pow(1024, i);
const s = (sizeBytes / p).toFixed(2);
return `${s} ${sizeName[i]}`;
}
export { formatFileSize };
```
- 測試 formater 是否正確:
- `vi tests/formatter.js`
```js=
import { formatFileSize } from "../formatter.js";
import { describe, it } from "node:test";
import assert from "node:assert";
describe("formatFileSize function", () => {
it("should return '0B' for sizeBytes = 0", () => {
assert.strictEqual(formatFileSize(0), '0B');
});
it("should return '1.00 MB' for sizeBytes = 1048576", () => {
assert.strictEqual(formatFileSize(1048576), '1.00 MB');
});
it("should return '1.00 GB' for sizeBytes = 1073741824 @large", () => {
assert.strictEqual(formatFileSize(1073741824), '1.00 GB');
});
it("should return '5.00 GB' for sizeBytes = 5368709120 @large", () => {
assert.strictEqual(formatFileSize(5368709120), '5.00 GB');
});
});
```
- `node --test`
- 測試結果報告
```=
▶ formatFileSize function
✔ should return "1.00 GB" for sizeBytes = 1073741824 (0.770195ms)
▶ formatFileSize function (4.013289ms)
ℹ tests 1
ℹ suites 1
ℹ pass 1
ℹ fail 0
ℹ cancelled 0
ℹ skipped 0
ℹ todo 0
ℹ duration_ms 112.371317
```
- This command prompts Node.js to search for files that fit specific patterns from the current directory downwards:
- Files with .js, .cjs, or .mjs extensions located in the test or tests directories.
- Any file named "test," regardless of extension, e.g., test.js.
- Files beginning with "test-", e.g., test-feature.cjs.
- Files ending with ".test", "-test", or "_test", e.g., example.test.js, example-test.cjs, example_test.mjs.
#### 3A 原則
- Arrange
- 準備測試會用到的 object 和環境
- Account
- 用 object 執行測試操作(例如呼叫函式),處理一些邏輯得出結果
- Assert
- 確認結果是否和預期一樣
#### Test Coverage
- 測試在每一個程式檔案的覆蓋率,可以很直觀的觀察有哪部分的邏輯有測試到
- 雖然覆蓋率理論上是越高越可以確保整個專案是正確的,但是當專案太大,很難覆蓋到所有的邏輯,這時候就可以選擇先針對價值比較高的邏輯寫測試
- 使用方式
- `node --test` 加上 `--experimental-test-coverage`
- Test Coverage 報告範例
- 
- line %: Percentage of lines executed by tests
- branch %: Percentage of branches (e.g. if/else logic) covered
- funcs %: Percentage of functions covered
- uncovered lines: Specific line numbers that were not executed during testing
#### Ref
- https://betterstack.com/community/guides/testing/nodejs-test-runner/#step-9-measuring-code-coverage
- https://nodejs.org/api/test.html#test-runner
---
### 單元測試
- 針對各個單元(unit)做測試(測 **Model 和 Module**),需注意**顆粒度**
> 單元的定義:沒有依賴其他模組的最小單元(ex:加減乘除)
- 單元測試的目標是「驗證對外公開的功能是不是對的」,而不是去管你內部怎麼寫出來的。
- 建議測試有抽象過後,會被外部呼叫(使用者或其他程式會用的介面)的 function、class、method
- 不建議測包在內部的實作細節(例如只是為了幫忙完成功能的小工具函式,ex:格式轉換),因為容易因 refactor 改變,導致需重新寫測試
- 藉由檢查每一個 function 回傳的物件,可以確定程式是不是有跑在預期的結果裡面
- 可以避免之後改版不小心改動到原本正常的 function
- 主要是測試 function 的邏輯
- 降低耦合 : 避免測試 A function 時呼叫 B function,但 B function 是有問題的
- [單元測試 code 實作](https://github.com/Anna0131/DevOps-Practice/blob/main/backend/tests/unit/auth.test.js)
### 整合測試
- 概念
- 整合測試是測試多個模組或元件整合後的行為是否正確,目的是驗證它們在整體流程中能否協同運作。
- 因為也有可能單元測試驗證結果通過,但整合測試驗證結果是失敗的情況
- [GIF](https://media.giphy.com/media/111Z8z6B8226vm/giphy.gif)
- 特點
* 針對模組之間的資料傳遞與邏輯串接進行測試。
* 常見於跨系統流程、API 呼叫、資料庫操作等場景。
* 測試的是「功能流程」而不是單一函式邏輯。
> ex:
> 訂單流程中,驗證「選商品 → 加入購物車 → 結帳 → 扣庫存 → 寄送通知」整體能否串接成功。
>
> 常見問題:
> - 參數格式不一致
> - 資料同步延遲
> - 系統間耦合過高導致錯誤難以追蹤
- [整合測試 code 實作](https://github.com/Anna0131/DevOps-Practice/blob/main/backend/tests/integration/integration.test.js)
### 端對端測試
- 概念
- aka e2e test
- 模擬並測試完整的流程,確保各個部分(前端、後端、資料庫)都能順利運作。
> 目標是模擬真實使用者操作,例如:
> - 在表單輸入資料並提交。
> - 按下按鈕導航到新頁面。
> - 測試購物車功能是否正常。
- 工具舉例:playwright
- 特色
- Trace viewer 動作錄製
- 在網頁上的所有操作行為, playwright 都能幫你錄製,當你按下錄製按鈕之後playwright會將你在網頁上的每一個操作步驟都寫成程式碼,完全不用自己動手
- 測試報告
- 會依照test case和瀏覽器逐一生成測試報告,讓測試結果一目瞭然,後方也會列出執行秒數
#### 端對端測試 vs 單元測試
| 測試類型 | 目的 | 適用範圍 |
|---|---|---|
| **單元測試(Unit Test)** | 測試單一函式或元件 | 按鈕點擊是否執行預期函式 |
| **端對端測試(E2E Test)** | 測試完整的使用者操作流程 | 測試登入、購物車、表單提交 |
- ref
- https://medium.com/coding-hot-pot/e2e-test%E7%9A%84%E6%98%8E%E6%97%A5%E4%B9%8B%E6%98%9F-playwright-de85659b56b9
## 6.負載測試
### 目的
- 驗證系統在不同壓力條件下的穩定性、反應速度與容錯能力。
- 確保系統能承受實際使用場景下的流量與使用模式。
### 種類
1. Smoke Testing(煙霧測試):用最小壓力快速檢查系統是否能正常啟動與運作。
> 這裡「最小壓力」指的是:
> - 只用極少的虛擬使用者(VUs)或請求量,來測試系統是否「能跑得起來」。不追求效能或高流量,只驗證系統「基本功能是否正常」。
>
> 為什麼叫 Smoke Testing?
> 這個概念來自硬體界常說:「只要打開設備,沒冒煙,就算基本 OK」。
> 在軟體世界中也一樣,只要系統跑得起來、基本功能不報錯,就算 Smoke Test 通過。
1. Load Testing(負載測試):模擬正常或尖峰使用情境,驗證系統在高併發下的效能表現。
1. Stress Testing(壓力測試):測試系統最多能撐到什麼程度才掛掉
模擬極端情況,**不斷加壓**直到系統撐不住,再觀察系統爆掉時的行為與反應、能不能 gracefully fail、快速恢復(recovery),以及哪個部分是瓶頸
1. Spike Testing(尖峰衝擊測試):模擬瞬間暴增的流量,觀察系統是否能瞬時吸收高壓。
**Spike Testing vs. Stress Testing**
| 類型 | 特徵 | 壓力型態 | 測試目的 |
|-------------------|------------------------------|------------------|-----------------------------------|
| Stress Testing| **逐步增加**直到系統崩潰 | 持續增加直到極限 | 找出系統最大承載量與崩潰點 |
| Spike Testing | **瞬間暴增 → 馬上掉回正常** | 短時間爆量 | 測系統是否能瞬間抗壓、快速恢復 |
1. Soak Testing(浸泡測試):長時間穩定流量下運行,檢查記憶體洩漏、資源枯竭等長期效應。
### k6
k6 是一套專門拿來做效能測試的開源工具,可以幫我們測系統在「實際使用下」的表現,像是反應時間、失敗率、負載能不能撐住。
> Using k6, you can test the reliability and performance of your application and infrastructure.
- Install
- `sudo snap install k6`
- Options
用來修改測試執行的設定參數
可彈性從多個注入點設定,優先順序由大到小為:
1. 命令列參數(command-line flags)
2. 環境變數(environment variables)
3. 測試程式(exported script options)
4. 組檔(configfile)
5. 預設值(defaults)
常見的有 vus(模擬同時幾個使用者(虛擬用戶))、Duration(測多久)、Iterations(總共跑幾次) 等等
- Threshold(門檻值):可以**設定測試要達到什麼條件才算通過**
- 語法格式: `<aggregation_method> <operator> <value>`
ex: 這裡設定了兩個條件,錯誤率要小於 1%,95% 的請求回應時間要低於 200ms。
```js=
export const options = {
thresholds: {
http_req_failed: ['rate<0.01'], // http request 失敗的總比例要低於 1% 才能算通過
http_req_duration: ['p(95)<200'], // 95% 的 requests 數回傳時間都要低於 200ms 以內才算通過
},
};
```
- 指標
- 當我們執行一個測試後,k6 會回傳一份指標報告,裡面會有很多重點指標
- `http_req_receiving` : API **從 server 回應** 所花費的時間
- `http_req_sending` : API **發送到 server** 所花費的時間
- `http_req_waiting` : API **從 server 等待回應** 所花費的時間
- `http_req_duration` : API **請求的總時間**
- 
- (`http_req_sending` + `http_req_waiting` + `http_req_receiving`)
- `http_req_failed` : API **失敗的機率**(預設 status code 是 200,若有特別判斷可使用 expectedStatuses)
- `iteration_duration` : **執行一次腳本所花費的時間**
- `http_reqs` : 計算測試總共發了多少 request
- ex:
- 
> 以這張圖為例,測試結果畫面中秀出每一個指標的平均值、最大值、p90、p95 等等。這些數字可以直接對照設定的 threshold 看有沒有超標。
- [負載測試 k6 code 實作](https://github.com/Anna0131/DevOps-Practice/blob/main/backend/tests/load/test.js)
- ref
- https://grafana.com/docs/k6/latest/using-k6/thresholds/
- https://blog.darkthread.net/blog/k6-load-testing/
## 7. 自動化部署
:::info
自動化是指通過技術手段自動執行需要人工操作的任務。為了提高效率、減少錯誤並釋放人力資源。
部署是指將軟體應用程式或系統從開發環境移動到生產環境的過程。這包括確保應用運行順利、處理配置和依賴項、並將代碼實際執行在伺服器或雲端上。
- Pipeline
- 由於DevOps 沒有固定模式
- 測試部屬跟自動化的工具組合很多變
- 根據公司自己的需求選擇工具
- 哩哩摳摳
- Kubernetes、jenkins、skaffold、grafana
- 主要介紹
- Docker、Ansible、Terraform
:::
### Docker
#### 為甚麼需要 Docker

<a href='[https://ithelp.ithome.com.tw/articles/10281464](https://www.youtube.com/watch?v=mPquwpxyUQU&t=116s)'>src</a>
::: info
在傳統的軟體開發與部署過程中,開發人員常常會遇到「環境不一致」的問題,導致應用程式在不同的設備上可能會出現無法運行的情況,為了模擬完全相同的開發環境,我們通常會想到使用VM,但是每個 VM 需要運行一個完整的作業系統與硬體設備,這導致大量 CPU、記憶體與硬碟資源的消耗。
容器的概念可以追溯到作業系統層級的虛擬化,其目標是在單一作業系統核心上隔離不同的應用程式,在隔離的環境中運行,擁有自己的資源分配,從而避免資源耗損,提高整體效能。
Docker 作為一種容器技術,能夠有效解決這些問題,Docker 提供了一個標準化的容器格式,提供一個**輕量級、可攜帶、獨立的運行環境**,確保應用程式可以在任何環境下穩定運行,而不受系統配置影響,Docker也提供了一個用於分享和管理容器映像檔的平台 Docker Hub。
:::
---
#### image
Docker 映像檔就是一個唯讀的模板,執行程式所需要的所有依賴元件都會包在裡面。
例如:一個映像檔可以包含一個完整的 ubuntu 作業系統環境,然後看我的軟體需要哪些需求就安裝哪些東西。
當你建立 Docker 映像檔(Image)時,它會包含:
1. 作業系統基礎層(Base Image):如 Ubuntu、Alpine。
- 舉例
- 超輕量化, `alpine`(適合 CI/CD、快速部署)
- Python 應用, `python:3.11-slim`(精簡但完整)
- Node.js 應用,`node:20`(完整的 Node 環境)
2. 應用程式(Application):你的 Python、Node.js 等程式碼。
3. 相依套件(Dependencies):應用程式需要的 library,如 `pip install flask` 或 `npm install express`。
4. 設定檔與環境變數(Config & Env Variables):如 `.env`、`config.json` 等,用來設定應用程式參數。
**特點**:
- 唯讀(Read-only)
- 映像檔本身不會改變,所有的變更都發生在容器內。
- 可重複使用
- 相同的映像檔可以在不同環境執行,確保一致性。
- 可分享
- 映像檔可以上傳到 **Docker Hub、AWS ECR、GCR** 等儲存庫,讓團隊成員下載使用。
#### Container
Docker 利用容器來執行應用,是從映像檔建立的**執行實例(Running Instance)**。
(也就是image的實體化)
一個容器主要是用來**運行單一應用或服務**(一個網站上的功能,資料庫,API Server),確保應用能夠在一個隔離的、可攜帶的環境中運行。容器能夠保證應用在不同的環境中都能夠一致地運行
容器是從映像檔建立的執行實例。它可以被啟動、開始、停止、刪除。每個容器都是相互隔離的、保證安全的平台。
**特點**:
- 可寫入(Writable):容器可以對應用程式進行變更(如儲存日誌、資料),但這些變更不會影響原始映像檔。
- 無狀態(Stateless):表示當容器被刪除或重新啟動時,所有**內部的資料都會消失**,
- **一次性運行,一次性刪除**,每次部署新版本時,舊的容器會被銷毀,新的容器會重新建立。
- 可使用Docker Volume與容器分離,容器刪除後 Volume 仍然存在。
- 獨立環境(Isolated):每個容器都有自己的 **檔案系統、處理程序、網路**,確保不同應用程式互不影響。
- 輕量(Lightweight):不同於 VM,容器**共用主機的核心**,不需要額外的 OS,啟動速度快。
#### Dockerfile
:::info
Dockerfile 是一個文字檔案,將 Dockerfile 視為建立 Docker 映像檔的「藍圖」或「腳本」,其中包含了一系列指令,在構建過程中,Docker 會按照 Dockerfile 中的指令一步步執行,並生成一個映像檔(Docker Image)。
Dockerfile也可以納入版本控制系統(例如 Git)中,方便追蹤和管理映像檔的變更。
:::
```
FROM node:20
# Create app directory
WORKDIR /usr/src/app
COPY ./backend/package*.json ./
#package*.json 會匹配所有以 package 開頭並以 .json 結尾的檔案。
只複製與安裝相依套件(dependencies)相關的檔案,減少 Docker build 的時間。
RUN npm install
# Bundle app source
COPY ./ .
CMD [ "node", "backend/index.js"]
```
#### 映像層、容器層
- 映像檔層(Image Layers)
- Docker映像檔是由多個映像檔層組成的。
- 每一層都代表映像檔的一個變更,例如安裝軟體、複製檔案等。
- `docker inspect <IMAGE>` 可以看Image的細節
- 容器層(Container Layer):
- 當你運行一個容器時,Docker會在映像檔層之上建立一個可寫的容器層。
- 容器內的應用程式對檔案系統的任何變更,都會寫入這個容器層。
- 容器層只有一層

#### Docker Hub
:::info
**Docker Hub** 是官方的 Docker 映像檔(Image)託管與管理平台,類似於 **GitHub**。它允許開發者:
- 存放、分享、管理 Docker Image(容器映像檔)
- 拉取(Pull)與推送(Push) Image
- 存取官方與社群提供的 Docker Image
- 私有(Private)與公開(Public)倉庫
:::
https://philipzheng.gitbook.io/docker_practice/basic_concept/repository
### Ansible[](https://)

<a href='[https://ithelp.ithome.com.tw/articles/10281464](https://medium.com/@chihsuan/ansible-%E8%87%AA%E5%8B%95%E5%8C%96%E9%83%A8%E7%BD%B2%E5%B7%A5%E5%85%B7-b2e8b8534a8d)'>src</a>
#### 為甚麼需要 Ansible
:::info
**Ansible** 是一款,主要用於 **配置管理(Configuration Management)**、**應用程式部署(Application Deployment)** 和 **IT 自動化工具**
部署環境時,需要花費大量的時間來安裝套件、設定Config檔案、調整dependency,使用 **Ansible** 可以將這些「制式化的系統操作」自動化,讓開發者將注意力投注在值得關注的事物上。
撰寫好「部署的流程」(例如安裝套件、複製檔案…)及「遠端主機的連線資訊」後,運行Ansible的主機即可連線至其他主機,進行自動部署,提高部署的效率、降低安裝過程中人為失誤的機率。
:::
#### YAML(YAML Ain’t Markup Language)
:::info
YAML
- 想像成是個文本紀錄資料或數據
- 使用 Key-Value(鍵值對)
- 有String,Integer,Boolean,Array
- 縮排代表層級結構,不像 JSON 需要 `{}` 和 `[]`
:::
- 用途
- 定義 CI/CD 工作流程
- 編寫 Ansible 劇本
- 舉例
```
name: 安裝 Nginx 在虛擬機
hosts: your_vms_group #your_vms_group #是隨便取的群組名稱
become: true # 使用 sudo 權限執行任務
tasks:
- name: 安裝 Nginx
apt:
name: nginx
state: present # 確保 Nginx 安裝(如果已安裝則不重複安裝)
- name: 啟動 Nginx 服務
service:
name: nginx
state: started # 啟動 Nginx 服務
enabled: true # 讓 Nginx 開機自動啟動
```
- 用途
- 配置管理
- **自動化管理伺服器、系統或應用的設定**,確保所有機器的配置狀態保持一致
- 應用程式部屬
- **將應用程式從開發環境部署到生產環境**(例如 Web 應用、API 伺服器、後端服務等)。
- 伺服器安全性管理
- 建立使用者、設定防火牆、安裝安全更新
- 雲端基礎設施管理(AWS、Azure、GCP)
- 晚點會介紹到Terraform
:::info
存放 yml 描述檔並執行 `ansible-playbook` 的機器稱為 Control Node,遠端的所有參與佈署的機器稱為 Managed Node。
:::
| 名稱 | 角色 | 主要功能 |
|-------------------------------|--------------------------------|----------|
| **Control Node(控制節點)** | 負責執行 Ansible 指令的機器 | 存放 yml 描述檔、執行 `ansible` 或 `ansible-playbook` 來管理受管節點 |
| **Managed Node(受管節點)** | 被 Ansible 控制的遠端機器 | 透過 SSH 接收 Ansible 指令並執行自動化任務 |
- 特點
- 在遠端只需要 python 環境與 ssh server,不需要其他 agent 也可以直接部屬,因此易於部署。
- **無代理(Agentless)**
傳統的 IT 自動化工具(如 Puppet、Chef)**需要在每台受管機器上安裝 Agent(代理程式)**Ansible 採用「無代理(Agentless)」設計,所有操作都透過 SSH(Linux)或 WinRM(Windows)執行**,無需額外安裝軟體。
- ssh步驟
- 生成公私鑰
- 將公鑰傳送到遠端機器
- 使用私鑰進行連接
- 它使用 YAML,以 Ansible Playbook 的形式,允許您以接近英語的方式描述您的自動化作業。
---
<!--
當 CI/CD Pipeline(如 GitHub Actions)測試通過,並將 Docker 映像檔推送到 Docker Hub 後,**Ansible 的任務就是透過 SSH 連線到新的伺服器,安裝環境並部署應用程式**。
1. Ansible 透過 SSH 連接到遠端伺服器
2. Ansible 安裝環境(假設是Kubernetes)
3. 建立 Kubernetes 叢集啟動應用程式
4. Ansible 拉取最新的image file
5. 啟用Kubernetes部屬
-->
#### Ansible術語
1. **Playbooks (劇本)**
- 解釋:Playbook 是 Ansible 中最重要的組織單位之一,**用來定義多個步驟**,並規定這些步驟該如何在目標機器上執行。Playbook 是由多個 "play" 組成,每個 Play 會對應一組目標主機並執行一系列的任務(tasks)。
- 例如,可以透過 Playbook 描述如何安裝應用程式、設置伺服器環境,並執行一系列的測試來驗證部署是否成功。
2. **Modules (模組)**
- 解釋:Ansible 模組是預定義的腳本,用來在目標機器上執行具體的操作。例如,安裝某個應用、設定資料庫等,這些都可以使用 Ansible 的模組來自動化,並確保在多台機器上以一致的方式執行。
:::info
**Playbooks 和 Modules**:在自動部署和測試流程中,模組和 Playbook 用來執行具體操作和協調不同伺服器上的任務。例如,Playbook 可以描述如何安裝和配置應用程式,並用 Modules 來運行具體的部署任務。
:::
3. **Inventory (清單)**
- 解釋:Inventory 是一個檔案(可以是 INI 格式或 YAML 格式),用來定義 Ansible 所管理的所有節點。它可以列出不同的伺服器(例如 web 伺服器、資料庫伺服器),並將它們分組,這樣可以在自動化部署或測試時根據不同群組執行操作。
```
name: 安裝 Nginx 在虛擬機
hosts: your_vms_group #your_vms_group #是隨便取的群組名稱
become: true # 使用 sudo 權限執行任務
tasks:
- name: 安裝 Nginx
apt:
name: nginx
state: present # 確保 Nginx 安裝(如果已安裝則不重複安裝)
- name: 啟動 Nginx 服務
service:
name: nginx
state: started # 啟動 Nginx 服務
enabled: true # 讓 Nginx 開機自動啟動
```
4. **Plugins (外掛)**
- 解釋:**外掛(Plugins)** 是一組可擴展的功能模組,它們用來增強 Ansible 的核心功能,與模組的區別在於,外掛主要運行於 Ansible 控制節點。
- Modules & Plugins
- **Modules(模組)** 是用來執行特定任務的(如安裝 Nginx、管理使用者、設定檔案等)。
- **Plugins(外掛)** 則是用來擴展 Ansible 本身的行為(如改變日誌格式、設定連線方式、處理錯誤等)
- 舉例
- 預設情況下,Ansible 只能透過 **SSH 連線** 到遠端機器,但如果想 **直接對 Docker 容器執行 Ansible**,可以用 `docker` 連線外掛。
```
name: 在 Docker 容器內安裝 Nginx
hosts: all
connection: docker # 使用 docker 連線外掛
tasks:
- name: 安裝 Nginx
apt:
name: nginx
state: present
```
---
https://minglunwu.com/notes/2021/ansible_note.html/
## 8. IaC
#### 什麼是 Infrastructure?
支撐系統/軟體服務正常運作的基礎建設(伺服器、網路、資料庫)
### Infrastructure as Code(IaC)
- 一種通過軟體語法就可以定義出雲端服務所需架構的方法,而這可定義的架構可包括了:
- 軟體、伺服器、儲存裝置、網路等等。
- 軟體:MySQL
- 伺服器:AWS EC2雲端VM
- 儲存裝置:
>儲存裝置可以是虛擬化硬碟
- 網路:port、IP
- 由於基礎架構能以程式碼的形式來定義,所以管理者就可以很快地自動把雲端服務部署起來
- 管理者也可以把這些程式碼存放到軟體版本控制系統如 Git、Gitlab 之內,以確保可複製性與版本控制的好處。
- 盡可能讓所有環境(開發、測試、正式),用統一規管的格式或設定檔(例如 yaml 檔)來部署,而不是人為去設定網路和伺服器等
- 設定檔也在 Git 版本控制裡
- 實現理想的軟體定義 (Software Defined)
- 可以追蹤環境的改變
### Terraform
相對 ansible,比較 focus 在雲端的 infra 的配置。
Terraform 可以「用程式碼」管理雲端資源,做到可重現、版本控制、自動化部署。
- Terraform 負責**建立基礎設施**(如 AWS EC2, GCP VM, Kubernetes Cluster)。
- Ansible 負責**設定伺服器內部環境**(如安裝 Nginx, 設定 MySQL)。
- 通常先用 Terraform 建立雲端資源,再用 Ansible 進行應用程式部署。
#### Configuration
- Terrform 採用名為 HCL (HashiCorp Configuration Language) 的組態語言來描述基礎架構。
- HCL 是一種宣告式的語言,讓你直接寫下期望的基礎架構,而不是寫下過程的每一個步驟。
- Terrform 組態語言的讓你更容易的宣告資源 (Resource),更可以把一系列的資源包裝成一個模組 (Module),藉此設計更龐大的架構。
- 在 Terraform 中,**區塊 (Block)** 和 **引數 (Argument)** 是用來定義基礎架構的核心語法。
**Provider**
- Terraform 不能直接管理雲端伺服器、呼叫 API
- Terraform 使用 Provide 作為中介
- 有了 Provide 就可以跟伺服器**資源**互動
- AWS 的 EC2 , S3 , VPC
**區塊 (Block)**
- 區塊 (Block) 是 **Terraform 配置語法的基本單位**,用來定義各種不同的設定,區塊的開頭是區塊類型 (Block Type),後面接著區塊標籤 (Block Label),隨著區塊種類的不同,標籤的數量會有差異。
**引數 (Argument)**
- 引數 (Argument) 的格式是以名稱開始,接著一個等號,等號後面放值。
在不同資源類型中,有各自的必要 (Required) 引數,一定要寫好這需必要引數,整個組態檔才能夠正常使用。
:::info
- `terraform` 區塊寫著需要的工具來源跟版本
- `provider` 區塊寫著供應商 aws 相關的設定
- `resource` 區塊寫著伺服器相關的設定
:::
1. 寫組態檔 (configuration)
- 組態檔要放到一個資料夾中,所以先建立一個資料夾。
- `mkdir build-aws-instance`
2. `vi main.tf`
```tf=
terraform {
required_providers {
aws = {
source = "hashicorp/aws"
version = "3.5.0"
}
}
}
provider "aws" {
profile = "default"
region = "ap-northeast-1"
}
resource "aws_instance" "example" {
ami = "ami-0461b11e2fad8c14a" # EC2 的作業系統和基本配置。
instance_type = "t2.micro" # 硬體規格和資源配置
}
```
這邊以 `resource` 的區塊為例,後面依續接著資源類型 (Resource Type) 跟區域名稱 (Local Name) 兩個標籤。
- 資源類型 (Resource Type): 要從供應商提供的服務清單中尋找,不同的供應商有著不同的資源類型
- 名稱 (Name): 就是自己定義的名稱,有效範圍只在這個模組內
- 區塊的內容用大括號 `{ }` 包起來,裡面有多個引數 (Argument)

#### ref
- https://medium.com/gemini-open-cloud/%E6%B7%BA%E8%AB%87%E6%9E%B6%E6%A7%8B-%E7%A8%8B%E5%BC%8F%E7%A2%BC-2e83f3ff6236
- https://ithelp.ithome.com.tw/m/users/20129946/ironman/3383
## 9. Git
### 介紹
- Git 是一個用於控制專案版本的工具
- <a href='https://github.com'>GitHub</a>, <a href='https://gitlab.com/'>GitLab</a> 等等是基於 Git 開發的平台,讓使用者可以將存於本地的檔案存於這些雲端的平台更便於團隊協作,也會提供其他進階的功能(例如:GitHub Action)
### 基礎指令
- <a href='https://learngitbranching.js.org/?locale=zh_TW'>learngitbranching</a>
- 包含教學以下常用 Git 指令 : commit, branch, checkout, cherry-pick, reset, revert, rebase, merge
### Branching Model
- 
- <a href='https://nvie.com/posts/a-successful-git-branching-model/'>src</a>
- master/main branch
- 是 production-ready,已經經過許多測試,確保服務和功能皆可正常運作
- 這個 branch 通常會設一些 <a href='https://docs.github.com/en/repositories/configuring-branches-and-merges-in-your-repository/managing-protected-branches/about-protected-branches'>protection rules</a>,例如不能被 force pushes
- develop branch
- 先從 main branch 切出來,用以開發新版本的各種 feature
- feature branch 再從此 branch 切出去
- 當此 branch 經過一定的測試後準備發布新版本,就可以 merge 回 release
- feature branch
- 用來開發各種新功能的 branch,開發完後再 merge 回 develop
- release brabch
- 與主分支(master/main)合併前的過渡階段,是發佈新版本之前所必須進行的相關手續。
- 在此分支必須進行一些整合性測試,並進行小 bug 修復及增加一些metadata(例如版本號或是 build 日期等)。
- hotfixes branch
- 出現了一個急需在短時間內修復的 bug,無法等到下一次發佈時才修復
- 開此分支的好處是 feature branch 可以繼續原本的工作,而 bug 修復工作也同時可由另外的成員執行,將對彼此間的影響降低。
### Ref
- https://arc.net/l/quote/diddlbfk
- https://nvie.com/posts/a-successful-git-branching-model/
## 10. GitHub Action
### Full Process of Git GitHub Action

### Components
- 
1. Workflows
- 一個 yaml 檔案,其定義了自動化的流程:當特定的 Event 發生,會去執行某些 Job(e.g., test, build, deploy, etc.)
- 一個專案可以有多個 Workflow,這些 Workflow 要被放在專案的路徑 `.github/workflows` 裡面
3. Events
- 觸發 Workflow 的事件,每個事件又包含了不同的 Types (子行為)
- 常用的 Events
- <a href='https://docs.github.com/en/actions/writing-workflows/choosing-when-your-workflow-runs/events-that-trigger-workflows#pull_request'>pull_request</a>
- default types : `opened`, `synchronize`, or `reopened`
- 範例
```yaml=
on:
pull_request:
types: [opened, reopened]
```
- <a href='https://docs.github.com/en/actions/writing-workflows/choosing-when-your-workflow-runs/events-that-trigger-workflows#push'>push</a>
- Runs your workflow when you <b>push a commit or tag</b>, or when you create a repository from a template.
- 範例
```yaml=
on:
push:
branches:
- main # pushes only on the main branch
tags:
- 'v*' # pushes that create tags (e.g., for releases),
paths:
- 'src/**' # pushes that modify specific files or folders.
```
- <a href='https://docs.github.com/en/actions/writing-workflows/choosing-when-your-workflow-runs/events-that-trigger-workflows'>events-that-trigger-workflows</a>
4. Jobs
- 由 Steps 組成,且 Steps 之間會有相依性(包含執行順序以及環境,因為同個 Job 的 Steps 是跑在同一個 Runner 上)
- Step : 每個 Step 的內容會被 GitHub Runner 包成一個 shell script 被執行
- 範例:
```yaml=
jobs:
test:
runs-on: ubuntu-latest # Runner
steps:
- uses: actions/checkout@v2
- name: Use Node.js 20.10.0
uses: actions/setup-node@v2
with:
node-version: 20.10.0
- name: Install package
run: cd ${{ vars.REPO_PATH }} && npm install # use the variables stored in github
- name: Testing
run: cd ${{ vars.REPO_PATH }} && npm test
build:
needs: test # 確保測試有成功才執行
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v3
- name: Login
run: |
docker login --username=${{ secrets.DOCKER_USERNAME }} --password=${{ secrets.DOCKER_PASSWORD }}
- name: Build and Publish
run: |
docker build . --tag ${{ vars.REGISTRY }}/${{ vars.IMAGE_NAME }}
docker push ${{ vars.REGISTRY }}/${{ vars.IMAGE_NAME }}
```
- <a href='https://docs.github.com/en/actions/writing-workflows/choosing-what-your-workflow-does/store-information-in-variables'>Variables</a>
- 用於儲存團隊、專案的一些設定在 github 上,在 Step 中就可以用環境變數的方式使用
- 好處
1. 共用變數,不用存多個一樣的,修改也更方便
2. 這樣修改時就不用改 code
- <a href='https://docs.github.com/en/actions/security-for-github-actions/security-guides/about-secrets'>Secerts</a>
- 也是一種變數,但是可以用儲存 sensitive data(例如:docker hub password)
- 新增完成後,就無法在 Github 網頁的介面上看到明文的 Secret
5. Runners
- 每個 Job 都會跑在一個 runner 上,這個 runner 可以是 virtual machine 或是 container
> GitHub provides Ubuntu Linux, Microsoft Windows, and macOS runners to run your workflows.
- 如果有特殊客製化的需求(e.g. 用其他 OS、硬體規格增加),可以用 self-hosted runner
- <a href='https://docs.github.com/en/actions/hosting-your-own-runners'>hosting-your-own-runners</a>
6. Actions
- 為了避免 Workflow 之間出現重複的內容,因此可以使用兩種 Action : Reusable workflows, Composite actions 來達成
| Reusable workflows | Composite actions |
| -------- | -------- |
| 作為一個 Job 被執行 | 作為一個 Step 被執行 |
| 內容可以包含多個 Job | 內容可以包含多個 Step |
| 可以用 Secret | 不能用 Secret |
| <a href='https://github.com/n3wt0n/ReusableWorkflow/blob/main/.github/workflows/buildAndPublishDockerImage.yml'>docker example</a> | <a href='https://docs.github.com/en/actions/sharing-automations/creating-actions/creating-a-docker-container-action'>docker example</a> |
- 常見的使用情境
1. 使用的 語言、Framework 本身相關常用的 Workflow : 這種通常在 <a href='https://github.com/marketplace'>GitHub Marketplace</a> 上有很多開源的 Action 可以用
2. 公司內部的客製化流程:這種就需要自己開發 Action
### Alternatives of GitHub action

### Ref
- https://docs.github.com/en/actions/about-github-actions/understanding-github-actions#the-components-of-github-actions