第3章:テストとデバッグ

はじめに

本章では、CPP Module 06のテストとデバッグ方法を解説します。

---

1. ScalarConverterのテスト

1.1 文字リテラルのテスト

void testCharLiterals() {
    std::cout << "=== Character Literals ===" << std::endl;

    // 表示可能文字
    ScalarConverter::convert("a");
    // char: 'a'
    // int: 97
    // float: 97.0f
    // double: 97.0

    ScalarConverter::convert("Z");
    // char: 'Z'
    // int: 90
    // float: 90.0f
    // double: 90.0

    ScalarConverter::convert("*");
    // char: '*'
    // int: 42
    // float: 42.0f
    // double: 42.0
}

1.2 整数リテラルのテスト

void testIntLiterals() {
    std::cout << "=== Integer Literals ===" << std::endl;

    ScalarConverter::convert("0");
    ScalarConverter::convert("42");
    ScalarConverter::convert("-42");
    ScalarConverter::convert("2147483647");   // INT_MAX
    ScalarConverter::convert("-2147483648");  // INT_MIN
    ScalarConverter::convert("2147483648");   // オーバーフロー
}

1.3 浮動小数点リテラルのテスト

void testFloatDoubleLiterals() {
    std::cout << "=== Float/Double Literals ===" << std::endl;

    ScalarConverter::convert("0.0");
    ScalarConverter::convert("42.0f");
    ScalarConverter::convert("-4.2f");
    ScalarConverter::convert("3.14159");
}

1.4 特殊値のテスト

void testSpecialValues() {
    std::cout << "=== Special Values ===" << std::endl;

    // Float特殊値
    ScalarConverter::convert("nanf");
    // char: impossible
    // int: impossible
    // float: nanf
    // double: nan

    ScalarConverter::convert("+inff");
    ScalarConverter::convert("-inff");

    // Double特殊値
    ScalarConverter::convert("nan");
    ScalarConverter::convert("+inf");
    ScalarConverter::convert("-inf");
}

1.5 エラーケースのテスト

void testErrorCases() {
    std::cout << "=== Error Cases ===" << std::endl;

    ScalarConverter::convert("");          // 空文字列
    ScalarConverter::convert("hello");     // 無効な文字列
    ScalarConverter::convert("42.42.42");  // 複数のドット
    ScalarConverter::convert("--42");      // 複数の符号
    ScalarConverter::convert("42ff");      // 無効なサフィックス
}

---

2. Serializerのテスト

2.1 基本テスト

void testSerializerBasic() {
    std::cout << "=== Serializer Basic Test ===" << std::endl;

    Data data;
    data.id = 42;
    data.name = "Test";

    uintptr_t raw = Serializer::serialize(&data);
    Data* result = Serializer::deserialize(raw);

    // 同じポインタを返すか確認
    assert(result == &data);
    std::cout << "Pointer comparison: PASSED" << std::endl;

    // データが正しいか確認
    assert(result->id == 42);
    assert(result->name == "Test");
    std::cout << "Data integrity: PASSED" << std::endl;
}

2.2 複数データのテスト

void testSerializerMultiple() {
    std::cout << "=== Serializer Multiple Test ===" << std::endl;

    Data data1, data2, data3;
    data1.id = 1;
    data2.id = 2;
    data3.id = 3;

    uintptr_t raw1 = Serializer::serialize(&data1);
    uintptr_t raw2 = Serializer::serialize(&data2);
    uintptr_t raw3 = Serializer::serialize(&data3);

    // それぞれ異なる値
    assert(raw1 != raw2);
    assert(raw2 != raw3);
    assert(raw1 != raw3);

    // 正しくデシリアライズ
    assert(Serializer::deserialize(raw1)->id == 1);
    assert(Serializer::deserialize(raw2)->id == 2);
    assert(Serializer::deserialize(raw3)->id == 3);

    std::cout << "Multiple data: PASSED" << std::endl;
}

---

3. 型識別のテスト

3.1 ポインタ版のテスト

void testIdentifyPointer() {
    std::cout << "=== Identify Pointer Test ===" << std::endl;

    A a;
    B b;
    C c;

    Base* pa = &a;
    Base* pb = &b;
    Base* pc = &c;

    std::cout << "Expected A: ";
    identify(pa);

    std::cout << "Expected B: ";
    identify(pb);

    std::cout << "Expected C: ";
    identify(pc);
}

