參考網站
參考網站
參考網站
與老牌工具 JMeter 相比,K6 比較年輕(2017),架構與設計現代化許多,採用 Go + JavaScript,聽起來就比 Java 年輕有活力。K6 沒有如 JMeter 的華麗友善 GUI,測試細節全靠指令參數與程式碼決定。
K6 核心以 Go 開發,不用擔心程式效能(依文件說明,K6 靠單一主機便可產生每秒 30 萬次請求);測試程式則採用 ES6 / JavaScript 語言,讓前端/全端工程師備感親切,進階應用需要對 Module、webpack 有點概念,但單純測試一個 .js 就能搞定,不難上手。官方文件整理得頗詳細,建議花點時間約略讀過再上路,許多東西文件都有寫到,不要瞎查資料胡亂嘗試浪費時間。
先釐清一點,雖然口語上我們很習慣說「壓力測試」或「壓測」,實際上這些相關測試可再細分成:煙霧測試、負載測試、壓力測試、尖峰測試、浸泡測試,下文會再詳細說明。
不過,「壓力測試」是比較通俗常用的說法,所以我會繼續統稱壓力測試,有特定目標的測試再特別區分。
介紹
K6 是用 Go 語言編寫的一種高效能的負載測試工具。具有下面幾個特點:
- K6 嵌入了 JavaScript runtime,可以使用 JavaScript ES2015、ES6 來編寫指令碼。
- 強大的 CLI 工具。
- 使用 Checks 和 Thresholds 可以更加輕鬆的做面向目標的自動化的負載測試。
Checks: 類似斷言功能,但不會真的中斷執行,只是會返回 check 的數據百分比。
Thresholds: 極限值、門檻、閾值,指測試系統性能所預期的通過 or 失敗的標準。
為什麼要用 K6?
他是用 JavaScript 直接進行負載測試(JavaScript 腳本 ➡️ Go 底層運作)
這對專注於寫 JavaScript 的人是一大福音!
以自身公司而言,大多都是 JavaScript 為基底的測試,所以如果團隊中能夠統一一種語言,不管是在測試、開發上,能降地學習成本及提高協做效率。
如何安裝?
MacOS
記得先安裝 Homebrew
Windows
方法二:使用 winget 安裝
Linux
Debian/Ubuntu
1
2
3
4
| sudo gpg --no-default-keyring --keyring /usr/share/keyrings/k6-archive-keyring.gpg --keyserver hkp://keyserver.ubuntu.com:80 --recv-keys C5AD17C747E3415A3642D57D77C6C491D6AC1D69
echo "deb [signed-by=/usr/share/keyrings/k6-archive-keyring.gpg] https://dl.k6.io/deb stable main" | sudo tee /etc/apt/sources.list.d/k6.list
sudo apt-get update
sudo apt-get install k6
|
Fedora/CentOS
1
2
| sudo dnf install https://dl.k6.io/rpm/repo.rpm
sudo dnf install k6
|
了解 K6 能做到哪些負載測試
- Smoke Testing 煙霧測試 - 驗證系統在最低硬體配備、正常負載下不會出錯
- Load Testing 負載測試(Performance Testing 效能測試) - 取得在一般及尖峰負載下的系統效能數字(用戶數、Throughput、吞吐量(RPS))
- Stress Testing 壓力測試 - 測試在高度負載或極端條件下系統的穩定性及可靠度,找出系統極限(請勿在 production 測試)
- Spike Testing 尖峰測試 - 故意製造瞬間流量驟升取得系統效能數字(請勿在 production 測試)
- Soak Testing 浸泡測試 - 測試系統在長期運作下的穩定性及可靠度
大致上的分類為(低中高流量每間公司定義都不一樣):
低流量 | 中等流量 | 高流量 |
---|
Smoke Testing 煙霧測試 | Load Testing 負載測試(Performance Testing 效能測試) | Stress Testing 壓力測試 |
Soak Testing 浸泡測試 | Spike Testing 尖峰測試 | |
小試身手
在 Windows 安裝很容易,我是用 Chocolatey choco install -y k6
兩分鐘搞定。寫幾行程式存成 script.js
,再執行 k6 run script.js
便做能完簡單測試,得到平均回應時間(http_req_duration) 及 Throughput (http_reqs):
script.js
1
2
3
4
5
6
7
8
9
10
11
| import http from "k6/http";
import { sleep } from "k6";
export const options = {
vus: 1, // user count
duration: '1s'
};
export default function () {
http.get("https://test.k6.io");
};
|
執行指令:
參數設定
K6 提供的參數很多,以下主要介紹幾個較常用的為主:
vus
虛擬用戶的數量,最少必須要 1 個,與 duration 搭配使用。
duration
指定測試運行的總持續時間,與 vus 一起使用。
sample code:
1
2
3
4
5
6
7
8
9
10
11
| import http from 'k6/http';
import { sleep } from 'k6';
export const options = {
vus: 1,
duration: '1s',
};
export default function () {
http.get('https://test.k6.io');
sleep(1);
}
|
CLI:
1
| k6 run --vus 1 --duration 1s script.js
|
iterations
腳本中的函數被執行的次數。
sample code:
1
2
3
4
5
6
7
8
9
10
11
12
| import http from 'k6/http';
import { sleep } from 'k6';
export const options = {
vus: 10,
duration: '5s',
iterations: 50
};
export default function () {
http.get('https://test.k6.io');
sleep(1);
}
|
CLI:
1
| k6 run --vus 10 --duration 5s --iterations 50 script.js
|
表示「模擬 10 個用戶,此次腳本最多就是執行 50 次,如果 50 次腳本沒執行完,時間卻到了,那此次測試會顯示 default ✗」。
Stages
可以指定在特定時間內增加或減少用戶數量的執行方式,也就是說可以模仿更精準的測試情境。
sample code:
1
2
3
4
5
6
7
8
9
10
11
12
13
14
| import http from 'k6/http';
import { sleep } from 'k6';
export let options = {
stages: [
{ duration: '30s', target: 10 },
{ duration: '1m30s', target: 30 },
{ duration: '20s', target: 0 },
],
};
export default function () {
http.get('https://test.k6.io');
sleep(1);
}
|
CLI:
1
| k6 run --stage 30s:10, -s 1m30s:30, -s 20s:0 .\script.js
|
- 第一階段就是在 30 秒間逐步把用戶加到 10 個
- 第二階段就是在一分半內逐步從 10 個用戶追加到 30 個用戶
- 第三階段是在 20 秒內逐步從 30 個用戶 降到 0 個用戶個用戶
rps
用戶每秒可以發送最大的 request 數量。
其實就是為了更好模擬用戶流量,再測試前須確認好測試範疇,多數平台應該都是在某些時間點流量會增大,但這個流量增大並非是無上限的增大,一定會落在某個數值,只要將數值取出來後並換算。
e.g:
查看數據後,發現這個時段會有 100000 個 request 數量
那我們要測試的是 60 秒能要達到 100000
那就是 100000/60 = 1666
1666 出來後,我們都會多估 10~20% 以防萬一,所以總計大約 2000 即是 rps 的數量
若要更準確的符合真實數據,也可以以下這樣計算:
1
2
3
4
5
| 用戶量 100 vus
執行時間 60s
request 條件是 100000
那就是一位 user rps 就是 100000/100/60 = 16
通常 rps 的範疇會多估 10-20% 以防萬一,所以湊個整數 20 就是較符合的數值
|
sample code:
1
2
3
4
5
6
7
8
9
10
11
12
| import http from 'k6/http';
import { sleep } from 'k6';
export const options = {
vus: 100,
duration: '60s',
rps: 20
};
export default function () {
http.get('https://test.k6.io');
sleep(1);
}
|
group
主要就是將按照功能進行組裝在同一個測試腳本。
可以針對測試方法做調整,通常在測試 API 時可能是一個行為,但是是會多個 API 連續調用的情況。
這時候可以使用 group 方法來實現,以下是官網的例子。
sample code:
1
2
3
4
5
6
7
8
9
10
11
12
| import { group } from 'k6';
export default function () {
group('user flow: returning user', function () {
group('visit homepage', function () {
// load homepage resources
});
group('login', function () {
// perform login
});
});
}
|
如上方例子,可以將整個登入流程加入一個群組,測試結果會多一個指標 group_duration
,就可方便得知這一個 group 平均執行時間。
另外,官方也寫到:
Discouraged: one group per request
Wrapping each request within a group might add unnecessary boilerplate.
意味著這個 group 功能不建議僅拿來組裝單一 API,它不是一個像單回傳 api module 般的使用。
這樣就失去 group 功能真正的涵義了。
sample code:
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
| import { group, check } from 'k6';
import http from 'k6/http';
const id = 5;
// reconsider this type of code
group('get post', function () {
http.get(`http://example.com/posts/${id}`);
});
group('list posts', function () {
const res = http.get(`http://example.com/posts`);
check(res, {
'is status 200': (r) => r.status === 200,
});
});
|
scenarios
顧名思義,就是測試的情境的參數。
在做效能測試前,通常都會先擬定好要測試的情境,蒐集真實數據進行評估,再進一步評估預期達到的目標再哪。
所以可以先將一些常用的測試寫好。只要引用一下,設定一下立刻可以達到我們想要測試的結果。
只要針對每個情境實作好了一些 Executors,最後只要呼叫它即可。
sample code:
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
| import http from 'k6/http';
export let options = {
discardResponseBodies: true,
scenarios: {
contacts: {
executor: 'constant-arrival-rate',
rate: 200, // 200 RPS, since timeUnit is the default 1s
duration: '1m',
preAllocatedVUs: 50,
maxVUs: 100,
},
},
};
export default function () {
http.get('https://test.k6.io/contacts.php');
}
|
使用這個範例,K6 會盡所能在 duration 內去達到 200 RPS。
所以只要使用這個方法,可以很快找到你 API 的極限瓶頸在哪。
group vs scenarios
- group: 通常一套業務邏輯且會被重複使用。
- scenarios: 純粹一個情境、一個故事,為了要讓單一邏輯或 group 能夠被相互組合應用,需要多種不同的參數,用戶數量(vus)、持續時間(duration)等等,以便達到該情境的預期結果。
常看的指標
Http 系列
- 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
上述的參數,主要可以分析整個測試的過程,同時也可以了解該 API 的詳細資料針對響應速度、等待回應時間等等,好讓相關團隊可以進行優化分析。
http_req_receiving、http_req_sending、http_req_waiting 三者關係圖
Checks
其實就是類似斷言。僅會返回通過(pass)或失敗(fail)
但這邊的斷言是,不論成功或失敗,腳本皆仍會繼續執行。
sample code:
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
| export default function () {
const res = http.get('http://test.k6.io/');
check(res, {
'is status 200': (r) => r.status === 200,
});
}
// or
export default function () {
const res = http.get('http://test.k6.io/');
check(res, {
'is status 200': (r) => r.status === 200,
'body size is 11,105 bytes': (r) => r.body.length == 11105,
});
}
|
但如果要需要特別處理錯誤情境時,可以使用 Thresholds。
Thresholds
強烈建議多參考官方文檔,寫得非常清楚。
主要是可以為測試的結果自訂一個標準限制,中文稱為門檻值。
可以針對各個指標限制於某個條件,相對來說會更彈性,測試會更為準確。
sample code:
1
2
3
4
5
6
7
8
9
10
11
12
| import http from 'k6/http';
export const options = {
thresholds: {
http_req_failed: ['rate<0.01'], // http request 失敗的總比例要低於 1% 才能算通過
http_req_duration: ['p(95)<200'], // 95% 的 requests 數回傳時間都要低於 200ms 以內才算通過
},
};
export default function () {
http.get('https://test-api.k6.io/public/crocodiles/1/');
}
|
其中 http_req_duration
的 p(95)
高於 200ms 了,所以它就會顯示 some thresholds have failed
Threshold 其實與 Check 有點類似,但差別在於:
- Check 設定比較單一 且 執行失敗的話,腳本仍會繼續執行,直到腳本結束,才會返回 Check 的數據值。
- Threshold 可以彈性設定條件 且 執行失敗的話,它還可以自行設置中斷點,讓執行中腳本達到某條件的話,就直接中斷並且返回錯誤資訊。
aborting sample code(可自行設定中斷點 sample code):
1
2
3
4
5
6
7
8
9
10
11
| export const options = {
vus: 30,
duration: '2m',
thresholds: {
http_req_duration: [{ threshold: 'p(90) < 400', abortOnFail: true }] //90% 的 requests 數回傳時間都要低於 400ms 以內才算通過,只要一旦高於 400ms 就會直接中斷測試
}
};
export default function () {
http.get('https://test-api.k6.io/public/crocodiles/1/');
}
|
其中 http_req_duration
的 p(95)
高於 200ms 了,所以它就會顯示 some thresholds have failed
常見的套件(module)
K6 套件(module) 基本上我覺得很萬用,是足夠支撐所有測試情境的,也主要介紹幾個較常用的 K6 本身支援的 module (JavaScript API)
http
主要是能發送 API,常使用為 get、post、put、delete…等。
GET sample code:
1
2
3
4
5
6
7
8
9
| import http from 'k6/http';
export const options = {
vus: 1,
duration: '1s',
};
export default function () {
http.get('https://test.k6.io');
}
|
POST sample code:
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
27
| import http from 'k6/http';
const url = 'https://httpbin.test.k6.io/post';
const logoBin = open('./logo.png', 'b');
export default function () {
let data = { name: 'Bert' };
// Using a JSON string as body
let res = http.post(url, JSON.stringify(data), {
headers: { 'Content-Type': 'application/json' },
});
console.log(res.json().json.name); // Bert
// Using an object as body, the headers will automatically include
// 'Content-Type: application/x-www-form-urlencoded'.
res = http.post(url, data);
console.log(res.json().form.name); // Bert
// Using a binary array as body. Make sure to open() the file as binary
// (with the 'b' argument).
http.post(url, logoBin, { headers: { 'Content-Type': 'image/png' } });
// Using an ArrayBuffer as body. Make sure to pass the underlying ArrayBuffer
// instance to http.post(), and not the TypedArray view.
data = new Uint8Array([104, 101, 108, 108, 111]);
http.post(url, data.buffer, { headers: { 'Content-Type': 'image/png' } });
}
|
html
解析 HTML 各元素,可能會需要取得 HTML 上的一些元素值做一些處理或換算。
sample code:
1
2
3
4
5
6
7
8
| import http from 'k6/http';
export default function () {
const res = http.get('https://k6.io');
const doc = parseHTML(res.body); // equivalent to res.html()
const pageTitle = doc.find('head title').text();
const langAttr = doc.find('html').attr('lang');
}
|
sleep
主要就是等待時間,有些時候使用 sleep 是為了更好模擬使用者情境。
因為使用者真實操作 web 可能都會遇到有 loading 渲染的時間,所以我們將 sleep 算進來的話就當作是再重現這部分。
sample code:
1
2
3
4
5
6
7
8
9
10
11
| import http from 'k6/http';
import { sleep } from 'k6';
export const options = {
vus: 1,
duration: '1s',
};
export default function () {
http.get('https://test.k6.io');
sleep(1);
}
|
crypto
這一定也不陌生,非常強大的加密 module ,支援多種不同的加密方式如下:
- hmac
- md4
- md5
- sha256
- sha384
- sha512
通常是為了確保用戶資料安全而特別處理的。e.g. 密碼、API header 等。
sample code:
1
2
3
4
5
6
7
8
9
| import crypto from 'k6/crypto';
export default function () {
console.log(crypto.sha256('hello world!', 'hex'));
const hasher = crypto.createHash('sha256');
hasher.update('hello ');
hasher.update('world!');
console.log(hasher.digest('hex'));
}
|
respone:
1
| INFO[0000] 7509e5bda0c762d2bac7f90d758b5b2263fa01ccbc542ab5e3df163be08e6ca9
|
encoding
1
2
3
4
5
6
7
8
9
10
11
12
| import { check } from 'k6';
import encoding from 'k6/encoding';
export default function () {
const str = 'hello world';
const enc = 'aGVsbG8gd29ybGQ=';
const buf = new Uint8Array([104, 101, 108, 108, 111, 32, 119, 111, 114, 108, 100]).buffer;
check(null, {
'is encoding string correct': () => encoding.b64encode(str) === enc,
'is encoding ArrayBuffer correct': () => encoding.b64encode(buf) === enc,
});
}
|
JSlib
K6 本身支援蠻多受用的套件,但除此之外,他們還有支援一個是 JSlib 顧名思義就是 JS 庫。
我覺得可以把它當作是外掛包因為其中還支援的很多額外的 module 可用。
- httpx: 是將 k6/http 原本套件加以簡化處理,可以當成是優化版的 http
- k6chaijs: 適用於 BDD 和 TDD 斷言風格
k6chaijs sample code:
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
| import { describe, expect } from 'https://jslib.k6.io/k6chaijs/4.3.4.1/index.js';
import http from 'k6/http';
export const options = {
thresholds: {
checks: [{ threshold: 'rate == 1.00' }], // fail test on any expect() failure
},
};
export default function testSuite() {
describe('Basic API test', () => {
const response = http.get('https://test-api.k6.io/public/crocodiles');
expect(response.status, 'API status code').to.equal(200);
});
}
|
utils sample code:
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
| import { sleep } from 'k6';
import http from 'k6/http';
import {
randomIntBetween,
randomString,
randomItem,
uuidv4,
findBetween,
} from 'https://jslib.k6.io/k6-utils/1.2.0/index.js';
export default function () {
const res = http.post(`https://test-api.k6.io/user/register/`, {
first_name: randomItem(['Joe', 'Jane']), // random name
last_name: `Jon${randomString(1, 'aeiou')}s`, //random character from given list
username: `user_${randomString(10)}@example.com`, // random email address,
password: uuidv4(), // random password in form of uuid
});
// find a string between two strings to grab the username:
const username = findBetween(res.body, '"username":"', '"');
console.log('username from response: ' + username);
sleep(randomIntBetween(1, 5)); // sleep between 1 and 5 seconds.
}
|
- aws: 主要是可以直接串接 AWS 上的 API 服務。
總結
k6 官方提供的文檔其實都非常好懂,甚至都會附上很多程式碼範本讓你直接使用。
基本上只要會 javascript,你就可能無痛起手了。
另外 k6 這框架大多使用的情境是以下:
適用人員 | 描述 |
---|
開發人員、SDET | 方便我們透過 K6 提供的 api 以及 CLI 工具來使用開發且開發人員同常對 javascript 也不太陌生,他們也能快速使用 javascript 來開發模擬真實場景的負載測試 |
DevOps、SRE | 這兩個職位都是偏運維方面的,我們能把上面 SDET 開發的 script 拿來進行自動化的壓力測試,確保我們的基礎建設與應用服務都還是保持著高性能的表現。在 K6 開發的 script 內,設定 SLO 來測試服務的運行狀況是否達標 |
QA | 更方便的寫測試案例與腳本,跑起來也很快,還能跟 Postman、Swagger 等整合,對 QA 來說是很方便的 |