# C言語テスト実践チュートリアル

このチュートリアルでは、Linux/Windows クロスプラットフォーム環境において、Google Test フレームワークを使用して C言語プログラムをテストする方法を詳しく説明します。

# 1 目次

1. テストフレームワークの概要
2. プロジェクト構造
3. 環境構築
   - Linux 環境
   - Windows 環境
4. モックの作成方法
5. 関数のテスト方法
6. main関数を含むプログラムのテスト
7. makefileの作成
8. テストの実行
9. 実践例
10. ベストプラクティス
11. トラブルシューティング
    - 共通の問題
    - Windows 固有の問題

# 2 テストフレームワークの概要

本プロジェクトでは、Google Test (gtest/gmock) を使用してC言語プログラムのユニットテストを行います。
Linux では GCC、Windows では MSVC を使用したクロスプラットフォーム開発環境をサポートしています。

## 2.1 主な特徴

- クロスプラットフォーム対応: Linux (GCC) と Windows (MSVC) の両方でシームレスに動作
- C言語とC++の統合: C言語で書かれたコードをC++ (Google Test) でテスト
- モックフレームワーク: Google Mock を使用した依存関係のモック化
- リンカラップ機能: Linux では `-Wl,--wrap`、Windows では適切なリンカオプションを使用した関数の置き換え
- テストフェーズの明確化: Arrange, Pre-Assert, Act, Assert の4フェーズでテストを構造化

## 2.2 テストの4フェーズ

本フレームワークでは、テストコードを以下の4つのフェーズに分けて記述します:

1. Arrange [状態]: テストの初期状態を設定
2. Pre-Assert [Pre-Assert確認] [Pre-Assert手順]: モックの期待動作を設定
3. Act [手順]: テスト対象のメソッドを実行
4. Assert [確認]: 実行結果を検証

# 3 プロジェクト構造

    c-modernization-kit/
    +-- testfw/                           # テストフレームワーク (サブモジュール)
    |   +-- cmnd/                        # テスト支援コマンド
    |   +-- include/                     # フレームワーク提供のモック (stdio等)
    |   +-- include_override/            # オーバーライド用ヘッダー
    |   +-- libsrc/                      # フレームワーク提供のモック実装
    |   +-- docs-src/                    # テストフレームワークドキュメント
    +-- makefw/                           # Make ビルドフレームワーク (サブモジュール)
    |   +-- makefiles/                   # makefile テンプレート
    |       +-- prepare.mk              # 準備処理
    |       +-- makemain.mk             # ビルドルール生成
    +-- test/                             # テストコード (本プロジェクト固有)
    |   +-- include/                     # プロジェクト固有のモックヘッダー
    |   |   +-- mock_calc.h             # calcHandlerのモック
    |   |   +-- mock_calcbase.h         # add, subtract, multiply, divide のモック
    |   +-- libsrc/                      # プロジェクト固有のモック実装
    |   |   +-- mock_calc/              # calcHandler モックライブラリ
    |   |   |   +-- makefile            # 標準テンプレート
    |   |   |   +-- makepart.mk         # プロジェクト固有の設定
    |   |   |   +-- mock_calc.cc
    |   |   |   +-- mock_calcHandler.cc
    |   |   +-- mock_calcbase/          # calcbase 関数モックライブラリ
    |   |       +-- makefile            # 標準テンプレート
    |   |       +-- makepart.mk         # プロジェクト固有の設定
    |   |       +-- mock_calcbase.cc
    |   |       +-- mock_add.cc
    |   |       +-- mock_subtract.cc
    |   |       +-- mock_multiply.cc
    |   |       +-- mock_divide.cc
    |   +-- src/                         # テストコード
    |       +-- calc/
    |           +-- libcalcbaseTest/    # ライブラリ関数のテスト
    |           |   +-- addTest/
    |           |       +-- makefile    # 標準テンプレート
    |           |       +-- makepart.mk # プロジェクト固有の設定
    |           |       +-- addTest.cc
    |           +-- main/                # main関数を含むプログラムのテスト
    |               +-- addTest/
    |               |   +-- makefile    # 標準テンプレート
    |               |   +-- makepart.mk # プロジェクト固有の設定
    |               |   +-- addTest.cc
    |               +-- calcTest/
    |                   +-- makefile    # 標準テンプレート
    |                   +-- makepart.mk # プロジェクト固有の設定
    |                   +-- calcTest.cc
    +-- prod/                             # テスト対象のソースコード
        +-- calc/
            +-- include/
            |   +-- libcalcbase.h       # 静的リンク用API
            |   +-- libcalc.h           # 動的リンク用API
            |   +-- libcalc_const.h     # 定数定義
            +-- libsrc/
            |   +-- calcbase/
            |       +-- add.c           # add関数の実装
            |       +-- subtract.c      # subtract関数の実装
            |       +-- multiply.c      # multiply関数の実装
            |       +-- divide.c        # divide関数の実装
            +-- src/
                +-- add/
                |   +-- add.c           # addコマンドのmain関数
                +-- calc/
                    +-- calc.c          # calcコマンドのmain関数

