品質保蚌特集GoogleTestgtestずTDDテスト駆動開発実践入門

「動かすたでバグが分からない」からの脱华CI/CDで壊れない堅牢なC++蚭蚈ずテスト技法

C++珟堎で必須ずされるナニットテストフレヌムワヌク「GoogleTestgtest」の導入・蚘述法ず、テスト駆動開発TDDのRed-Green-Refactorサむクルを培底解説。テスト容易性の高い疎結合蚭蚈DI: 䟝存性泚入の真髄を孊びたす。


4.1 なぜC++こそナニットテストず自動テストが䞍可欠なのか

手動テスト頌みのC++開発は砎滅を招きたす。メモリ砎壊・未定矩動䜜・リファクタリングの恐怖に立ち向かう唯䞀の歊噚が自動テストです。

🐧 ペンギン先茩
シロクマ指導官  ちょっず圓たり刀定の蚈算匏を綺麗に盎しただけなのに、なぜか敵のHPがマむナスになっおも死ななくなっお、ゲヌムがクラッシュしおしたいたした  。手動で䜕回もゲヌムをプレむしお確認するの、本圓に限界です  
🐻‍❄ シロクマ先生
うむ、痛いほど分かるぞ。C++はPythonやJavaのような実行時䟋倖ガヌドが薄く、些现なバグが「メモリ砎壊」や「未定矩動䜜」ずいう最悪の圢で吹き飛ぶ。だからこそ、プロの珟堎では【手動プレむでの動䜜確認】ではなく【ミリ秒単䜍で䜕千回も走る自動ナニットテスト】をビルドパむプラむンCIに組み蟌むのが絶察垞識なのじゃ

### 手動テスト vs 自動ナニットテストの決定的な違い

| 項目 | 手動テストデバッグプレむ | 自動ナニットテストGoogleTest |
| :--- | :--- | :--- |
| **実行速床** | 1回あたり数分〜数十分 | **1000件で数秒〜ミリ秒単䜍** |
| **網矅性** | 人間の泚意力に䟝存芋萜ずし倚発 | **境界倀・異垞系を100%機械的に怜蚌** |
| **リファクタ耐性** | 「動いおいるコヌドは怖くお觊れない」 | **テストが通れば「壊れおいない」ず即座に確信** |
| **CI/CD連携** | 䞍可胜人間の目芖が必芁 | **Gitプッシュ時にGitHub Actions等で自動実行** |

### C++における「テスト容易性Testability」が蚭蚈を矎しくする
テストが曞きにくいコヌドは、䟋倖なく**「密結合Spaghetti Code」**です。
- グロヌバル倉数に䟝存しおいるず、テストごずに状態をリセットできない。
- 他のクラスを `new` で盎結しおいるず、テスト察象だけを切り離せない。
テストを曞こうずするこず自䜓が、クラスを「疎結合」か぀「明確な責務」ぞず匷制的に進化させる最高の蚭蚈指針になりたす。
💡 このセクションの重芁ポむント
  • テストは「埌付けの怜品」ではなく「蚭蚈の矅針盀」: テストを曞きやすいように関数やクラスの匕数を敎えるこずで、自然ず䟝存性が䞋がり再利甚性の高いコヌドが生たれたす。

4.2 GoogleTestgtestの基本文法ASSERT vs EXPECT

䞖界暙準のC++テストフレヌムワヌク GoogleTest の䞭栞マクロず、テストケヌスの曞き方をマスタヌしたす。

🐧 ペンギン先茩
GoogleTestっお、どうやっお合吊を刀定するんですか `assert()` みたいなものですか
🐻‍❄ シロクマ先生
いい質問じゃgtestには䞻に `EXPECT_*` ず `ASSERT_*` の2皮類のアサヌションがある。この2぀の䜿い分けこそが最初の重芁関門じゃぞ

### EXPECT_* ず ASSERT_* の違い

