0
0

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?

新規受託案件にてCursor(AIエージェント) + k6でAPIの負荷試験を効率化した話

Last updated at Posted at 2025-12-11

はじめに

bravesoft株式会社でエンジニアをしているはっしーです。
今回新規受託案件にて負荷試験の実施をするにあたってCursor+k6を使った負荷試験の環境構築から簡単なテストスクリプトを用意したところまでを記事にしてみました

k6とは

Go言語で開発されたオープンソースの高性能な負荷試験ツールです。
テストシナリオをJavaScript(またはTypeScript)で記述し、アプリケーションのパフォーマンス、信頼性、耐久性をテストするために使用されています。

テストシナリオはJavaScriptまたはTypeScriptで記述します。これにより、Web開発者が使い慣れた言語で複雑なテストロジックを簡単に構築できます。

環境構築していこう

Cursorに色々と指示しながらまずは環境を構築していきましょう

  • Docker
  • k6
  • Cursor
- k6にて負荷試験を行える環境を構築したい
- Node.jsプロジェクトとして初期化し、k6、@types/k6、typescript、biomeを開発依存パッケージとしてインストールするための**package.json**を作成してください
- k6のTypeScriptスクリプトをコンパイルするための**tsconfig.jsonを作成してください
- endPointは簡単に切り替えられるようenvに定義しておきたい
- docker+Kubernetes環境でk6が実行できるようにしてほしい
- node_modulesはgitignoreして
- READMEを用意して
など

記事用のサンプルなので指示が大雑把な指示になってますが実際は案件によって細かく指示出す方がいいでしょう

こんな感じでシンプルな環境が用意できました

スクリーンショット 2025-12-08 20.34.25.png

簡単なテストスクリプトを実行してみる

1.簡単なここではお知らせリストを取得して既読にするような簡単なテストを書く

notice-read-test.ts
import { check, sleep } from 'k6';
// Notice list API test script (standalone version)
// For integrated tests, use integrated-test.ts instead
import http from 'k6/http';
import { API_CONFIG } from '../config.js';

// --- マスク処理のヘルパー関数を定義 ---
/**
 * 文字列の最初と最後を残し、中間をアスタリスクでマスクします。
 * @param str マスク対象の文字列
 * @returns マスクされた文字列
 */
const maskString = (str: string, visibleLength = 4) => {
  if (!str || str.length <= visibleLength * 2) {
    return '***MASKED***';
  }
  const start = str.substring(0, visibleLength);
  const end = str.substring(str.length - visibleLength);
  return `${start}**********${end}`;
};
// ------------------------------------

// Test configuration
export const options = {
  vus: 1,
  iterations: 1,
  duration: '1s',
};

// Test function
export default function () {
  const appToken = API_CONFIG.appToken;
  const maskedAppToken = maskString(appToken || ''); 

  // Create headers with authorization
  const headers = {
    ...API_CONFIG.headers,
    'Authorization': `Bearer ${appToken}`,
    'XXXXX': '',
    'YYYYY': '',
  };

  // Query parameters
  const page = __ENV.NOTICE_PAGE || '1';
  const type = __ENV.NOTICE_TYPE || 'game';

  // Build query string manually (k6 doesn't have URLSearchParams)
  const queryString = `page=${page}&type=${type}`;
  const url = `${API_CONFIG.baseUrl}/v1/notice/list?${queryString}`;
  const maskedUrlPath = `/v1/notice/list?${queryString}`;
  console.log(`Making request to: ${API_CONFIG.baseUrl} [Path: ${maskedUrlPath}]`);

  const response = http.get(url, { headers });

  const success = check(response, {
    'status is 200': (r) => r.status === 200,
    'response time < 2000ms': (r) => r.timings.duration < 2000,
    'response has data': (r) => {
      try {
        const body = JSON.parse(r.body as string);
        return body.result !== undefined || body.data !== undefined;
      } catch {
        return false;
      }
    },
  });

  if (success) {
    console.log('✅ Notice list API test passed');
    console.log(`Using Token: ${maskedAppToken}`);
    try {
      const body = JSON.parse(response.body as string);
      console.log(
        `Response: ${JSON.stringify(body, null, 2).substring(0, 500)}...`
      );
    } catch (e) {
      console.log(`Response: ${response.body}`);
    }
  } else {
    console.error('❌ Notice list API test failed');
    console.error(`Status: ${response.status}`);
    console.error(`Response: ${response.body}`); 
    console.error(`Attempted Token: ${maskedAppToken}`);
  }

  sleep(1);
}

