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

はじめに

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

---

1. Bitcoin Exchangeのテスト

1.1 データベースのテスト

void testDatabaseLoading() {
    std::cout << "=== Database Loading Test ===" << std::endl;

    BitcoinExchange btc;

    // 正常なデータベース
    try {
        btc.loadDatabase("data.csv");
        std::cout << "Database loaded: OK" << std::endl;
    } catch (const std::exception& e) {
        std::cout << "FAIL: " << e.what() << std::endl;
    }

    // 存在しないファイル
    try {
        BitcoinExchange btc2;
        btc2.loadDatabase("nonexistent.csv");
        std::cout << "FAIL: Should have thrown" << std::endl;
    } catch (const std::exception& e) {
        std::cout << "Nonexistent file error: OK" << std::endl;
    }
}

1.2 日付検証テスト

void testDateValidation() {
    std::cout << "\n=== Date Validation Test ===" << std::endl;

    // 有効な日付
    std::vector<std::string> validDates;
    validDates.push_back("2022-01-01");
    validDates.push_back("2022-12-31");
    validDates.push_back("2020-02-29");  // 閏年
    validDates.push_back("2011-01-03");

    for (size_t i = 0; i < validDates.size(); i++) {
        if (isValidDate(validDates[i])) {
            std::cout << validDates[i] << ": OK" << std::endl;
        } else {
            std::cout << validDates[i] << ": FAIL" << std::endl;
        }
    }

    // 無効な日付
    std::vector<std::string> invalidDates;
    invalidDates.push_back("2022-13-01");    // 月が範囲外
    invalidDates.push_back("2022-00-01");    // 月が0
    invalidDates.push_back("2022-01-32");    // 日が範囲外
    invalidDates.push_back("2021-02-29");    // 閏年ではない
    invalidDates.push_back("2022-1-01");     // フォーマット不正
    invalidDates.push_back("22-01-01");      // 年が短い
    invalidDates.push_back("2022/01/01");    // 区切り文字が違う

    for (size_t i = 0; i < invalidDates.size(); i++) {
        if (!isValidDate(invalidDates[i])) {
            std::cout << invalidDates[i] << " (invalid): OK" << std::endl;
        } else {
            std::cout << invalidDates[i] << " (invalid): FAIL" << std::endl;
        }
    }
}

1.3 値検証テスト

void testValueValidation() {
    std::cout << "\n=== Value Validation Test ===" << std::endl;

    struct TestCase {
        std::string value;
        bool shouldPass;
        std::string description;
    };

    std::vector<TestCase> tests;

    // 有効な値
    TestCase t1 = {"0", true, "zero"};
    tests.push_back(t1);
    TestCase t2 = {"1", true, "one"};
    tests.push_back(t2);
    TestCase t3 = {"1000", true, "max"};
    tests.push_back(t3);
    TestCase t4 = {"0.5", true, "decimal"};
    tests.push_back(t4);
    TestCase t5 = {"999.99", true, "near max"};
    tests.push_back(t5);

    // 無効な値
    TestCase t6 = {"-1", false, "negative"};
    tests.push_back(t6);
    TestCase t7 = {"1001", false, "too large"};
    tests.push_back(t7);
    TestCase t8 = {"abc", false, "not a number"};
    tests.push_back(t8);
    TestCase t9 = {"", false, "empty"};
    tests.push_back(t9);

    for (size_t i = 0; i < tests.size(); i++) {
        float result;
        bool isValid = isValidValue(tests[i].value, result);

        // 範囲チェック
        if (isValid) {
            isValid = (result >= 0 && result <= 1000);
        }

        bool passed = (isValid == tests[i].shouldPass);
        std::cout << tests[i].description << " (" << tests[i].value << "): "
                  << (passed ? "OK" : "FAIL") << std::endl;
    }
}

1.4 レート検索テスト

