cpp-unit-testing について
AI コーディングエージェント向けの C++ ユニットテストスキルです。一貫したテストパターン、ワークフロー、テンプレートを使い、プロジェクト内のテスト作成と実行を支援します。
主な機能
- 01FIRST テスト原則
- 02In-Got-Want と AAA パターン
- 03テーブル駆動・データ駆動テスト
- 04フィクスチャとテストダブル
- 05CMake へのテスト登録手順
- 06一般的なテストケースのテンプレート
活用シーン
- 01新しい C++ 関数のテスト作成
- 02境界値や無効入力のケース追加
- 03チーム内でのテスト構造の統一
インストール
コマンドをコピーしてターミナルで実行します。
npx skills add https:/ / github.com/ sentenz/ skills --skill cpp-unit-testingインストール後、Claude Code で/cpp-unit-testingを入力して読み込まれたか確認します。
詳細
単体テスト
このC++プロジェクトで一貫したソフトウェアテストパターンを用いて単体テストの作成を自動化するための、AIコーディングエージェント向けの手順です。
1. 利点
-
可読性
高いコード品質と信頼性を確保します。テスト自体がドキュメントの役割を果たすため、レビュー担当者や保守担当者の認知負荷を軽減します。
-
一貫性
テスト全体で構造を統一することで、チームメンバーが効率よく把握できる、予測しやすく見慣れたコードになります。
-
拡張性
テーブル駆動やデータ駆動のアプローチにより、新しいテストケースを追加する際の定型コードを最小限に抑え、カバレッジを簡単に拡張できます。
-
デバッグのしやすさ
スコープ付きのトレースと詳細なアサーションメッセージにより、継続的インテグレーションやローカルテストでの失敗箇所をすばやく特定できます。
2. 原則
2.1. FIRST
単体テストにおけるFIRSTの原則は、効果的で保守しやすいテストを作成することを重視します。
-
高速(Fast)
開発や継続的インテグレーションで迅速なフィードバックが得られるよう、単体テストは短時間で実行できる必要があります。
-
独立性(Independent)
各単体テストは自己完結し、ほかのテストの状態や動作に依存しないようにします。
-
再現性(Repeatable)
実行環境や実行順序にかかわらず、単体テストは実行するたびに決定的な結果を返す必要があります。
-
自己検証可能(Self-Validating)
単体テストは、手動で確認しなくても合格または不合格が明確にわかる必要があります。
-
適時性(Timely)
問題をできるだけ早く検出できるよう、開発プロセスの早い段階で単体テストを作成して実行する必要があります。
3. パターン
3.1. In-Got-Want
In-Got-Wantパターンでは、各テストケースを3つの明確なセクションに分けます。
-
In
テストへの入力パラメーターまたは条件を定義します。
-
Got
テスト対象のコードが生成した実際の出力または結果を記録します。
-
Want
テストで検証する期待される出力または結果を指定します。
3.2. テーブル駆動テスト
テーブル駆動テストでは、テストケースを表形式で整理することで、複数のシナリオを簡潔に定義できます。
-
テストケースの構造
テーブルの各行は、それぞれ異なる入力と期待される出力を持つ個別のテストケースを表します。
-
反復処理
テストフレームワークは各行を反復処理し、異なるデータで同じテストロジックを実行します。
3.3. データ駆動テスト(DDT)
データ駆動テストでは、テストデータをテストロジックから分離することで、複数の入力データセットを使って同じテストロジックを実行できます。
-
外部データソース
テストデータは外部ファイル(JSON、CSVなど)に保存し、実行時に読み込むことができます。
-
再利用性
同じテストロジックを異なるデータセットで再利用できるため、保守性とカバレッジが向上します。
3.4. Arrange, Act, Assert(AAA)
AAAパターンでは、各テストケースを3つの明確なフェーズに分けます。
-
Arrange
テストに必要な前提条件と入力を設定します。
-
Act
テスト対象の関数またはメソッドを実行します。
-
Assert
実際の出力が期待される出力と一致することを検証します。
3.5. テストフィクスチャ
テストフィクスチャは、テストケースで一貫性のある再利用可能なセットアップおよび後処理の仕組みを提供します。
-
セットアップ
複数のテストで必要となる共通のオブジェクトまたは状態を初期化します。
-
後処理
各テストの後にリソースをクリーンアップするか、状態をリセットします。
3.6. テストダブル
テストダブル(モック、スタブ、フェイクなど)は、テスト対象の単位を分離するために使用する、複雑なオブジェクトやコンポーネントの簡略版です。
-
モック
実際のオブジェクトの振る舞いをシミュレートし、やり取りを検証します。
-
スタブ
完全な振る舞いを実装せずに、メソッド呼び出しに対して事前に定義された応答を返します。
-
フェイク
機能を制限した、実際のオブジェクトの簡略版を実装します。
4. ワークフロー
-
特定する
src/にある新しい関数を特定します(例:src/<module>/<header>.hpp)。 -
追加/作成する
ソースコードと同じ場所に新しいテストを作成します(例:
src/<module>/<header>_test.cpp)。 -
CMake に登録する
適切なオプション(例:
WITH_DDT)を指定してmeta_gtest()を使用し、テストファイルをsrc/<module>/CMakeLists.txtに追加します。テスト構成では、
META_BUILD_TESTING変数を使ったENABLEオプションを使用してください。include(meta_gtest) meta_gtest( ENABLE ${META_BUILD_TESTING} TARGET ${PROJECT_NAME}-test SOURCES <header>_test.cpp LINK ${PROJECT_NAME}::<module> ) -
テストカバレッジの要件
包括的なエッジケースを含めます。
- カバレッジガイド付きのケース
- 境界値(最小値/最大値の制限、しきい値の境界)
- 空または null の入力
- null ポインターおよび無効な参照
- オーバーフロー/アンダーフローのケース
- 特殊なケース(負の数、ゼロ、特殊な状態)
-
テンプレートを適用する
以下のテンプレートパターンを使用して、すべてのテストを構成します。
5. コマンド
| コマンド | 説明 |
|---|---|
make cmake-gcc-test-unit-build | CMake プリセットを構成し、Ninja でコンパイルします |
make cmake-gcc-test-unit-run | ctest でテストを実行します |
make cmake-gcc-test-unit-coverage | ctest でテストを実行し、カバレッジレポートを生成します |
6. スタイルガイド
-
テストフレームワーク
#include <gtest/gtest.h>を使って、GoogleTest (GTest) フレームワークを使用します。 -
ヘッダーのインクルード
必要な標準ライブラリのヘッダー(
<vector>、<string>、<climits>など)と、モジュール固有のヘッダーを論理的な順序でインクルードします。システムヘッダーを先に、その後にプロジェクトのヘッダーを記述します。必要なヘッダーは次の順序でインクルードします。
- GTest/GMock のヘッダー(
<gtest/gtest.h>、<gmock/gmock.h>) - 標準ライブラリのヘッダー(
<memory>、<string>など) - プロジェクトのインターフェースヘッダー
- プロジェクトの実装ヘッダー
- GTest/GMock のヘッダー(
-
名前空間
テストのスコープは限定されているため、明瞭さを保ちながら記述を簡潔にできるよう、テスト関数内では便宜上
using namespace <namespace>;を使用します。 -
テストの構成
1つの関数に対するテストケースは、テーブル駆動テストを使って1つの
TEST(...)関数にまとめます。この方法には、次の利点があります。
- テスト関数の定義の重複をなくせます
- 関連するシナリオをまとめることで、メンテナンスが簡単になります
- セットアップとティアダウンの処理におけるコードの重複を減らせます
- テストケースの追加や変更が容易になります
-
テストマクロ
各
TEST(...)関数は、1つの関数または一貫性のある動作に焦点を当てます。複雑なセットアップには、TEST_Fフィクスチャまたはヘルパー関数を使って重複を減らします。 -
モック
Google Mock (GMock) を使って、テスト対象の単体を分離するためのテストダブル(モック、スタブ、フェイク)を作成します。cpp-mock-testing スキルを参照してください。
-
トレーサビリティ
テーブル駆動テストで失敗箇所を追跡できるよう、
SCOPED_TRACE(tc.label)を使用します。 -
アサーション
すべてのテストケースを実行できるよう、
ASSERT_*ではなくEXPECT_*マクロを使用します。
7. テンプレート
新しい単体テストには、以下のテンプレートを使用します。プレースホルダーを実際の値に置き換えてください。
7.1. ファイルヘッダーテンプレート
#include <gtest/gtest.h>
#include <string>
#include <vector>
#include "<module>/<header>.hpp"
using namespace <namespace>;
7.2. テーブル駆動テストテンプレート
TEST(<Module>Test, <FunctionName>)
{
// In-Got-Want
struct Tests
{
std::string label;
struct In
{
/* input types and names */
} in;
struct Want
{
/* expected output type(s) and name(s) */
} want;
};
// Table-Driven Testing
const std::vector<Tests> tests = {
{"case-description-1", {/* input */}, {/* expected */}},
{"case-description-2", {/* input */}, {/* expected */}},
};
for (const auto &tc : tests)
{
SCOPED_TRACE(tc.label);
// Arrange
<Module> <object>;
// Act
auto got = <object>.<function>(tc.in.<input>);
// Assert
EXPECT_EQ(got, tc.want.<expected>);
}
}
7.3. テストフィクスチャテンプレート
class <Module>Test : public ::testing::Test
{
protected:
void SetUp() override
{
// Initialize common objects or state
}
void TearDown() override
{
// Clean up resources or reset state
}
<Module> object_;
};
TEST_F(<Module>Test, <FunctionName>)
{
// Arrange
auto input = <input_value>;
// Act
auto got = object_.<function>(input);
// Assert
EXPECT_EQ(got, <expected>);
}
7.4. 例外テストテンプレート
TEST(<Module>Test, <FunctionName>ThrowsOnInvalidInput)
{
// Arrange
<Module> object;
auto invalid_input = <invalid_value>;
// Act & Assert
EXPECT_THROW(object.<function>(invalid_input), <ExceptionType>);
}
7.5. 境界値テストテンプレート
TEST(<Module>Test, <FunctionName>BoundaryValues)
{
// In-Got-Want
struct Tests
{
std::string label;
struct In
{
<input_type> input;
} in;
struct Want
{
<output_type> expected;
} want;
};
// Table-Driven Testing with boundary cases
const std::vector<Tests> tests = {
{"minimum-value", {<MIN_VALUE>}, {/* expected */}},
{"maximum-value", {<MAX_VALUE>}, {/* expected */}},
{"zero-value", {0}, {/* expected */}},
{"empty-input", {{}}, {/* expected */}},
{"negative-value", {-1}, {/* expected */}},
};
for (const auto &tc : tests)
{
SCOPED_TRACE(tc.label);
// Arrange
<Module> object;
// Act
auto got = object.<function>(tc.in.input);
// Assert
EXPECT_EQ(got, tc.want.expected);
}
}
7.6. データ駆動テストテンプレート(JSON)
#include <nlohmann/json.hpp>
#include <fstream>
TEST(<Module>Test, <FunctionName>DataDriven)
{
// Load test data from JSON file
std::ifstream file("<module>/<header>_test.json");
nlohmann::json test_data;
file >> test_data;
for (const auto &tc : test_data["tests"])
{
SCOPED_TRACE(tc["label"].get<std::string>());
// Arrange
<Module> object;
auto input = tc["in"]["input"].get<<input_type>>();
auto expected = tc["want"]["expected"].get<<output_type>>();
// Act
auto got = object.<function>(input);
// Assert
EXPECT_EQ(got, expected);
}
}