# 4 環境構築

## 4.1 Linux 環境

### 4.1.1 必要なパッケージ

    sudo apt-get install -y \
        build-essential \
        g++ \
        make \
        libgtest-dev \
        libgmock-dev \
        gcovr \
        lcov

    sudo dnf install -y \
        gcc \
        g++ \
        make \
        gtest-devel \
        gmock-devel \
        python3-pip

    sudo pip3 install gcovr

## 4.2 Windows 環境

### 4.2.1 必要なツール

- Visual Studio Build Tools (または Visual Studio) - MSVC コンパイラとリンカ
- GNU Make - Make ビルドシステム (make.exe)
- Git for Windows - MinGW ツールチェーンを含む
- Google Test/Mock - テストフレームワーク

### 4.2.2 環境設定スクリプト

Windows では、`Start-VSCode-With-Env.ps1` を実行して VS Code を起動します:

    .\Start-VSCode-With-Env.ps1

## 4.3 サブモジュールの初期化

    cd /path/to/c-modernization-kit
    git submodule update --init --recursive

## 4.4 ワークスペースルートの設定

プロジェクトのルートディレクトリに `.workspaceRoot` ファイルを作成します:

    touch .workspaceRoot

    New-Item .workspaceRoot -ItemType File

    type nul > .workspaceRoot

このファイルにより、makefile がプロジェクトルートを自動検出できます。

# 5 モックの作成方法

モックを作成することで、テスト対象のコードが依存する関数の動作を制御できます。

## 5.1 ステップ1: モックヘッダーファイルの作成

`test/include/mock_xxxxx.h` にモックヘッダーを作成します。

### 5.1.1 例: mock_calcbase.h

    #ifndef _MOCK_CALCBASE_H
    #define _MOCK_CALCBASE_H

    #include <stdio.h>
    #include <gmock/gmock.h>

    // テスト対象のヘッダーをインクルード
    #include <libcalcbase.h>

    // モッククラスの定義
    class Mock_calcbase
    {
    public:
        // MOCK_METHOD マクロで関数をモック化
        // 構文: MOCK_METHOD(戻り値の型, 関数名, (引数リスト));
        MOCK_METHOD(int, add, (int, int, int *));
        MOCK_METHOD(int, subtract, (int, int, int *));
        MOCK_METHOD(int, multiply, (int, int, int *));
        MOCK_METHOD(int, divide, (int, int, int *));

        Mock_calcbase();
        ~Mock_calcbase();
    };

    // グローバルなモックインスタンスへのポインタ
    extern Mock_calcbase *_mock_calcbase;

    #endif // _MOCK_CALCBASE_H

### 5.1.2 ポイント

- MOCK_METHOD マクロ: Google Mock の機能で、関数のシグネチャを定義します
- extern宣言: モックインスタンスをグローバルに共有するための宣言
- C++クラス: C言語の関数をC++のモッククラスでラップします

## 5.2 ステップ2: モッククラスの実装

`test/libsrc/mock_xxxxx/mock_xxxxx.cc` にモッククラスの実装を作成します。

### 5.2.1 例: mock_calcbase.cc

    #include <testfw.h>
    #include <mock_calcbase.h>

    // グローバルインスタンスの実体
    Mock_calcbase *_mock_calcbase = nullptr;

    // コンストラクタ: デフォルト動作を設定
    Mock_calcbase::Mock_calcbase()
    {
        // デフォルト動作を設定 (オプション)
        ON_CALL(*this, add(_, _, _))
            .WillByDefault(Invoke([](int a, int b, int *result) {
                *result = a + b;
                return CALC_SUCCESS;
            })); // モックの既定の挙動を定義する例

        ON_CALL(*this, subtract(_, _, _))
            .WillByDefault(Return(CALC_SUCCESS)); // 一般的にはモックの既定の挙動は NOP にしておき、テストプログラムで具体的な挙動を決める

        ON_CALL(*this, multiply(_, _, _))
            .WillByDefault(Return(CALC_SUCCESS));

        ON_CALL(*this, divide(_, _, _))
            .WillByDefault(Return(CALC_SUCCESS));

        _mock_calcbase = this;
    }

    // デストラクタ: グローバルポインタをクリア
    Mock_calcbase::~Mock_calcbase()
    {
        _mock_calcbase = nullptr;
    }