void testRateLookup() {
    std::cout << "\n=== Rate Lookup Test ===" << std::endl;

    BitcoinExchange btc;
    btc.loadDatabase("data.csv");

    // 正確な日付
    try {
        float rate = btc.getRate("2011-01-03");
        std::cout << "2011-01-03: " << rate << std::endl;
    } catch (const std::exception& e) {
        std::cout << "Error: " << e.what() << std::endl;
    }

    // 日付が存在しない場合(前の日付を使用)
    try {
        float rate = btc.getRate("2011-01-04");
        std::cout << "2011-01-04 (interpolated): " << rate << std::endl;
    } catch (const std::exception& e) {
        std::cout << "Error: " << e.what() << std::endl;
    }

    // 最初の日付より前
    try {
        float rate = btc.getRate("2008-01-01");
        std::cout << "FAIL: Should have thrown" << std::endl;
    } catch (const std::exception& e) {
        std::cout << "2008-01-01 (too early): OK" << std::endl;
    }
}

---

2. RPNのテスト

2.1 基本演算テスト

void testBasicOperations() {
    std::cout << "=== Basic Operations Test ===" << std::endl;

    RPN rpn;

    struct TestCase {
        std::string expr;
        int expected;
    };

    std::vector<TestCase> tests;

    TestCase t1 = {"1 2 +", 3};
    tests.push_back(t1);
    TestCase t2 = {"5 3 -", 2};
    tests.push_back(t2);
    TestCase t3 = {"4 3 *", 12};
    tests.push_back(t3);
    TestCase t4 = {"8 2 /", 4};
    tests.push_back(t4);
    TestCase t5 = {"9", 9};
    tests.push_back(t5);

    for (size_t i = 0; i < tests.size(); i++) {
        try {
            int result = rpn.evaluate(tests[i].expr);
            if (result == tests[i].expected) {
                std::cout << tests[i].expr << " = " << result << ": OK" << std::endl;
            } else {
                std::cout << tests[i].expr << " = " << result
                          << " (expected " << tests[i].expected << "): FAIL" << std::endl;
            }
        } catch (const std::exception& e) {
            std::cout << tests[i].expr << ": FAIL (" << e.what() << ")" << std::endl;
        }
    }
}

2.2 複合式テスト

void testComplexExpressions() {
    std::cout << "\n=== Complex Expressions Test ===" << std::endl;

    RPN rpn;

    struct TestCase {
        std::string expr;
        int expected;
    };

    std::vector<TestCase> tests;

    TestCase t1 = {"8 9 * 9 - 9 - 9 - 4 - 1 +", 42};
    tests.push_back(t1);
    TestCase t2 = {"7 7 * 7 -", 42};
    tests.push_back(t2);
    TestCase t3 = {"1 2 * 2 / 2 * 2 4 - +", 0};
    tests.push_back(t3);
    TestCase t4 = {"1 2 + 3 + 4 +", 10};
    tests.push_back(t4);
    TestCase t5 = {"5 1 2 + 4 * + 3 -", 14};
    tests.push_back(t5);

    for (size_t i = 0; i < tests.size(); i++) {
        try {
            int result = rpn.evaluate(tests[i].expr);
            if (result == tests[i].expected) {
                std::cout << "\"" << tests[i].expr << "\" = " << result << ": OK" << std::endl;
            } else {
                std::cout << "\"" << tests[i].expr << "\" = " << result
                          << " (expected " << tests[i].expected << "): FAIL" << std::endl;
            }
        } catch (const std::exception& e) {
            std::cout << "\"" << tests[i].expr << "\": FAIL (" << e.what() << ")" << std::endl;
        }
    }
}

2.3 エラーケーステスト

void testRPNErrors() {
    std::cout << "\n=== RPN Error Cases Test ===" << std::endl;

    RPN rpn;

    std::vector<std::string> errorCases;
    errorCases.push_back("(1 + 1)");      // 括弧は不可
    errorCases.push_back("1 +");          // オペランド不足
    errorCases.push_back("+ 1 2");        // 順序が違う
    errorCases.push_back("1 2");          // 演算子不足
    errorCases.push_back("1 0 /");        // ゼロ除算
    errorCases.push_back("10 2 +");       // 2桁の数は不可
    errorCases.push_back("a b +");        // 数字以外
    errorCases.push_back("");             // 空文字列

    for (size_t i = 0; i < errorCases.size(); i++) {
        try {
            rpn.evaluate(errorCases[i]);
            std::cout << "\"" << errorCases[i] << "\": FAIL (no exception)" << std::endl;
        } catch (const std::exception& e) {
            std::cout << "\"" << errorCases[i] << "\": OK (caught error)" << std::endl;
        }
    }
}

---

3. PmergeMeのテスト

3.1 ソート正確性テスト

