mcpField
C
cpp-unit-testing

cpp-unit-testing

github.com/sentenzテスト・品質336 回インストール今日更新
リポジトリ

cpp-unit-testing について

AI コーディングエージェント向けの C++ ユニットテストスキルです。一貫したテストパターン、ワークフロー、テンプレートを使い、プロジェクト内のテスト作成と実行を支援します。

主な機能

  1. 01FIRST テスト原則
  2. 02In-Got-Want と AAA パターン
  3. 03テーブル駆動・データ駆動テスト
  4. 04フィクスチャとテストダブル
  5. 05CMake へのテスト登録手順
  6. 06一般的なテストケースのテンプレート

活用シーン

  1. 01新しい C++ 関数のテスト作成
  2. 02境界値や無効入力のケース追加
  3. 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. ワークフロー

  1. 特定する

    src/ にある新しい関数を特定します(例:src/<module>/<header>.hpp)。

  2. 追加/作成する

    ソースコードと同じ場所に新しいテストを作成します(例:src/<module>/<header>_test.cpp)。

  3. 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>
    )
    
  4. テストカバレッジの要件

    包括的なエッジケースを含めます。

    • カバレッジガイド付きのケース
    • 境界値(最小値/最大値の制限、しきい値の境界)
    • 空または null の入力
    • null ポインターおよび無効な参照
    • オーバーフロー/アンダーフローのケース
    • 特殊なケース(負の数、ゼロ、特殊な状態)
  5. テンプレートを適用する

    以下のテンプレートパターンを使用して、すべてのテストを構成します。

5. コマンド

コマンド説明
make cmake-gcc-test-unit-buildCMake プリセットを構成し、Ninja でコンパイルします
make cmake-gcc-test-unit-runctest でテストを実行します
make cmake-gcc-test-unit-coveragectest でテストを実行し、カバレッジレポートを生成します

6. スタイルガイド

  • テストフレームワーク

    #include <gtest/gtest.h> を使って、GoogleTest (GTest) フレームワークを使用します。

  • ヘッダーのインクルード

    必要な標準ライブラリのヘッダー(<vector>、<string>、<climits> など)と、モジュール固有のヘッダーを論理的な順序でインクルードします。システムヘッダーを先に、その後にプロジェクトのヘッダーを記述します。

    必要なヘッダーは次の順序でインクルードします。

    1. GTest/GMock のヘッダー(<gtest/gtest.h>、<gmock/gmock.h>)
    2. 標準ライブラリのヘッダー(<memory>、<string> など)
    3. プロジェクトのインターフェースヘッダー
    4. プロジェクトの実装ヘッダー
  • 名前空間

    テストのスコープは限定されているため、明瞭さを保ちながら記述を簡潔にできるよう、テスト関数内では便宜上 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);
  }
}

8. 参考資料

  • GoogleTest の Primer ガイド。
  • GoogleTest の Advanced ガイド。

コメント