## 5.3 ステップ3: モック関数の実装

`test/libsrc/mock_xxxxx/mock_関数名.cc` にC言語関数のモック実装を作成します。

### 5.3.1 例: mock_add.cc

    #include <testfw.h>
    #include <mock_calcbase.h>

    // C言語の関数として実装
    // この関数がテスト時に本物の add() 関数の代わりに呼ばれます
    // WEAK_ATR 属性により、リンク時に弱いシンボルとして扱われる
    WEAK_ATR int add(int a, int b, int *result)
    {
        int rtc = 0;

        // モックインスタンスが存在する場合はモックを呼び出す
        if (_mock_calcbase != nullptr)
        {
            rtc = _mock_calcbase->add(a, b, result);
        }

        // トレース出力 (デバッグ用)
        if (getTraceLevel() > TRACE_NONE)
        {
            printf("  > %s %d, %d, 0x%p", __func__, a, b, (void *)result);
            if (getTraceLevel() >= TRACE_DETAIL)
            {
                printf(" -> %d, %d\n", *result, rtc);
            }
            else
            {
                printf("\n");
            }
        }

        return rtc;
    }

### 5.3.2 ポイント

- WEAK_ATR 属性: リンク時に弱いシンボルとして扱われ、実装がない場合にモック関数が使用される
- C言語関数: `extern "C"` は不要 (`.cc` ファイルでも関数名が C++ にならない)
- モックインスタンスチェック: `_mock_calcbase != nullptr` でモックの有無を確認
- トレース機能: デバッグ時に関数呼び出しを確認できる

## 5.4 モックライブラリの makefile

`test/libsrc/mock_xxxxx/` ディレクトリに以下のファイルを作成します。

### 5.4.1 makefile (標準テンプレート)

`test/libsrc/mock_xxxxx/makefile` は標準テンプレートをそのまま使用します。

    find-up = \
        $(if $(wildcard $(1)/$(2)),$(1),\
            $(if $(filter $(1),$(patsubst %/,%,$(dir $(1)))),,\
                $(call find-up,$(patsubst %/,%,$(dir $(1))),$(2))\
            )\
        )
    WORKSPACE_FOLDER := $(strip $(call find-up,$(CURDIR),.workspaceRoot))

    include $(WORKSPACE_FOLDER)/makefw/makefiles/prepare.mk

    ##### makepart.mk の内容は、このタイミングで処理される #####

    include $(WORKSPACE_FOLDER)/makefw/makefiles/makemain.mk

### 5.4.2 makepart.mk (プロジェクト固有の設定)

`test/libsrc/mock_xxxxx/makepart.mk` にプロジェクト固有の設定を記述します。

    OUTPUT_DIR := $(WORKSPACE_FOLDER)/test/lib

    SRCS := \
        mock_calcbase.cc \
        mock_add.cc \
        mock_subtract.cc \
        mock_multiply.cc \
        mock_divide.cc

# 6 関数のテスト方法

通常の関数 (main を含まない) のテスト方法を説明します。

## 6.1 テストコードの基本構造

    #include <testfw.h>
    #include <mock_stdio.h>
    #include <libcalcbase.h>

    // テストフィクスチャクラス
    class addTest : public Test
    {
    };

    // テストケース
    TEST_F(addTest, test_1_add_2)
    {
        // Arrange (状態設定)
        int result;

        // Pre-Assert (期待動作設定)
        // - モックの期待動作を設定 (この例では不要)

        // Act (実行)
        int rtc = add(1, 2, &result); // [手順] - add(1, 2, &result) を呼び出す。

        // Assert (検証)
        EXPECT_EQ(CALC_SUCCESS, rtc); // [確認] - 戻り値が CALC_SUCCESS であること。
        EXPECT_EQ(3, result);         // [確認] - 結果が 3 であること。
    }

## 6.2 実践例: add関数のテスト