void testSortCorrectness() {
    std::cout << "=== Sort Correctness Test ===" << std::endl;

    std::vector<int> input;
    input.push_back(5);
    input.push_back(3);
    input.push_back(8);
    input.push_back(1);
    input.push_back(9);
    input.push_back(2);
    input.push_back(7);
    input.push_back(4);
    input.push_back(6);

    std::vector<int> expected;
    expected.push_back(1);
    expected.push_back(2);
    expected.push_back(3);
    expected.push_back(4);
    expected.push_back(5);
    expected.push_back(6);
    expected.push_back(7);
    expected.push_back(8);
    expected.push_back(9);

    std::vector<int> vec = input;
    fordJohnsonSort(vec);

    bool correct = (vec == expected);
    std::cout << "Vector sort: " << (correct ? "OK" : "FAIL") << std::endl;

    std::deque<int> deq(input.begin(), input.end());
    fordJohnsonSort(deq);

    correct = std::equal(deq.begin(), deq.end(), expected.begin());
    std::cout << "Deque sort: " << (correct ? "OK" : "FAIL") << std::endl;
}

3.2 エッジケーステスト

void testEdgeCases() {
    std::cout << "\n=== Edge Cases Test ===" << std::endl;

    // 空の配列
    {
        std::vector<int> empty;
        fordJohnsonSort(empty);
        std::cout << "Empty array: " << (empty.empty() ? "OK" : "FAIL") << std::endl;
    }

    // 1要素
    {
        std::vector<int> single;
        single.push_back(42);
        fordJohnsonSort(single);
        std::cout << "Single element: " << (single[0] == 42 ? "OK" : "FAIL") << std::endl;
    }

    // 2要素
    {
        std::vector<int> two;
        two.push_back(5);
        two.push_back(3);
        fordJohnsonSort(two);
        std::cout << "Two elements: " << (two[0] == 3 && two[1] == 5 ? "OK" : "FAIL") << std::endl;
    }

    // 既にソート済み
    {
        std::vector<int> sorted;
        for (int i = 1; i <= 10; i++) sorted.push_back(i);
        fordJohnsonSort(sorted);

        bool correct = true;
        for (int i = 0; i < 10; i++) {
            if (sorted[i] != i + 1) correct = false;
        }
        std::cout << "Already sorted: " << (correct ? "OK" : "FAIL") << std::endl;
    }

    // 逆順
    {
        std::vector<int> reverse;
        for (int i = 10; i >= 1; i--) reverse.push_back(i);
        fordJohnsonSort(reverse);

        bool correct = true;
        for (int i = 0; i < 10; i++) {
            if (reverse[i] != i + 1) correct = false;
        }
        std::cout << "Reverse sorted: " << (correct ? "OK" : "FAIL") << std::endl;
    }

    // 重複あり
    {
        std::vector<int> duplicates;
        duplicates.push_back(3);
        duplicates.push_back(1);
        duplicates.push_back(4);
        duplicates.push_back(1);
        duplicates.push_back(5);
        duplicates.push_back(9);
        duplicates.push_back(2);
        duplicates.push_back(6);

        fordJohnsonSort(duplicates);

        bool correct = true;
        for (size_t i = 1; i < duplicates.size(); i++) {
            if (duplicates[i] < duplicates[i-1]) correct = false;
        }
        std::cout << "With duplicates: " << (correct ? "OK" : "FAIL") << std::endl;
    }
}

3.3 大規模データテスト

void testLargeDataset() {
    std::cout << "\n=== Large Dataset Test ===" << std::endl;

    const int SIZE = 10000;

    std::vector<int> vec;
    std::srand(std::time(NULL));
    for (int i = 0; i < SIZE; i++) {
        vec.push_back(std::rand() % 100000);
    }

    std::deque<int> deq(vec.begin(), vec.end());

    // Vector
    clock_t vecStart = clock();
    fordJohnsonSort(vec);
    clock_t vecEnd = clock();
    double vecTime = static_cast<double>(vecEnd - vecStart) / CLOCKS_PER_SEC * 1000;

    // Deque
    clock_t deqStart = clock();
    fordJohnsonSort(deq);
    clock_t deqEnd = clock();
    double deqTime = static_cast<double>(deqEnd - deqStart) / CLOCKS_PER_SEC * 1000;

    // ソート確認
    bool vecSorted = true;
    for (size_t i = 1; i < vec.size(); i++) {
        if (vec[i] < vec[i-1]) vecSorted = false;
    }

    bool deqSorted = true;
    for (size_t i = 1; i < deq.size(); i++) {
        if (deq[i] < deq[i-1]) deqSorted = false;
    }

    std::cout << "Vector (" << SIZE << " elements): "
              << (vecSorted ? "OK" : "FAIL")
              << " (" << vecTime << " ms)" << std::endl;

    std::cout << "Deque (" << SIZE << " elements): "
              << (deqSorted ? "OK" : "FAIL")
              << " (" << deqTime << " ms)" << std::endl;
}

