第3章:テストとデバッグ
はじめに
本章では、CPP Module 00の課題をテストし、デバッグする方法を学びます。適切なテストケースの設計と、よくある間違いの対処法を解説します。
---
1. コンパイルとエラー解決
1.1 コンパイルオプション
42のCPP Moduleでは以下のオプションでコンパイルする必要があります:
c++ -Wall -Wextra -Werror -std=c++98 *.cpp -o program
| オプション | 意味 |
|-----------|------|
| -Wall | 基本的な警告を有効化 |
| -Wextra | 追加の警告を有効化 |
| -Werror | 警告をエラーとして扱う |
| -std=c++98 | C++98標準に準拠 |
1.2 よくあるコンパイルエラー
未定義の参照(undefined reference):
undefined reference to `Account::_nbAccounts'
原因:静的メンバ変数がソースファイルで定義されていない
// 解決方法:ソースファイルで定義を追加
int Account::_nbAccounts = 0;
型の不一致:
error: invalid conversion from 'int' to 'const char*'
原因:間違った型の値を渡している
オーバーロード関数の曖昧さ:
error: call of overloaded 'function(int)' is ambiguous
原因:複数の関数候補が同等に一致
1.3 警告の修正
// 警告: 未使用の変数
int unused; // warning: unused variable 'unused'
// 解決: 変数を削除または使用
// 警告: 符号付き/符号なしの比較
int i;
size_t len;
if (i < len) // warning: comparison between signed and unsigned
// 解決: 型を統一
size_t i;
---
2. ex00: Megaphoneのテスト
2.1 基本テストケース
# テスト1: 通常の入力
./megaphone "hello world"
# 期待: HELLO WORLD
# テスト2: 複数の引数
./megaphone "hello" "world"
# 期待: HELLOWORLD(スペースなし)
# テスト3: 引数なし
./megaphone
# 期待: * LOUD AND UNBEARABLE FEEDBACK NOISE *
# テスト4: 空の引数
./megaphone ""
# 期待: (空の出力、改行のみ)
# テスト5: スペースのみ
./megaphone " "
# 期待: (スペースはそのまま)
2.2 エッジケース
# 数字と特殊文字
./megaphone "hello123!@#"
# 期待: HELLO123!@#
# 既に大文字
./megaphone "HELLO"
# 期待: HELLO
# 混合
./megaphone "HeLLo WoRLD"
# 期待: HELLO WORLD
# 長い入力
./megaphone "$(printf 'a%.0s' {1..1000})"
# 期待: A...A(1000個のA)
2.3 テストスクリプト
#!/bin/bash
# test_megaphone.sh
RED='\033[0;31m'
GREEN='\033[0;32m'
NC='\033[0m'
test_case() {
local description="$1"
local expected="$2"
shift 2
local result=$("$@")
if [ "$result" = "$expected" ]; then
echo -e "${GREEN}PASS${NC}: $description"
else
echo -e "${RED}FAIL${NC}: $description"
echo " Expected: $expected"
echo " Got: $result"
fi
}
# テスト実行
test_case "Normal input" "HELLO WORLD" ./megaphone "hello world"
test_case "Multiple args" "HELLOWORLD" ./megaphone "hello" "world"
test_case "No args" "* LOUD AND UNBEARABLE FEEDBACK NOISE *" ./megaphone
test_case "Numbers" "HELLO123" ./megaphone "hello123"
---
3. ex01: PhoneBookのテスト
3.1 機能テスト
ADDコマンド:
Enter command: ADD
First Name: John
Last Name: Doe
Nickname: JD
Phone Number: 123-456-7890
Darkest Secret: Afraid of spiders
Contact added successfully!
SEARCHコマンド:
Enter command: SEARCH
Index|First Name| Last Name| Nickname
--------------------------------------------
0| John| Doe| JD
Enter index to view details: 0
First Name: John
Last Name: Doe
Nickname: JD
Phone Number: 123-456-7890
Darkest Secret: Afraid of spiders
EXITコマンド:
Enter command: EXIT
(プログラム終了)
3.2 エッジケース
空の電話帳での検索:
Enter command: SEARCH
PhoneBook is empty.
8件を超える追加:
# 9番目のcontactを追加
# 最初のcontactが上書きされる
無効なインデックス:
Enter index to view details: -1
Invalid index.
Enter index to view details: 10
Index out of range.
Enter index to view details: abc
Invalid index.
長い文字列の切り詰め:
First Name: VeryLongName
# 表示: VeryLongN.
空入力の拒否:
First Name:
Input cannot be empty.
First Name:
EOFの処理(Ctrl+D):
Enter command: ^D
(プログラムがクリーンに終了)
3.3 対話テスト
# 自動テスト用入力ファイル
cat << EOF | ./phonebook
ADD
John
Doe
JD
123-456-7890
Secret1
SEARCH
0
EXIT
EOF
---
4. ex02: Accountのテスト
4.1 期待される出力
提供されるテストファイルと一致するかを確認:
# タイムスタンプを除外して比較
./account 2>&1 | sed 's/\[[0-9_]*\]/[TIMESTAMP]/g' > output.txt
cat expected_output.txt | sed 's/\[[0-9_]*\]/[TIMESTAMP]/g' > expected.txt
diff output.txt expected.txt
4.2 チェックポイント
コンストラクタのログ:
[YYYYMMDD_HHMMSS] index:0;amount:42;created
[YYYYMMDD_HHMMSS] index:1;amount:54;created
...
displayAccountsInfos:
[YYYYMMDD_HHMMSS] accounts:8;total:20049;deposits:0;withdrawals:0
入金操作:
[YYYYMMDD_HHMMSS] index:0;p_amount:42;deposit:5;amount:47;nb_deposits:1
出金操作(成功):
[YYYYMMDD_HHMMSS] index:0;p_amount:47;withdrawal:5;amount:42;nb_withdrawals:1
出金操作(失敗):
[YYYYMMDD_HHMMSS] index:0;p_amount:47;withdrawal:1000;refused
デストラクタのログ(逆順):
[YYYYMMDD_HHMMSS] index:7;amount:XXX;closed
[YYYYMMDD_HHMMSS] index:6;amount:XXX;closed
...
[YYYYMMDD_HHMMSS] index:0;amount:XXX;closed
4.3 タイムスタンプの検証
// テスト用:固定タイムスタンプを使用
void Account::_displayTimestamp(void) {
#ifdef TEST_MODE
std::cout << "[19920104_091532] ";
#else
// 通常の実装
#endif
}
---
5. デバッグ技法
5.1 printfデバッグ
void PhoneBook::add() {
std::cerr << "[DEBUG] add() called" << std::endl;
Contact newContact;
// ...
std::cerr << "[DEBUG] count before: " << count << std::endl;
// 追加処理
std::cerr << "[DEBUG] count after: " << count << std::endl;
}
5.2 GDBの使用
# デバッグ情報付きでコンパイル
c++ -g -std=c++98 *.cpp -o phonebook
# GDBで実行
gdb ./phonebook
(gdb) break PhoneBook::add
(gdb) run
(gdb) print count
(gdb) next
(gdb) continue
5.3 Valgrindでメモリチェック
valgrind --leak-check=full ./phonebook << EOF
ADD
John
Doe
JD
123
Secret
EXIT
EOF
期待される出力:
==12345== All heap blocks were freed -- no leaks are possible
---
6. よくある間違いと解決法
6.1 std::cin と std::getline の混在
問題:
int number;
std::cin >> number;
std::string line;
std::getline(std::cin, line); // 空行を読んでしまう
解決:
int number;
std::cin >> number;
std::cin.ignore(std::numeric_limits<std::streamsize>::max(), '\n');
std::string line;
std::getline(std::cin, line); // 正しく動作
6.2 配列の境界外アクセス
問題:
Contact contacts[8];
contacts[8] = newContact; // 境界外!
解決:
if (count < 8) {
contacts[count] = newContact;
count++;
}
6.3 静的メンバの初期化忘れ
問題:
// ヘッダ
class MyClass {
static int count;
};
// ソースファイルで初期化忘れ
// int MyClass::count = 0; // これが必要!
6.4 コピー代入演算子での自己代入
問題:
MyClass& operator=(const MyClass& other) {
delete data; // 自己代入時に問題
data = new int(*other.data);
return *this;
}
解決:
MyClass& operator=(const MyClass& other) {
if (this != &other) {
delete data;
data = new int(*other.data);
}
return *this;
}
6.5 EOF処理の忘れ
問題:
std::string input;
std::getline(std::cin, input); // EOFで無限ループの可能性
解決:
std::string input;
if (!std::getline(std::cin, input)) {
std::cout << std::endl;
break; // またはreturn
}
---
7. 提出前チェックリスト
7.1 コンパイル
- [ ]
-Wall -Wextra -Werror -std=c++98で警告なしにコンパイル - [ ] 全ての
.cppファイルが含まれている - [ ] ヘッダファイルのインクルードガードが正しい
- [ ] ex00: 全てのテストケースが通過
- [ ] ex01: ADD/SEARCH/EXITが正しく動作
- [ ] ex02: ログ出力が期待と一致
- [ ] Orthodox Canonical Form(ex01, ex02)
- [ ] 禁止関数を使用していない(printf, malloc等)
- [ ] メモリリークがない
- [ ] Makefileが正しく動作
- [ ] 不要なファイル(.o, 実行ファイル)がない
- [ ] 正しいディレクトリ構造
- コンパイルエラーの解決: よくあるエラーと対処法
- テストケースの設計: 正常系とエッジケース
- デバッグ技法: printf、GDB、Valgrind
- よくある間違い: 典型的なバグパターン
- 提出前チェック: 品質保証
7.2 機能
7.3 コード品質
7.4 ファイル
---
まとめ
本章で学んだこと:
これらのスキルは、今後のCPP Moduleでも活用できます。