`test/src/calc/libcalcbaseTest/addTest/addTest.cc`

    #include <testfw.h>
    #include <mock_stdio.h>
    #include <libcalcbase.h>

    class addTest : public Test
    {
    };

    // 正の数の加算テスト
    TEST_F(addTest, test_1_add_2)
    {
        // Arrange
        int result;

        // Pre-Assert

        // Act
        int rtc = add(1, 2, &result); // [手順] - add(1, 2, &result) を呼び出す。

        // Assert
        EXPECT_EQ(CALC_SUCCESS, rtc); // [確認] - 戻り値が CALC_SUCCESS であること。
        EXPECT_EQ(3, result);         // [確認] - 結果が 3 であること。
    }

    // 交換法則のテスト
    TEST_F(addTest, test_2_add_1)
    {
        // Arrange
        int result;

        // Pre-Assert

        // Act
        int rtc = add(2, 1, &result); // [手順] - add(2, 1, &result) を呼び出す。

        // Assert
        EXPECT_EQ(CALC_SUCCESS, rtc); // [確認] - 戻り値が CALC_SUCCESS であること。
        EXPECT_EQ(3, result);         // [確認] - 結果が 3 であること。
    }

    // NULLポインタのテスト
    TEST_F(addTest, test_null_result)
    {
        // Arrange

        // Pre-Assert

        // Act
        int rtc = add(1, 2, NULL); // [手順] - add(1, 2, NULL) を呼び出す。

        // Assert
        EXPECT_EQ(CALC_ERROR, rtc); // [確認] - 戻り値が CALC_ERROR であること。
    }

# 7 main関数を含むプログラムのテスト

main関数を含むプログラムをテストするには、リンカラップ機能を使用します。

## 7.1 リンカラップ機能とは

GCCの `-Wl,--wrap=main` オプションを使用すると:

- `main` 関数は `__wrap_main` として定義される
- 元の `main` 関数は `__real_main` として参照可能になる
- `gtest_wrapmain` ライブラリが `__wrap_main` を提供し、Google Test を起動

## 7.2 テストコードの構造

    #include <testfw.h>
    #include <mock_stdio.h>
    #include <mock_calcbase.h>

    class addTest : public Test
    {
    };

    TEST_F(addTest, less_argc)
    {
        // Arrange
        int argc = 2;
        const char *argv[] = {"addTest", "1"}; // [状態] - main() に与える引数を、"1" **(不足)** とする。

        // Pre-Assert

        // Act
        int rtc = __real_main(argc, (char **)&argv); // [手順] - main() に引数を与えて呼び出す。

        // Assert
        EXPECT_NE(0, rtc); // [確認] - main() の戻り値が 0 以外であること。
    }

    TEST_F(addTest, normal)
    {
        // Arrange
        NiceMock<Mock_stdio> mock_stdio;
        Mock_calcbase mock_calcbase;
        int argc = 3;
        const char *argv[] = {"addTest", "1", "2"}; // [状態] - main() に与える引数を、"1", "2" とする。

        // Pre-Assert
        EXPECT_CALL(mock_calcbase, add(1, 2, _))
            .WillOnce([](int, int, int *result) {
                *result = 3;
                return CALC_SUCCESS;
            }); // [Pre-Assert確認] - add(1, 2, &result) が 1 回呼び出されること。
                // [Pre-Assert手順] - add(1, 2, &result) にて result に 3 を設定し、CALC_SUCCESS を返す。

        EXPECT_CALL(mock_stdio, printf(_, _, _, StrEq("3\n")))
            .WillOnce(DoDefault()); // [Pre-Assert確認] - printf() が 1 回呼び出され、内容が "3\n" であること。

        // Act
        int rtc = __real_main(argc, (char **)&argv); // [手順] - main() に引数を与えて呼び出す。

        // Assert
        EXPECT_EQ(0, rtc); // [確認] - main() の戻り値が 0 であること。
    }

## 7.3 NiceMock とは

`NiceMock<T>` は、未設定のモック呼び出しを許容するラッパーです。

- NiceMock: 未設定の呼び出しを許容 (警告なし)
- NaggyMock: 未設定の呼び出しで警告 (テストは通る)
- StrictMock: 未設定の呼び出しでテスト失敗

    // 例: stdio のモックは多数の関数があるため、
    // 全てを EXPECT_CALL で設定するのは煩雑
    // NiceMock を使うことで、必要な呼び出しだけを検証できる
    NiceMock<Mock_stdio> mock_stdio;

# 8 makefileの作成

## 8.1 テストコード用 makefile

テストディレクトリに以下のファイルを作成します。

### 8.1.1 makefile (標準テンプレート)