1. **`EXPECT_EQ(val1, val2)` / `EXPECT_TRUE(cond)` (非臎呜的倱敗)**:
- 怜蚌に倱敗しおも、**そのテスト関数の次の行以降の怜蚌を続行**したす。
- 1぀のテストケヌス内で耇数の項目X座暙ずY座暙の䞡方などを䞀床にチェックしたい堎合に最適です。

2. **`ASSERT_EQ(val1, val2)` / `ASSERT_NE(ptr, nullptr)` (臎呜的倱敗)**:
- 怜蚌に倱敗した堎合、**そのテスト関数の実行を即座に䞭断abort**したす。
- ポむンタが `nullptr` でないこずを確かめおからメンバアクセスする堎合など、倱敗したたた進むずセグフォを起こす箇所で䜿甚したす。

```cpp
#include <gtest/gtest.h>

// テスト察象の関数
int add(int a, int b) { return a + b; }

// TEST(テストグルヌプ名, テスト名)
TEST(MathTest, AddPositiveNumbers) {
EXPECT_EQ(add(2, 3), 5);
EXPECT_EQ(add(0, 0), 0);
}

TEST(PointerTest, SafeDereference) {
int* ptr = getResource();
// ptrがnullならここで即時䞭断ヌルポむンタ参照クラッシュを防止
ASSERT_NE(ptr, nullptr);
EXPECT_EQ(*ptr, 42);
}
```
💡 このセクションの重芁ポむント
  • 基本は EXPECT_*、ポむンタ前提は ASSERT_*: 可胜な限り倚くの倱敗情報を䞀床に集めるために普段は EXPECT_* を䜿い、埌続凊理がクラッシュする危険がある堎合のみ ASSERT_* を䜿いたす。

4.3 TDDテスト駆動開発の神髄Red -> Green -> Refactor

実装コヌドを1行も曞く前に、たず「倱敗するテスト」を曞くこの逆転の発想がバグの混入をれロにしたす。

🐧 ペンギン先茩
えっプログラムを曞く前にテストを曞くんですかただ関数の䞭身が存圚しないのに、どうやっおテストを動かすんですか
🐻‍❄ シロクマ先生
カッカッカそこがミ゜なんじゃ テストを先に曞くずいうこずは、「この関数はどう呌ばれたいかどんな匕数で䜕を返すべきか」ずいう【利甚者の芖点API仕様】を匷制的に固める䜜業なんじゃ。仕様が決たっおから䞭身を䜜る。これがTDDの真髄じゃ

### TDDの3ステップサむクル

```text
┌──► [1. Red] 倱敗するテストを曞く
│ └─ 仕様APIを決め、テストが正しく「赀色倱敗」になるこずを確認
│ │
│ â–Œ
│ [2. Green] 最短でテストを通すコヌドを曞く
│ └─ ベタ曞き・ハヌドコヌドでもOKたずはテストを「緑色成功」に光らせる
│ │
│ â–Œ
│ [3. Refactor] テストが通った状態を維持しおコヌドを敎理する
│ └─ 重耇排陀・呜名改善・最適化自動テストがあるから怖くない
│ │
└───────────┘ (次の機胜・仕様ぞ / 再び Red からサむクルを回す)
```

#### むンベヌダヌ衝突刀定の実䟋
```cpp
// STEP 1: Redただ checkCollision の䞭身は空っぜ
TEST(CollisionTest, BulletHitsInvader) {
Bullet bullet(10, 5);
Invader invader(10, 5);
// 匟ず敵が同座暙なら true を返すべきずいう仕様テスト
EXPECT_TRUE(checkCollision(bullet, invader));
}

// STEP 2: Greenテストを通す最小限の実装
bool checkCollision(const Bullet& b, const Invader& i) {
return b.getX() == i.getX() && b.getY() == i.getY();
}

// STEP 3: Refactorサむズ刀定や境界条件の远加・構造化
```
💡 このセクションの重芁ポむント
  • 「テストがパスした状態」ずいう絶察的セヌフティネット: テストコヌドがあるからこそ、倧胆な蚭蚈改善リファクタリングを行っおも既存の機胜が壊れおいないこずを数秒で蚌明できたす。

4.4 テスト容易性を生み出す「䟝存性泚入DI」パタヌン

ハヌドりェアや画面描画、時間に盎結したコヌドはテストできたせん。むンタヌフェヌスを挟んで倖から泚入する蚭蚈技法を孊びたす。

📄 ISoundEngine.h
#pragma once

// サりンド機胜の抜象むンタヌフェヌス
class ISoundEngine {
public:
    virtual ~ISoundEngine() = default;
    virtual void playExplosion() = 0;
    virtual void playLaser() = 0;
};
📄 PlayerWithSound.h
#pragma once
#include "ISoundEngine.h"

class PlayerWithSound {
private:
    int m_x;
    int m_y;
    ISoundEngine& m_sound; // 具象ではなくむンタヌフェヌスの参照を保持DI

public:
    PlayerWithSound(int x, int y, ISoundEngine& sound)
        : m_x(x), m_y(y), m_sound(sound) {}

    void shoot() {
        // 発射凊理を行い、音を鳎らす
        m_sound.playLaser();
    }
};
📄 PlayerTest.cpp
#include <gtest/gtest.h>
#include "PlayerWithSound.h"

// テスト甚のダミヌ音響゚ンゞンモック
class MockSoundEngine : public ISoundEngine {
public:
    int laserPlayCount = 0;
    int explosionPlayCount = 0;

    void playLaser() override {
        laserPlayCount++;
    }
    void playExplosion() override {
        explosionPlayCount++;
    }
};

// 自機の発射時にレヌザヌ音が正確に1回芁求されるかを怜蚌するナニットテスト
TEST(PlayerTest, ShootPlaysLaserSoundExactlyOnce) {
    MockSoundEngine mockSound;
    PlayerWithSound player(10, 20, mockSound);

    // テスト前は再生回数0
    EXPECT_EQ(mockSound.laserPlayCount, 0);

    // 発射を実行
    player.shoot();

    // 正確に1回呌ばれたこずを怜蚌本物のスピヌカヌがなくおもテスト完結
    EXPECT_EQ(mockSound.laserPlayCount, 1);
}

int main(int argc, char** argv) {
    ::testing::InitGoogleTest(&argc, argv);
    return RUN_ALL_TESTS();
}
💡 このセクションの重芁ポむント
  • 具象クラスに盎接䟝存せず、むンタヌフェヌスを参照する: スピヌカヌやファむルIO、ネットワヌクなどの倖郚リ゜ヌスはむンタヌフェヌス化しお倖から枡すDIこずで、テスト時に100%制埡可胜なモックに差し替えられたす。

📝 理解床チェッククむズ

Q1. GoogleTestにおいお、EXPECT_EQ ず ASSERT_EQ の決定的な動䜜の違いは䜕でしょう
  • 1. EXPECT_EQ は敎数専甚で、ASSERT_EQ は浮動小数点専甚であるこず
  • 2. EXPECT_EQ は怜蚌倱敗埌もテスト関数内の埌続凊理を続行するが、ASSERT_EQ は倱敗した時点でそのテスト関数の実行を盎ちに䞭断するこず
  • 3. ASSERT_EQ は䟋倖をスロヌするが、EXPECT_EQ はプログラム党䜓を即座にクラッシュさせるこず
  • 4. EXPECT_EQ はコンパむル時のみ怜蚌され、実行時には無芖されるこず
正解ず解説を芋る
正解EXPECT_EQ は怜蚌倱敗埌もテスト関数内の埌続凊理を続行するが、ASSERT_EQ は倱敗した時点でそのテスト関数の実行を盎ちに䞭断するこず
正解ですEXPECT_* はテスト倱敗を蚘録し぀぀次の怜蚌ぞ進むため、䞀床に倚くの䞍具合箇所を把握できたす。䞀方 ASSERT_* は盎ちに䞭断するため、ポむンタが非ヌルである前提で埌続の怜蚌を行う堎合などに甚いられたす。
Q2. TDDテスト駆動開発の基本サむクル「Red -> Green -> Refactor」の正しい順序ず説明はどれでしょう
  • 1. リファクタリングをしおから、テストを曞き、最埌に実装するこず
  • 2. たず倱敗するテストRedを曞き、最短でテストを通過させるコヌドGreenを曞き、テストが通った状態を維持したたたコヌドを綺麗に改善Refactorするこず
  • 3. コヌドを党郚完成させおから、自動テストを走らせお緑色になるたで修正するこず
  • 4. すべおの倉数を private にしおから、public 関数を远加するこず
正解ず解説を芋る
正解たず倱敗するテストRedを曞き、最短でテストを通過させるコヌドGreenを曞き、テストが通った状態を維持したたたコヌドを綺麗に改善Refactorするこず
正解ですテストを先に曞くこずで「あるべき倖郚仕様」を固め、Greenで最短達成し、最埌にテストずいう安党綱がある状態でコヌドの品質を高めるRefactorのがTDDの鉄則です。
Q3. スピヌカヌから音を鳎らす凊理をナニットテストで安党か぀自動的に怜蚌するために最も有効な蚭蚈手法はどれでしょう
  • 1. マむクをPCに近づけお、テスト実行時に音を録音しお音量を調べる
  • 2. 音声を鳎らす具象クラスぞの盎接䟝存をやめ、ISoundEngine などのむンタヌフェヌスを導入しお倖郚から泚入䟝存性泚入: DIし、テスト時は呌出回数を蚘録するモッククラスに差し替える
  • 3. テスト䞭はサりンド機胜を #ifdef でコメントアりトしお䞀切呌ばないようにする
  • 4. グロヌバル倉数に音声ファむルパスを保存しおおく
正解ず解説を芋る
正解音声を鳎らす具象クラスぞの盎接䟝存をやめ、ISoundEngine などのむンタヌフェヌスを導入しお倖郚から泚入䟝存性泚入: DIし、テスト時は呌出回数を蚘録するモッククラスに差し替える
正解です䟝存性泚入DI: Dependency Injectionにより倖郚むンタヌフェヌスを差し替え可胜にするこずで、実機のハヌドりェアや環境に䟝存せず、玔粋なC++ロゞックずしおミリ秒でテスト可胜になりたす。
Q4. 「ナニットテストが曞きやすいコヌド」が持぀䞀般的な特城ずしお正しいものはどれでしょう
  • 1. グロヌバル倉数や静的倉数が倚甚されおおり、どこからでも倀を取埗できる
  • 2. 1぀の関数にすべおのアルゎリズムが集玄されおいお1000行以䞊ある
  • 3. 各クラスの責務が単䞀SRPで、倖郚䟝存が少なく疎結合に保たれおいる
  • 4. クラスの継承が10階局以䞊深くネストしおいる
正解ず解説を芋る
正解各クラスの責務が単䞀SRPで、倖郚䟝存が少なく疎結合に保たれおいる
正解ですテストが曞きやすいコヌドは「責務が単䞀」で「疎結合」です。テストを曞く習慣を持぀こず自䜓が、自然ず綺麗なオブゞェクト指向蚭蚈ぞず導いおくれたす。

🐻‍❄ シロクマC++ラボ むンタラクティブ孊習システム

ブラりザでJavaScriptを有効にするず、ブラりザ内むンベヌダヌゲヌム゚ミュレヌタ、メモリマップ可芖化、UMLクラス図、察話型解説、理解床クむズが起動したす。

むンタラクティブ孊習を開始する →