はじめに
k6で負荷試験用のスクリプトを作成する際に、負荷シナリオに対するテストをどう書けばいいものなのか、疑問が湧きます。
テストの方法として、単体テスト、結合テスト等、いくつかの方法があると思います。
その中でも、今回はモックAPIサーバを準備して、負荷シナリオに対するテストを行う方法を紹介します。
先に結論から
イメージ図は以下の通りです。
Mock API ServerをExpressを使って立てます。
各シナリオからは、実際のWebサーバではなく、Mock API Serverへアクセスします。
Mock API Serverはどんなエンドポイントが叩かれても、200 okを返却するAPIを用意します。
個別にMock APIのレスポンスを制御したい場合には、その都度APIを準備します。
なお、この構成で出来ること、出来ないことは以下の通りです。
- 出来ること
- k6のシナリオの動作の担保
- 出来ないこと
- k6内で定義した関数やモジュールの振る舞いの担保
- リクエスト先のAPIのレスポンスが正しいことの担保
シナリオを動かしても、エラーが起きないよね?程度の確認となります。
実際のMock API Serverの作成は、以下のPRが参考になります。
https://github.com/gonkunkun/k6-template/pull/2
Mock API Serverを立ててみよう
それでは実際にサーバを立てていきましょう。
ディレクトリ構成は以下をご参考にどうぞ。
https://github.com/gonkunkun/k6-template/
~/D/g/t/mock ❯❯❯ tree ./ -I node_modules
./
├── package-lock.json
├── package.json
├── src
│ ├── index.ts
│ └── sampleProduct
│ └── routes.ts
└── tsconfig.json
Mock API Serverを立てる
以下のようなpackage.jsonファイルを準備します。
{
"name": "test",
"version": "1.0.0",
"description": "",
"main": "src/index.ts",
"author": "",
"license": "ISC",
"devDependencies": {
"@types/express": "^4.17.21",
"express": "^4.19.2",
"ts-node": "^10.9.2",
"typescript": "^5.4.3"
}
}
そしたら、モジュール郡をinstallします。
~/D/g/template-of-k6 ❯❯❯ cd mock
~/D/g/t/mock ❯❯❯ npm install
srcディレクトリ配下にサーバの設定を追加します。
以下、src/index.tsを作成します。
import express, { Request, Response } from 'express'
import sampleProductRouter from './sampleProduct/routes'
const app = express()
const port = process.argv || 3003
app.get('/', (req: Request, res: Response) => {
console.log(req.body)
res.status(200).json({ message: 'This is mock endpoint' })
})
app.use('/sampleProduct', sampleProductRouter)
app.listen(port, () => console.log(`Listening on port ${port}`))
これで、ルートの/*
に対するレスポンスを定義出来ました。
更に、将来的に複数のプロダクトがこのk6スクリプトを利用することを考慮して、プロダクト毎にエンドポイントを分けましょう。
import { Router, Request, Response } from 'express'
const router = Router()
router.get('/sample', (req: Request, res: Response) => {
console.log(req.body)
res.status(200).json({ message: 'This is sampleProduct endpoint' })
})
router.use((req: Request, res: Response) => {
console.log(req.body)
res.status(200).json({ message: 'OK' })
})
export default router
これで、ルートの/sampleProduct
に対するレスポンスを定義出来ました。
上記のAPIはひたすら200 okのみを返却します。
例えば、/sample/test
に対するレスポンスを更に細かく制御したいという場合には、都度都度新しいAPIを追加してあげましょう。
Mock API Serverが完成したら、サーバを起動します。
~/D/g/t/mock ❯❯❯ npx ts-node ./src/index 3005
Listening on port 3005
試しにAPIを叩いてみます。
~/D/g/template-of-k6 ❯❯❯ curl -XGET localhost:3005/sampleProduct
{"message":"OK"}%
~/D/g/template-of-k6 ❯❯❯ curl -XGET localhost:3005/sampleProduct/test
{"message":"OK"}%
良さそうですね。
k6からMock API Serverを利用する
後は、k6実行時のエンドポイントをMockに向けるだけです。
サンプルアプリの中では.env
で環境変数を渡していますので、ここのエンドポイントを例えばSAMPLE_PRODUCT_ENDPOINT=http://localhost:3005
に修正してあげます。
https://github.com/gonkunkun/k6-template/blob/main/.env.sample#L4
あとはk6を実行するだけです。
~/D/g/template-of-k6 ❯❯❯ npm run smoke:sample-product ✘ 255 main ✱
/\ |‾‾| /‾‾/ /‾‾/
/\ / \ | |/ / / /
/ \/ \ | ( / ‾‾\
/ \ | |\ \ | (‾) |
/ __________ \ |__| \__\ \_____/ .io
execution: local
script: ./dist/loadTest.js
output: -
scenarios: (100.00%) 2 scenarios, 2 max VUs, 1m30s max duration (incl. graceful stop):
* sampleScenario1: 1 iterations for each of 1 VUs (maxDuration: 1m0s, exec: sampleScenario1, gracefulStop: 30s)
* sampleScenario2: 1 iterations for each of 1 VUs (maxDuration: 1m0s, exec: sampleScenario2, gracefulStop: 30s)
INFO[0003] 0se: == setup() BEGIN =========================================================== source=console
INFO[0003] 0se: Start of test: 2024-03-31 19:11:07 source=console
INFO[0003] 0se: Test environment: local source=console
INFO[0003] 0se: == Check scenario configurations ====================================================== source=console
INFO[0003] 0se: Scenario: sampleScenario1() source=console
INFO[0003] 0se: Scenario: sampleScenario2() source=console
INFO[0003] 0se: == Check scenario configurations FINISHED =============================================== source=console
INFO[0003] 0se: == Initialize Redis ====================================================== source=console
INFO[0003] 0se: == setup() END =========================================================== source=console
INFO[0009] 6se: Scenario sampleScenario1 is initialized. Lens is 10000 source=console
INFO[0009] 6se: Scenario sampleScenario2 is initialized. Lens is 10000 source=console
INFO[0009] 6se: == Initialize Redis FNISHED =============================================== source=console
INFO[0009] 6se: sampleScenario2() start ID: 2, vu iterations: 1, total iterations: 0 source=console
INFO[0009] 6se: sampleScenario1() start ID: 2, vu iterations: 1, total iterations: 0 source=console
INFO[0010] 7se: sampleScenario2() end ID: 2, vu iterations: 1, total iterations: 0 source=console
INFO[0010] 7se: sampleScenario1() end ID: 2, vu iterations: 1, total iterations: 0 source=console
INFO[0010] 0se: == All scenarios FINISHED =========================================================== source=console
INFO[0010] 0se: == Teardown() STARTED =========================================================== source=console
INFO[0010] 0se: == Initialize Redis ====================================================== source=console
INFO[0010] 0se: == Teardown() FINISHED =========================================================== source=console
INFO[0010] 0se: == Initialize Redis FINISHED =============================================== source=console
█ setup
█ sampleScenario2
✓ Status is 200
█ sampleScenario1
✓ Status is 200
█ teardown
checks.........................: 100.00% ✓ 2 ✗ 0
data_received..................: 152 kB 21 kB/s
data_sent......................: 939 kB 132 kB/s
group_duration.................: avg=621.43ms min=621.42ms med=621.43ms max=621.45ms p(90)=621.45ms p(95)=621.45ms
http_req_blocked...............: avg=395.64ms min=392.1ms med=395.64ms max=399.17ms p(90)=398.46ms p(95)=398.81ms
http_req_connecting............: avg=175.13ms min=175.12ms med=175.13ms max=175.14ms p(90)=175.14ms p(95)=175.14ms
http_req_duration..............: avg=213.6ms min=209.97ms med=213.6ms max=217.23ms p(90)=216.5ms p(95)=216.87ms
{ expected_response:true }...: avg=213.6ms min=209.97ms med=213.6ms max=217.23ms p(90)=216.5ms p(95)=216.87ms
http_req_failed................: 0.00% ✓ 0 ✗ 2
http_req_receiving.............: avg=390.5µs min=58µs med=390.49µs max=723µs p(90)=656.5µs p(95)=689.75µs
http_req_sending...............: avg=20.21ms min=15.2ms med=20.21ms max=25.21ms p(90)=24.21ms p(95)=24.71ms
http_req_tls_handshaking.......: avg=187.4ms min=183.88ms med=187.4ms max=190.93ms p(90)=190.23ms p(95)=190.58ms
http_req_waiting...............: avg=193ms min=191.96ms med=193ms max=194.04ms p(90)=193.83ms p(95)=193.93ms
http_reqs......................: 2 0.280001/s
iteration_duration.............: avg=1.93s min=507.73µs med=625.79ms max=6.49s p(90)=4.73s p(95)=5.61s
iterations.....................: 2 0.280001/s
vus............................: 2 min=0 max=2
vus_max........................: 2 min=2 max=2
running (0m07.1s), 0/2 VUs, 2 complete and 0 interrupted iterations
sampleScenario1 ✓ [======================================] 1 VUs 0m00.6s/1m0s 1/1 iters, 1 per VU
sampleScenario2 ✓ [======================================] 1 VUs 0m00.6s/1m0s 1/1 iters, 1 per VU
これで、mock API Serverを利用して、シナリオを動作確認が出来ます。
わざわざ実サーバを立てなくても良い点が便利です。
おわりに
今回は、Mock API Serverを立てて、シナリオの動作の担保が出来るようにしてみました。
ここまで出来るようになると、次はこのテストをCIに組み込むことが出来ます。
以下の記事でその説明をしています。
興味があればご覧くださいませ。