`test/src/calc/libcalcbaseTest/addTest/makefile` は標準テンプレートをそのまま使用します。

    find-up = \
        $(if $(wildcard $(1)/$(2)),$(1),\
            $(if $(filter $(1),$(patsubst %/,%,$(dir $(1)))),,\
                $(call find-up,$(patsubst %/,%,$(dir $(1))),$(2))\
            )\
        )
    WORKSPACE_FOLDER := $(strip $(call find-up,$(CURDIR),.workspaceRoot))

    include $(WORKSPACE_FOLDER)/makefw/makefiles/prepare.mk

    ##### makepart.mk の内容は、このタイミングで処理される #####

    include $(WORKSPACE_FOLDER)/makefw/makefiles/makemain.mk

### 8.1.2 makepart.mk (プロジェクト固有の設定)

`test/src/calc/libcalcbaseTest/addTest/makepart.mk` にプロジェクト固有の設定を記述します。

    TEST_SRCS := \
        $(WORKSPACE_FOLDER)/prod/calc/libsrc/calcbase/add.c

## 8.2 main関数テスト用 makefile

### 8.2.1 makefile (標準テンプレート)

`test/src/calc/main/addTest/makefile` は標準テンプレートをそのまま使用します。

    find-up = \
        $(if $(wildcard $(1)/$(2)),$(1),\
            $(if $(filter $(1),$(patsubst %/,%,$(dir $(1)))),,\
                $(call find-up,$(patsubst %/,%,$(dir $(1))),$(2))\
            )\
        )
    WORKSPACE_FOLDER := $(strip $(call find-up,$(CURDIR),.workspaceRoot))

    include $(WORKSPACE_FOLDER)/makefw/makefiles/prepare.mk

    ##### makepart.mk の内容は、このタイミングで処理される #####

    include $(WORKSPACE_FOLDER)/makefw/makefiles/makemain.mk

### 8.2.2 makepart.mk (プロジェクト固有の設定)

`test/src/calc/main/addTest/makepart.mk` にプロジェクト固有の設定を記述します。

    TEST_SRCS := \
        $(WORKSPACE_FOLDER)/prod/calc/src/add/add.c

    USE_WRAP_MAIN := 1

    LIBS += mock_calcbase mock_libc

## 8.3 makefile のポイント

### 8.3.1 makefile と makepart.mk の分離

- makefile: 標準テンプレート（すべてのテストディレクトリで共通）
- makepart.mk: プロジェクト固有の設定を記述

この分離により、ビルドシステムの更新が容易になり、保守性が向上します。

### 8.3.2 ワークスペースフォルダの検出

makefile テンプレート内で `find-up` 関数を使用して `.workspaceRoot` ファイルを検出し、プロジェクトルートを特定します。

    find-up = \
        $(if $(wildcard $(1)/$(2)),$(1),\
            $(if $(filter $(1),$(patsubst %/,%,$(dir $(1)))),,\
                $(call find-up,$(patsubst %/,%,$(dir $(1))),$(2))\
            )\
        )
    WORKSPACE_FOLDER := $(strip $(call find-up,$(CURDIR),.workspaceRoot))

### 8.3.3 テンプレートの読み込み順序

    include $(WORKSPACE_FOLDER)/makefw/makefiles/prepare.mk

    include $(WORKSPACE_FOLDER)/makefw/makefiles/makemain.mk

### 8.3.4 main関数のラップ

- `USE_WRAP_MAIN := 1`: main関数をラップして `__real_main` として呼び出し可能にする
  - Linux では `-Wl,--wrap=main` オプションが自動的に設定される
  - Windows では適切なリンカオプションが自動的に設定される

### 8.3.5 ライブラリの指定

- `LIBS`: リンクするライブラリ (プレフィックス `-l` なしで指定)
  - `mock_xxxxx`: モックライブラリ
  - `mock_libc`: 標準C関数のモック (stdio等)

例:

    LIBS += mock_calcbase mock_libc

# 9 テストの実行

## 9.1 全テストの実行

プロジェクトルートから:

    cd test
    make clean    # クリーンビルド
    make          # ビルド
    make test     # テスト実行

> Windows の注意: コマンドプロンプトで環境設定スクリプトを実行してから make を実行してください。

## 9.2 個別テストの実行

特定のテストディレクトリで:

    cd test/src/calc/libcalcbaseTest/addTest
    make test

## 9.3 特定のテストケースのみ実行

フィルター機能を使用:

    export GTEST_FILTER=*test_1_add_2*
    make test
    export -n GTEST_FILTER  # フィルターを解除

    make test GTEST_FILTER=*test_1_add_2*

    REM Windows (コマンドプロンプト)
    REM 方法1: 環境変数で指定
    set GTEST_FILTER=*test_1_add_2*
    make test
    set GTEST_FILTER=

    REM 方法2: makeコマンドに指定
    make test GTEST_FILTER=*test_1_add_2*

    $env:GTEST_FILTER="*test_1_add_2*"
    make test
    Remove-Item Env:\GTEST_FILTER

    make test GTEST_FILTER=*test_1_add_2*

## 9.4 カバレッジレポートの生成

    cd test
    make test  # テスト実行 (カバレッジデータ収集)

    gcovr --exclude-unreachable-branches

> 注意: Windows でのコードカバレッジ取得は、使用するツールによって手順が異なります。
> MSVC 環境では、Visual Studio のコードカバレッジツールまたはサードパーティツールの使用を検討してください。

## 9.5 テスト出力例

    [==========] Running 2 tests from 1 test suite.
    [----------] Global test environment set-up.
    [----------] 2 tests from addTest
    [ RUN      ] addTest.test_1_add_2
    [       OK ] addTest.test_1_add_2 (0 ms)
    [ RUN      ] addTest.test_2_add_1
    [       OK ] addTest.test_2_add_1 (0 ms)
    [----------] 2 tests from addTest (0 ms total)

    [----------] Global test environment tear-down
    [==========] 2 tests from 1 test suite ran. (0 ms total)
    [  PASSED  ] 2 tests.

# 10 実践例

## 10.1 例1: calcHandler 関数のモックを使用したテスト

`test/src/calc/main/calcTest/calcTest.cc`

    #include <testfw.h>
    #include <mock_stdio.h>
    #include <mock_calc.h>

    class calcTest : public Test
    {
    };

    // 引数不足のテスト
    TEST_F(calcTest, less_argc)
    {
        // Arrange
        int argc = 2;
        const char *argv[] = {"calcTest", "1"}; // [状態] - main() に与える引数を、"1" **(不足)** とする。

        // Pre-Assert

        // Act
        int rtc = __real_main(argc, (char **)&argv); // [手順] - main() に引数を与えて呼び出す。

        // Assert
        EXPECT_NE(0, rtc); // [確認] - main() の戻り値が 0 以外であること。
    }

    // 正常系のテスト
    TEST_F(calcTest, normal)
    {
        // Arrange
        NiceMock<Mock_stdio> mock_stdio;
        Mock_calc mock_calc;
        int argc = 4;
        const char *argv[] = {"calcTest", "1", "+", "2"}; // [状態] - main() に与える引数を、"1", "+", "2" とする。

        // Pre-Assert
        EXPECT_CALL(mock_calc, calcHandler(CALC_KIND_ADD, 1, 2, _))
            .WillOnce([](int, int, int, int *result) {
                *result = 3;
                return CALC_SUCCESS;
            }); // [Pre-Assert確認] - calcHandler(CALC_KIND_ADD, 1, 2, &result) が 1 回呼び出されること。
                // [Pre-Assert手順] - calcHandler(CALC_KIND_ADD, 1, 2, &result) にて result に 3 を設定し、CALC_SUCCESS を返す。

        EXPECT_CALL(mock_stdio, printf(_, _, _, StrEq("3\n")))
            .WillOnce(DoDefault()); // [Pre-Assert確認] - printf() が 1 回呼び出され、内容が "3\n" であること。

        // Act
        int rtc = __real_main(argc, (char **)&argv); // [手順] - main() に引数を与えて呼び出す。

        // Assert
        EXPECT_EQ(0, rtc); // [確認] - main() の戻り値が 0 であること。
    }

このテストの makepart.mk:

    TEST_SRCS := \
        $(WORKSPACE_FOLDER)/prod/calc/src/calc/calc.c

    USE_WRAP_MAIN := 1

    LIBS += mock_calc mock_libc

## 10.2 例2: モックの高度な使い方

### 10.2.1 呼び出し回数の検証

    TEST_F(MyTest, call_times_check)
    {
        Mock_calcbase mock;

        // 期待: add が正確に 2 回呼ばれること
        EXPECT_CALL(mock, add(_, _))
            .Times(2)
            .WillRepeatedly(Return(0));

        // 実際の呼び出し
        add(1, 2);
        add(3, 4);
        // テスト終了時に自動検証される
    }

### 10.2.2 呼び出し順序の検証

    TEST_F(MyTest, call_order_check)
    {
        Mock_calcbase mock;
        InSequence seq;  // 順序を保証

        EXPECT_CALL(mock, add(1, 2)).Times(1);
        EXPECT_CALL(mock, add(3, 4)).Times(1);

        // この順序で呼ばれる必要がある
        add(1, 2);
        add(3, 4);
    }

### 10.2.3 引数の詳細な検証

    TEST_F(MyTest, argument_check)
    {
        Mock_stdio mock_stdio;

        // 第4引数が "Hello" という文字列であることを検証
        EXPECT_CALL(mock_stdio, printf(_, _, _, StrEq("Hello\n")))
            .Times(1);

        printf("%s", "", "", "Hello\n");
    }

### 10.2.4 複数回呼び出しでの戻り値変化

    TEST_F(MyTest, multiple_returns)
    {
        Mock_calcbase mock;

        EXPECT_CALL(mock, add(_, _))
            .WillOnce(Return(10))      // 1回目は 10
            .WillOnce(Return(20))      // 2回目は 20
            .WillRepeatedly(Return(0)); // 3回目以降は 0

        EXPECT_EQ(10, add(1, 2));  // 1回目
        EXPECT_EQ(20, add(3, 4));  // 2回目
        EXPECT_EQ(0, add(5, 6));   // 3回目以降
    }

# 11 ベストプラクティス

## 11.1 1. テストコードの構造化

4つのフェーズを明確にコメントで記述:

    TEST_F(MyTest, test_case)
    {
        // Arrange
        // - テストデータの準備

        // Pre-Assert
        // - モックの期待動作設定

        // Act
        // - テスト対象の実行

        // Assert
        // - 結果の検証
    }

## 11.2 2. テストケースの命名規則

テストケース名は具体的かつ分かりやすく:

- Good: `test_add_positive_numbers`
- Good: `test_main_with_insufficient_args`
- Bad: `test1`, `test_normal`

## 11.3 3. モックのスコープ

モックオブジェクトはテストケース内で宣言:

    TEST_F(MyTest, test_case)
    {
        // このスコープ内でモックが有効
        Mock_calcbase mock;

        // テストコード
    }
    // スコープを抜けるとモックが破棄される

## 11.4 4. NiceMock の活用

複雑なモック (stdio等) は NiceMock を使用:

    // 多数の関数を持つモックは NiceMock で簡潔に
    NiceMock<Mock_stdio> mock_stdio;

    // 検証したい呼び出しのみ EXPECT_CALL で指定
    EXPECT_CALL(mock_stdio, printf(_, _, _, StrEq("result\n")))
        .WillOnce(DoDefault());

## 11.5 5. テストの独立性

各テストケースは独立して実行可能にする:

- テストケース間で状態を共有しない
- グローバル変数の使用は最小限に
- SetUp/TearDown を活用

    class MyTest : public Test
    {
    protected:
        void SetUp() override
        {
            // 各テストケース実行前の初期化
        }

        void TearDown() override
        {
            // 各テストケース実行後のクリーンアップ
        }
    };

## 11.6 6. エラーケースのテスト

正常系だけでなく異常系もテスト:

    // 正常系
    TEST_F(MyTest, normal_case) { /* ... */ }

    // 境界値
    TEST_F(MyTest, boundary_case) { /* ... */ }

    // エラーケース
    TEST_F(MyTest, error_case) { /* ... */ }

    // NULL ポインタ
    TEST_F(MyTest, null_pointer_case) { /* ... */ }

## 11.7 7. コメントの活用

テストの意図を日本語コメントで明確に:

    // Act
    int rtc = add(1, 2); // [手順] - add(1, 2) を呼び出す。

    // Assert
    EXPECT_EQ(3, rtc); // [確認] - 戻り値が 3 であること。

## 11.8 8. makefileの保守性

makefile と makepart.mk を分離:

- makefile: 標準テンプレート（すべてのディレクトリで共通、編集禁止）
- makepart.mk: プロジェクト固有の設定（TEST_SRCS, LIBS, USE_WRAP_MAIN など）

共通処理はフレームワークに集約:

- `makefw/makefiles/prepare.mk`: 準備処理
- `makefw/makefiles/makemain.mk`: ビルドルール生成

この分離により、ビルドシステムの更新が容易になり、保守性が向上します。

## 11.9 9. カバレッジの確認

定期的にカバレッジを確認:

    make test
    gcovr --exclude-unreachable-branches --html --html-details -o coverage.html

## 11.10 10. CI/CD への統合

テストを自動化:

    #!/bin/bash

    cd test
    make clean
    make
    make test

    if [ $? -ne 0 ]; then
        echo "Tests failed!"
        exit 1
    fi

    echo "All tests passed!"

    @echo off
    REM test-runner.bat (Windows)

    cd test
    make clean
    make
    make test

    if %ERRORLEVEL% neq 0 (
        echo Tests failed!
        exit /b 1
    )

    echo All tests passed!