3.2 参照版のテスト

void testIdentifyReference() {
    std::cout << "=== Identify Reference Test ===" << std::endl;

    A a;
    B b;
    C c;

    Base& ra = a;
    Base& rb = b;
    Base& rc = c;

    std::cout << "Expected A: ";
    identify(ra);

    std::cout << "Expected B: ";
    identify(rb);

    std::cout << "Expected C: ";
    identify(rc);
}

3.3 generate()のランダム性テスト

void testGenerate() {
    std::cout << "=== Generate Randomness Test ===" << std::endl;

    int countA = 0, countB = 0, countC = 0;

    for (int i = 0; i < 100; i++) {
        Base* ptr = generate();
        if (dynamic_cast<A*>(ptr)) countA++;
        else if (dynamic_cast<B*>(ptr)) countB++;
        else if (dynamic_cast<C*>(ptr)) countC++;
        delete ptr;
    }

    std::cout << "A: " << countA << std::endl;
    std::cout << "B: " << countB << std::endl;
    std::cout << "C: " << countC << std::endl;

    // すべての型が少なくとも1回は生成されているはず
    assert(countA > 0);
    assert(countB > 0);
    assert(countC > 0);
    std::cout << "All types generated: PASSED" << std::endl;
}

---

4. よくある間違い

4.1 ScalarConverter: リテラル判定の順序

// 間違い: 整数をfloat/doubleとして判定
if (hasDecimalPoint(literal)) {
    // ...
} else {
    // intとして処理 - これが先でよい
}

// 正しい順序
// 1. 特殊値(nan, inf)
// 2. 単一文字
// 3. float(末尾がf)
// 4. double(小数点あり)
// 5. int

4.2 ScalarConverter: 精度の問題

// 間違い: setprecisionを忘れる
std::cout << "float: " << f << "f" << std::endl;
// 出力: float: 42f (小数点なし)

// 正しい
std::cout << std::fixed << std::setprecision(1);
std::cout << "float: " << f << "f" << std::endl;
// 出力: float: 42.0f

4.3 Serializer: NULLポインタ

// NULLポインタも正しくシリアライズ
Data* ptr = NULL;
uintptr_t raw = Serializer::serialize(ptr);  // 0
Data* result = Serializer::deserialize(raw); // NULL

4.4 identify: dynamic_cast参照版

// 間違い: ポインタ版と同じ方法
A* a = dynamic_cast<A*>(&base);
if (a) { ... }  // これはポインタ版

// 正しい: 例外を使用
try {
    A& a = dynamic_cast<A&>(base);
    std::cout << "A" << std::endl;
    return;
} catch (std::bad_cast&) {
    // 次の型を試す
}

4.5 仮想デストラクタを忘れる

// 間違い: dynamic_castが動作しない
class Base {
    // 仮想関数がない
};

// 正しい
class Base {
public:
    virtual ~Base() {}  // 仮想デストラクタ
};

---

5. 提出前チェックリスト

ex00

  • [ ] 単一文字リテラルを正しく変換
  • [ ] 整数リテラルを正しく変換
  • [ ] floatリテラル(fサフィックス)を正しく変換
  • [ ] doubleリテラルを正しく変換
  • [ ] 特殊値(nan, inf)を正しく処理
  • [ ] 範囲外の値は"impossible"
  • [ ] 非表示文字は"Non displayable"
  • [ ] 小数点以下1桁で表示
  • ex01

  • [ ] Data構造体に意味のあるメンバ
  • [ ] serialize()がポインタをuintptr_tに変換
  • [ ] deserialize()がuintptr_tをポインタに変換
  • [ ] 元のポインタと同じものが返る
  • ex02

  • [ ] Baseに仮想デストラクタ
  • [ ] A, B, CがBaseを継承
  • [ ] generate()がランダムに生成
  • [ ] identify(Base*)がポインタから識別
  • [ ] identify(Base&)が参照から識別
  • [ ] 参照版はポインタを使用しない
  • ---

    まとめ

    CPP Module 06のテストでは以下を確認:

  • スカラー変換: すべてのリテラル形式と特殊値
  • シリアライズ: ポインタの往復変換
  • 型識別: ポインタと参照の両方の版