こんにちは。よっしーです(^^)
今日は、k6におけるライフサイクルについてご紹介します。
背景
Dockerで構築したWebアプリの開発環境において、k6のライフサイクルについて調査したときの内容を備忘として残しました。
開発環境のソースは下記のリポジトリにあります。
ライフサイクルとは
k6テストのライフサイクルでは、スクリプトは常に以下の順序でこれらのステージを実行します:
- initコンテキストのコード:スクリプトの準備を行い、ファイルの読み込み、モジュールのインポート、テストライフサイクル関数の定義を行います。必須です。
- setup関数の実行:テスト環境のセットアップとデータの生成を行います。オプションです。
- デフォルトまたはシナリオ関数内でのVUコードの実行:オプションで定義された通りに、長さと回数を指定して実行します。必須です。
- teardown関数の実行:データの後処理とテスト環境のクローズを行います。オプションです。
ライフサイクル機能 init コードを除いて、各ステージはライフサイクル関数、つまり k6 ランタイムの特定のシーケンスで呼び出される関数で発生します。
// 1. init code
export function setup() {
// 2. setup code
}
export default function (data) {
// 3. VU code
}
export function teardown(data) {
// 4. teardown code
}
ライフサイクル段階の概要
各段階の例と実装の詳細については、後続のセクションを参照してください。
- init ローカルファイルの読み込み、モジュールのインポート、ライフサイクル関数の宣言 JSONファイルを開く、モジュールをインポート 各VUごとに1回*
- Setup 処理のためのデータセットアップ、データのVU間共有 APIを呼び出してテスト環境をセットアップ 1回
- VUコード テスト関数の実行、通常はデフォルトの関数 httpsリクエストを作成し、レスポンスを検証 イテレーションごとに1回、テストオプションで必要な回数だけ
- Teardown セットアップコードの結果の処理、テスト環境の停止 セットアップが特定の結果を持っていることを検証し、テストが完了したことを通知するWebhookを送信 1回 **
Setup 関数が異常終了した場合 (エラーがスローされるなど)、teardown() 関数は呼び出されません。エラーを処理し、適切なクリーンアップを確保するために、setup() 関数にロジックを追加することを検討してください。
initステージ
initステージは必須です。テストが実行される前に、k6はテスト条件を初期化する必要があります。テストを準備するために、initコンテキスト内のコードは各VUごとに1回実行されます。
initステージで行われる可能性がある操作には以下が含まれます:
- モジュールのインポート
- ローカルファイルシステムからファイルの読み込み
- すべてのオプションに対してテストの設定
- VU、setup、teardownステージのライフサイクル関数の定義(カスタムまたはhandleSummary()関数の場合も同様)
- ライフサイクル関数以外のすべてのコードはinitコンテキスト内のコードです。initコンテキスト内のコードは常に最初に実行されます。
// init context: importing modules
import http from 'k6/http';
import { Trend } from 'k6/metrics';
// init context: define k6 options
export const options = {
vus: 10,
duration: '30s',
};
// init context: global variables
const customTrend = new Trend('oneCustomMetric');
// init context: define custom function
function myCustomFunction() {
// ...
}
initステージをVUステージから分離することにより、VUコードから無関係な計算を除外することができ、k6のパフォーマンスが向上し、テスト結果が信頼性が高くなります。initコードの制限の1つは、HTTPリクエストを行うことができないことです。この制限により、initステージがテスト間で再現可能であることが確保されます(プロトコルリクエストの応答は動的で予測不可能です)。
VUステージ
スクリプトには、少なくともVUのロジックを定義するシナリオ関数が含まれている必要があります。この関数内のコードがVUコードです。通常、VUコードはデフォルトの関数内にありますが、シナリオで定義された関数内にあることもあります(例については後のセクションを参照してください)。
export default function () {
// do things here...
}
VUコードはテストの期間中に繰り返し実行されます。VUコードはHTTPリクエストを作成し、メトリクスを発行し、一般的には負荷テストが行うすべての操作を実行できます。唯一の例外はinitコンテキスト内で行われるジョブです。
VUコードはローカルファイルシステムからファイルを読み込むことはできません。 VUコードは他のモジュールをインポートすることはありません。 再度、VUコードの代わりに、これらのジョブはinitコードで実行されます。
デフォルト関数のライフサイクル
VUは、デフォルト()関数をシーケンスの最初から最後まで実行します。VUが関数の最後に達すると、最初に戻り、コードを全体で再実行します。
この「再起動」プロセスの一環として、k6はVUをリセットします。クッキーがクリアされ、TCP接続が切断されることがある(テスト構成オプションに依存します)。
セットアップおよびティアダウンステージ
デフォルトと同様に、セットアップおよびティアダウン関数もエクスポートされた関数である必要があります。ただし、デフォルト関数とは異なり、k6はセットアップとティアダウンをテストごとに1回だけ呼び出します。
セットアップはテストの開始時に呼び出され、initステージの後でVUステージの前に呼び出されます。 ティアダウンはテストの最後に、VUステージ(デフォルト関数)の後に呼び出されます。
セットアップおよびティアダウンステージでは、initステージとは異なり、完全なk6 APIを呼び出すことができます。例えば、HTTPリクエストを行うことができます:
import http from 'k6/http';
export function setup() {
const res = http.get('https://httpbin.test.k6.io/get');
return { data: res.json() };
}
export function teardown(data) {
console.log(JSON.stringify(data));
}
export default function (data) {
console.log(JSON.stringify(data));
}
セットアップとティアダウンステージの実行をスキップするには、オプション–no-setupおよび–no-teardownを使用できます。
k6 run --no-setup --no-teardown ...
セットアップからデータをデフォルトとティアダウンに使用するには、次の基本的なk6テストの構造を見てみましょう:
// 1. init code
export function setup() {
// 2. setup code
}
export default function (data) {
// 3. VU code
}
export function teardown(data) {
// 4. teardown code
}
おそらく、default() と teardown() 関数の関数シグネチャが、ここでは data と呼ばれる引数を受け取ることに気付いたかもしれません。
以下は、セットアップコードからデータをVUおよびティアダウンステージに渡す例です:
export function setup() {
return { v: 1 };
}
export default function (data) {
console.log(JSON.stringify(data));
}
export function teardown(data) {
if (data.v != 1) {
throw new Error('incorrect data: ' + JSON.stringify(data));
}
}
たとえば、セットアップ() 関数によって返されたデータを使用することで、次のことができます:
- 各VUにデータの同一のコピーへのアクセスを提供
- ティアダウンコード内でデータの事後処理
ただし、いくつかの制限があります。
- セットアップと他のステージの間でデータ(つまりJSON)のみを渡すことができます。関数を渡すことはできません。
- セットアップ() 関数によって返されるデータが大きい場合、より多くのメモリを消費します。
- デフォルト() 関数でデータを操作してから、それをティアダウン() 関数に渡すことはできません。
各ステージと各VUが、セットアップ() 関数が返すデータの新鮮な「コピー」にアクセスできると考えるのが最善です。
可変データをすべてのVU間およびティアダウン間で渡すことは、特に分散型セットアップでは非常に複雑で計算負荷が高くなる可能性があります。これはk6の主要な目標に反することになります:同じスクリプトは複数のモードで実行可能であるべきです。
追加のライフサイクル機能
k6にはライフサイクル関数を使用するさらなる方法がいくつかあります:
- handleSummary()。カスタムのサマリーを作成したい場合、k6はテストの最後にもう1つのライフサイクル関数を呼び出します。詳細については「カスタムサマリー」を参照してください。
- シナリオ関数。デフォルト関数の代わりに、シナリオ関数でVUコードを実行することもできます。
import http from 'k6/http';
import { sleep } from 'k6';
export const options = {
scenarios: {
my_web_test: {
// the function this scenario will execute
exec: 'webtest',
executor: 'constant-vus',
vus: 50,
duration: '1m',
},
},
};
export function webtest() {
http.get('https://test.k6.io/contacts.php');
sleep(Math.random() * 2);
}
おわりに
今日は、k6におけるライフサイクルについてご紹介しました。
何か質問や相談があれば、コメントをお願いします。また、エンジニア案件の相談にも随時対応していますので、お気軽にお問い合わせください。
それでは、また明日お会いしましょう(^^)
コメント