3.4 性能比較テスト

void testPerformanceComparison() {
    std::cout << "\n=== Performance Comparison Test ===" << std::endl;

    int sizes[] = {100, 1000, 3000, 10000};

    for (int s = 0; s < 4; s++) {
        int size = sizes[s];

        std::vector<int> vec;
        std::srand(42);  // 再現可能な結果のため
        for (int i = 0; i < size; i++) {
            vec.push_back(std::rand() % 100000);
        }
        std::deque<int> deq(vec.begin(), vec.end());

        // Vector
        clock_t vecStart = clock();
        fordJohnsonSort(vec);
        clock_t vecEnd = clock();
        double vecTime = static_cast<double>(vecEnd - vecStart) / CLOCKS_PER_SEC * 1000000;

        // Deque
        clock_t deqStart = clock();
        fordJohnsonSort(deq);
        clock_t deqEnd = clock();
        double deqTime = static_cast<double>(deqEnd - deqStart) / CLOCKS_PER_SEC * 1000000;

        std::cout << "Size " << size << ": "
                  << "vector=" << vecTime << "us, "
                  << "deque=" << deqTime << "us" << std::endl;
    }
}

---

4. よくある間違い

4.1 ファイルパース

// 間違い: BOMを考慮しない
std::ifstream file(filename);
std::string line;
std::getline(file, line);
// UTF-8 BOMがある場合、lineの先頭にゴミが入る

// 正しい: BOMをチェック
std::getline(file, line);
if (line.size() >= 3 &&
    (unsigned char)line[0] == 0xEF &&
    (unsigned char)line[1] == 0xBB &&
    (unsigned char)line[2] == 0xBF) {
    line = line.substr(3);
}

4.2 RPNのスタック操作

// 間違い: オペランドの順序
int b = stk.top(); stk.pop();
int a = stk.top(); stk.pop();
stk.push(b - a);  // 順序が逆

// 正しい
int b = stk.top(); stk.pop();
int a = stk.top(); stk.pop();
stk.push(a - b);  // aからbを引く

4.3 Ford-Johnsonの挿入順序

// 間違い: 線形に挿入
for (size_t i = 1; i < pend.size(); i++) {
    // 単純に順番に挿入
}

// 正しい: Jacobsthal順序で挿入
// 3, 2, 5, 4, 11, 10, 9, 8, 7, 6, ...

4.4 時間計測

// 間違い: 精度が低い
time_t start = time(NULL);
// 処理
time_t end = time(NULL);
// 秒単位でしか計測できない

// 正しい: 高精度
clock_t start = clock();
// 処理
clock_t end = clock();
double us = (double)(end - start) / CLOCKS_PER_SEC * 1000000;

---

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

ex00

  • [ ] データベースファイルを正しく読み込む
  • [ ] 日付フォーマット(YYYY-MM-DD)を検証
  • [ ] 値の範囲(0-1000)を検証
  • [ ] 存在しない日付で前の日付を使用
  • [ ] エラーメッセージが適切
  • ex01

  • [ ] 1桁の数値のみ許可
  • [ ] +, -, *, / の全演算子が動作
  • [ ] オペランド不足でエラー
  • [ ] 演算子不足でエラー
  • [ ] ゼロ除算でエラー
  • ex02

  • [ ] 正の整数のみ許可
  • [ ] 2種類のコンテナで実装
  • [ ] Ford-Johnsonアルゴリズムを使用
  • [ ] 実行時間を表示
  • [ ] 3000個以上の要素で動作
  • ---

    まとめ

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

  • Bitcoin Exchange: パース、検索、エラー処理
  • RPN: 式評価、エラー処理
  • PmergeMe: ソート正確性、性能比較