## 11.11 11. クロスプラットフォーム開発のヒント

異なるプラットフォームでコードを保守する際のベストプラクティス:

### 11.11.1 プラットフォーム固有のコードを最小限に

- できるだけ標準C言語の機能のみを使用
- プラットフォーム固有の処理は条件付きコンパイルで分離

    #ifdef _WIN32
        // Windows 固有の処理
    #else
        // Linux 固有の処理
    #endif

### 11.11.2 ビルドシステムの活用

- makefw サブモジュールがプラットフォーム検出を自動化
- 各プラットフォームに適したコンパイラオプションを自動設定
- makefile は共通のテンプレートを使用

### 11.11.3 定期的なクロスプラットフォームテスト

- 両方のプラットフォームで定期的にテストを実行
- CI/CD パイプラインで両環境をテスト
- プラットフォーム固有の問題を早期に発見

### 11.11.4 ドキュメントの明確化

- プラットフォーム固有の手順を明記
- 環境設定の要件を文書化
- トラブルシューティング情報を共有

# 12 トラブルシューティング

## 12.1 共通の問題

### 12.1.1 リンクエラー: undefined reference

原因: 必要なライブラリがリンクされていない

解決策: makefile の `LIBS` に追加

    LIBS += -lmock_calcbase -ltest_com

### 12.1.2 多重定義エラー: multiple definition

原因: 同じシンボルが複数回定義されている

解決策:
- モック関数の実装ファイルを確認
- `TEST_SRCS` に同じソースを重複して追加していないか確認

### 12.1.3 テストが実行されない

原因: `__real_main` が未定義

解決策: makepart.mk に `USE_WRAP_MAIN := 1` を追加

    USE_WRAP_MAIN := 1

これにより、ビルドシステムが自動的にプラットフォームに応じた適切なリンカオプションを設定します。

### 12.1.4 モックが呼ばれない

原因: リンク順序の問題

解決策:
1. モックライブラリを makepart.mk の LIBS に追加
2. `TEST_SRCS` に実体のソースを含めない

    LIBS += mock_calcbase  # OK

## 12.2 Windows 固有の問題

### 12.2.1 環境変数が設定されていないエラー

原因: Visual Studio Build Tools の環境変数が設定されていない

解決策: `Start-VSCode-With-Env.ps1` で環境設定を実行

    . .\Start-VSCode-With-Env.ps1 -EnvOnly

### 12.2.2 ビルドエラー: コンパイラオプションの違い

原因: GCC と MSVC ではコンパイラオプションが異なる

解決策: makefw サブモジュールのビルドフレームワークが自動的にプラットフォームを検出し、
適切なコンパイラオプションを設定します。makefile の設定を確認してください。

# 13 参考資料

- GoogleTest User’s Guide
- GoogleMock for Dummies
- testfw サブモジュール内のドキュメント:
  - `testfw/docs-src/how-to-mock.md`
  - `testfw/docs-src/how-to-test.md`
  - `testfw/docs-src/about-test-phase.md`
  - `testfw/docs-src/how-to-expect.md`
  - `testfw/docs-src/how-to-extern.md`

# 14 まとめ

このチュートリアルでは、以下の内容を説明しました:

1. クロスプラットフォーム対応: Linux (GCC) と Windows (MSVC) の両方でシームレスに動作するテスト環境
2. 環境構築: Linux と Windows それぞれの環境設定手順
3. テストフレームワークの構造: Google Test を使用した C言語のテスト環境
4. モックの作成: ヘッダー、クラス、関数の3段階でのモック実装
   - WEAK_ATR 属性を使用したモック関数の実装
   - ON_CALL を使用したデフォルト動作の設定
5. 関数のテスト: 通常の関数のユニットテスト方法
   - ステータスコードとポインタ経由の結果取得パターン
6. main関数のテスト: リンカラップ機能を使用した main関数のテスト
   - `USE_WRAP_MAIN := 1` による自動設定
7. makefileの作成: makefile と makepart.mk の分離による保守性の向上
   - 標準テンプレート（makefile）とプロジェクト固有の設定（makepart.mk）の分離
   - makefw サブモジュールによるクロスプラットフォーム対応
8. 実践例: 実際のコードを使った具体的なテスト例
9. トラブルシューティング: プラットフォーム固有の問題と解決方法

これらの知識を活用して、Linux/Windows 両対応の堅牢で保守性の高いテストコードを作成してください。