第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)を検証
- [ ] 存在しない日付で前の日付を使用
- [ ] エラーメッセージが適切
- [ ] 1桁の数値のみ許可
- [ ] +, -, *, / の全演算子が動作
- [ ] オペランド不足でエラー
- [ ] 演算子不足でエラー
- [ ] ゼロ除算でエラー
- [ ] 正の整数のみ許可
- [ ] 2種類のコンテナで実装
- [ ] Ford-Johnsonアルゴリズムを使用
- [ ] 実行時間を表示
- [ ] 3000個以上の要素で動作
- Bitcoin Exchange: パース、検索、エラー処理
- RPN: 式評価、エラー処理
- PmergeMe: ソート正確性、性能比較
ex01
ex02
---
まとめ
CPP Module 09のテストでは以下を確認: