C++: ユニットテスト
最終更新:2026-08-31
レッスン49では、パフォーマンス最適化について学びました。
ここでは、ユニットテストについて学びます。
テスト可能なコードを書くことは、保守性の鍵です。
1. ユニットテストの基本
(1) 1.1 ユニットテストとは?
ユニットテスト(Unit Test)は最小単位(関数、クラス)のテストです。
利点:
- バグを早期発見
- リファクタリングが可能
- ドキュメントとして機能
(2) 1.2 テスティングフレームワーク
| フレームワーク | 説明 |
|---|---|
| Google Test | Google製、標準的 |
| Catch2 | ヘッダオンリ、使いやすい |
| Boost.Test | Boostの一部 |
| Doctest | Catch2に似ている、高速 |
推奨: Google Test(標準的)または Catch2(手軽)
2. Google Test
(1) 2.1 Google Testのインストール
Linux:
sudo apt-get install libgtest-dev
Windows: vcpkgまたは手動コンパイル。
(2) 2.2 基本的なテスト
▶ サンプル 2:コード例(難易度 ⭐)
#include <gtest/gtest.h>
int add(int a, int b) {
return a + b;
}
TEST(AddTest, Positive) {
EXPECT_EQ(add(2, 3), 5);
}
TEST(AddTest, Negative) {
EXPECT_EQ(add(-2, -3), -5);
}
int main(int argc, char** argv) {
::testing::InitGoogleTest(&argc, argv);
return RUN_ALL_TESTS();
}
出力:
TEXT 📖 参照専用[==========] Running 2 tests from 1 test suite. [ PASSED ] 2 tests.
3. アサーションマクロ
(1) 3.1 ASSERT vs EXPECT
| マクロ | 失敗時の動作 |
|---|---|
ASSERT_* |
即座に停止(テスト全体) |
EXPECT_* |
継続(複数の検証) |
推奨: EXPECT_*(より多くの情報を得られる)
(2) 3.2 よく使うマクロ
| マクロ | 説明 |
|---|---|
EXPECT_EQ(a, b) |
a == b |
EXPECT_NE(a, b) |
a != b |
EXPECT_LT(a, b) |
a < b |
EXPECT_GT(a, b) |
a > b |
EXPECT_TRUE(cond) |
条件が真 |
EXPECT_FALSE(cond) |
条件が偽 |
EXPECT_THROW(statement, exception_type) |
例外を投げる |
4. テストフィクスチャ
(1) 4.1 なぜ必要?
問題: テスト間で共通のコードがある。
解決策: テストフィクスチャ(testing::Test を継承)
(2) 4.2 例:テストフィクスチャでキューをテスト(難易度 ⭐⭐)
#include <gtest/gtest.h>
#include <queue>
class QueueTest : public testing::Test {
protected:
void SetUp() override {
q.push(1);
q.push(2);
q.push(3);
}
void TearDown() override {
// クリーンアップ(必要なら)
}
std::queue<int> q;
};
TEST_F(QueueTest, Size) {
EXPECT_EQ(q.size(), 3);
}
TEST_F(QueueTest, Front) {
EXPECT_EQ(q.front(), 1);
}
int main(int argc, char** argv) {
::testing::InitGoogleTest(&argc, argv);
return RUN_ALL_TESTS();
}
5. テスト駆動開発(TDD)
(1) 5.1 TDDとは?
TDD(Test-Driven Development)はテストを先に書き、コードを後に書く手法です。
サイクル:
- テストを書く(失敗)
- コードを書く(成功)
- リファクタリング
(2) 5.2 例:TDDでスタックを実装(難易度 ⭐⭐⭐)
// 1. テストを書く
TEST(StackTest, PushAndPop) {
Stack<int> s;
s.push(42);
EXPECT_EQ(s.pop(), 42);
}
// 2. 最小限の実装
template<typename T>
class Stack {
std::vector<T> data;
public:
void push(const T& x) { data.push_back(x); }
T pop() { T x = data.back(); data.pop_back(); return x; }
};
6. 実践:クラスのテスト
▶ サンプル 1:完全なテスト例(難易度 ⭐⭐⭐)
#include <gtest/gtest.h>
#include <string>
class Student {
std::string name;
int age;
public:
Student(const std::string& name, int age) : name(name), age(age) {}
std::string getName() const { return name; }
int getAge() const { return age; }
};
TEST(StudentTest, Constructor) {
Student s("田中", 20);
EXPECT_EQ(s.getName(), "田中");
EXPECT_EQ(s.getAge(), 20);
}
int main(int argc, char** argv) {
::testing::InitGoogleTest(&argc, argv);
return RUN_ALL_TESTS();
}
▶ サンプル 3:エッジケースのテスト(難易度 ⭐⭐)
#include <gtest/gtest.h>
int divide(int a, int b) {
if (b == 0) {
throw std::invalid_argument("ゼロ除算");
}
return a / b;
}
TEST(DivideTest, NormalCase) {
EXPECT_EQ(divide(10, 2), 5);
EXPECT_EQ(divide(9, 3), 3);
}
TEST(DivideTest, EdgeCases) {
EXPECT_EQ(divide(0, 5), 0); // 分子がゼロ
EXPECT_EQ(divide(5, 5), 1); // 同じ値
EXPECT_EQ(divide(-10, 2), -5); // 分子が負
}
TEST(DivideTest, ExceptionOnZero) {
EXPECT_THROW(divide(10, 0), std::invalid_argument);
}
int main(int argc, char** argv) {
::testing::InitGoogleTest(&argc, argv);
return RUN_ALL_TESTS();
}
出力:
TEXT 📖 参照専用[==========] Running 3 tests from 1 test suite. [ PASSED ] 3 tests.
❓ よくある質問
Q:ユニットテストでバグを防げる? A:いいえ。ユニットテストは特定の種類のエラーを防げます。統合テスト、システムテストも必要。
Q:テストコードと本番コードの比率は? A:プロジェクトによりますが、テストコードの方が長いことも。
Q:プライベート関数をテストする? A:- 1. パブリックインターフェースをテスト(推奨)
- FRIEND_TESTマクロでアクセス
- リファクタリングして関数を別クラスに抽出
📖 まとめ
| トピック | 要点 |
|---|---|
| ユニットテスト | 最小単位のテスト |
| Google Test | 標準的なC++テスティング |
| アサーションマクロ | EXPECT_* と ASSERT_* |
| テストフィクスチャ | 共通コードの整理 |
| TDD | テストを先に書き、コードを後に書く |
📝 練習問題
-
初級(難易度 ⭐): add(a, b) 関数を書き、Google Testで3つのテストケースを書く:正の数、負の数、ゼロ。
-
中級(難易度 ⭐⭐): isPalindrome 関数(文字列が回文か判定)を書く。5つのテストケース:true、false、空文字、大文字小文字、数字入力。
-
上級(難易度 ⭐⭐⭐): 「銀行口座」クラス(deposit/withdraw/getBalance)を書き、ユニットテストを作成。正常、異常、並行性(オプション)をテスト。
- ユニットテストは関数やモジュール単位の検証
- テスティングフレームワーク:Google Test / Catch2 / doctest
- テストケース:準備→実行→検証
- テスト駆動開発(TDD):テストを先に書いてからコードを書く
- テストフィクスチャで共通の準備コードを整理
次のレッスン:プロジェクト:住所録システム(#51)