2.作成したスクリプトをk6で実行してみる

$ cd xxxxxxx_k6 && export $(cat .env.local | grep -v '^#' | grep -v '^$' | xargs) && APP_TOKEN=XXXXXXX k6 run dist/test/notice-read-test.ts 2>&1

3.実行結果を確認してみる

こんな感じで結果が出力されるので結果を共有しやすい

スクリーンショット 2025-12-08 22.17.46.png

お知らせリスト取得: 成功
7件のお知らせを取得
ページ1で全件取得
お知らせ既読: 成功
7件すべての既読に成功
すべてのリクエストが200 OK
パフォーマンス
リクエスト数: 9件(リスト取得2件 + 既読7件)
平均レスポンス時間: 79.97ms
エラー率: 0%
すべてのチェック: 100%成功

📊 k6の主な指標

指標名 内容
checks チェック関数の成功割合
http_req_duration HTTPリクエストの完了にかかった合計時間
http_req_failed 失敗したHTTPリクエストの割合
iteration_duration 1回のイテレーションを完了するのにかかった時間
vus アクティブな仮想ユーザー (VU) の数
vus_max 最大仮想ユーザー (VU) 数
http_reqs テスト中に生成されたHTTPリクエストの総数
http_req_blocked リクエスト開始前にブロックされている時間
http_req_connecting TCP接続の確立にかかった時間

シナリオ作成について

  1. OpenAPIの仕様書をCursorに渡す
  2. 対象のAPIのリストとテスト条件などをプロンプトに渡して各テストのスクリプトのベースを用意してもらう
  3. 閾値の調整や、条件などの細かい調整

繰り返し指示を与えていくことでAIがよしなに作成してくれますが、意図する通りに動かないこともあるので必要に応じて開発者が閾値の調整やスクリプトを調整していくことは必要です

結果の可視化について

負荷試験の結果はメトリクスとして Prometheus に送りだすことで、Grafana ダッシュボードで可視化することができます。 他にも、New Relic や Datadog 等、SaaS ツールとの連携も可能です。

今回はそこまでやりませんがk6-reporterで一旦可視化してみました。

テストスクリプトに以下の関数を追加することで、テスト実行後に自動的にHTMLレポートが生成されます

// @ts-ignore - k6-reporterはCDNから読み込むため型定義がない
import { htmlReport } from 'https://raw.githubusercontent.com/benc-uk/k6-reporter/main/dist/bundle.js';

// HTMLレポートを生成(k6-reporterを使用)
export function handleSummary(data: unknown) {
  return {
    'results/summary.html': htmlReport(data),
  };
}

スクリーンショット 2025-12-09 2.34.28.png

今後やりたいこと

  • Grafana ダッシュボードで負荷試験の結果の可視化 
  • Cursorで基盤を作成できたので、量産はClaudCode MCPサーバーに情報を渡してテストに関する情報を渡して自動でテストスクリプトを作成してもらう
  • kubernetesで分散して負荷試験をできるようにする
  • 非エンジニア、QAメンバーでもテストスクリプトを組んでローカルでkubernetes + docker + k6実行できるようにする

まとめ

Cursorを効果的に活用し、k6による高性能な負荷試験環境を迅速に構築する手法を紹介しました。

このアプローチにより、開発初期段階からCI/CDに組み込む負荷試験環境を、エンジニアの作業コストを抑えつつ用意することが可能となると思います。

Grafanaでの可視化とLLMによるスクリプト自動生成は、負荷試験の質と効率を飛躍的に向上させる鍵となると思うので今後運用しながら色々と改善していければと思います。


bravesoftではエンジニアを募集しております。採用ページをご確認ください

0
0
0

Register as a new user and use Qiita more conveniently

  1. You get articles that match your needs
  2. You can efficiently read back useful information
  3. You can use dark theme
What you can do with signing up
0
0

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?