第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ファイルが含まれている
  • [ ] ヘッダファイルのインクルードガードが正しい
  • 7.2 機能

  • [ ] ex00: 全てのテストケースが通過
  • [ ] ex01: ADD/SEARCH/EXITが正しく動作
  • [ ] ex02: ログ出力が期待と一致
  • 7.3 コード品質

  • [ ] Orthodox Canonical Form(ex01, ex02)
  • [ ] 禁止関数を使用していない(printf, malloc等)
  • [ ] メモリリークがない
  • 7.4 ファイル

  • [ ] Makefileが正しく動作
  • [ ] 不要なファイル(.o, 実行ファイル)がない
  • [ ] 正しいディレクトリ構造
  • ---

    まとめ

    本章で学んだこと:

  • コンパイルエラーの解決: よくあるエラーと対処法
  • テストケースの設計: 正常系とエッジケース
  • デバッグ技法: printf、GDB、Valgrind
  • よくある間違い: 典型的なバグパターン
  • 提出前チェック: 品質保証

これらのスキルは、今後のCPP Moduleでも活用できます。