第6章:テストと最適化
はじめに
ft_containersの品質を確保するには、包括的なテストが必要です。本章では、テスト戦略とパフォーマンス最適化を学びます。
---
1. テスト戦略
1.1 テストの種類
1. 単体テスト(Unit Test)
- 各関数/メソッドの個別テスト
- 境界条件のテスト
2. 比較テスト
- std:: vs ft:: の結果を比較
- 同一の入力に対して同一の出力を確認
3. パフォーマンステスト
- 大量データでの速度測定
- メモリ使用量の確認
4. エッジケーステスト
- 空のコンテナ
- 単一要素
- 最大サイズ
1.2 テストフレームワーク
シンプルなテストマクロ:
#include <iostream>
#define ASSERT_EQ(a, b) \
do { \
if ((a) != (b)) { \
std::cerr << "FAIL: " << #a << " != " << #b << std::endl; \
std::cerr << " Got: " << (a) << " Expected: " << (b) << std::endl; \
std::cerr << " at " << __FILE__ << ":" << __LINE__ << std::endl; \
return false; \
} \
} while(0)
#define ASSERT_TRUE(x) ASSERT_EQ((x), true)
#define ASSERT_FALSE(x) ASSERT_EQ((x), false)
#define RUN_TEST(test) \
do { \
std::cout << "Running " << #test << "... "; \
if (test()) { \
std::cout << "OK" << std::endl; \
} else { \
std::cout << "FAILED" << std::endl; \
} \
} while(0)
---
2. vectorのテスト
2.1 基本操作のテスト
bool test_vector_push_pop() {
ft::vector<int> ft_v;
std::vector<int> std_v;
// push_back
for (int i = 0; i < 10; ++i) {
ft_v.push_back(i);
std_v.push_back(i);
}
ASSERT_EQ(ft_v.size(), std_v.size());
// 要素の比較
for (size_t i = 0; i < ft_v.size(); ++i) {
ASSERT_EQ(ft_v[i], std_v[i]);
}
// pop_back
while (!ft_v.empty()) {
ASSERT_EQ(ft_v.back(), std_v.back());
ft_v.pop_back();
std_v.pop_back();
}
ASSERT_TRUE(ft_v.empty());
return true;
}
bool test_vector_insert() {
ft::vector<int> ft_v;
std::vector<int> std_v;
// 先頭に挿入
for (int i = 0; i < 5; ++i) {
ft_v.insert(ft_v.begin(), i);
std_v.insert(std_v.begin(), i);
}
ASSERT_EQ(ft_v.size(), std_v.size());
for (size_t i = 0; i < ft_v.size(); ++i) {
ASSERT_EQ(ft_v[i], std_v[i]);
}
// 中間に挿入
ft_v.insert(ft_v.begin() + 2, 100);
std_v.insert(std_v.begin() + 2, 100);
ASSERT_EQ(ft_v.size(), std_v.size());
for (size_t i = 0; i < ft_v.size(); ++i) {
ASSERT_EQ(ft_v[i], std_v[i]);
}
return true;
}
2.2 イテレータのテスト
bool test_vector_iterators() {
ft::vector<int> ft_v;
for (int i = 0; i < 10; ++i) {
ft_v.push_back(i);
}
// 順方向イテレータ
int expected = 0;
for (ft::vector<int>::iterator it = ft_v.begin();
it != ft_v.end(); ++it) {
ASSERT_EQ(*it, expected++);
}
// 逆方向イテレータ
expected = 9;
for (ft::vector<int>::reverse_iterator it = ft_v.rbegin();
it != ft_v.rend(); ++it) {
ASSERT_EQ(*it, expected--);
}
// constイテレータ
const ft::vector<int>& const_v = ft_v;
expected = 0;
for (ft::vector<int>::const_iterator it = const_v.begin();
it != const_v.end(); ++it) {
ASSERT_EQ(*it, expected++);
}
return true;
}
2.3 例外のテスト
bool test_vector_exceptions() {
ft::vector<int> v;
// at() の範囲外アクセス
try {
v.at(0);
return false; // 例外が発生しなかった
} catch (const std::out_of_range&) {
// 期待通り
}
v.push_back(1);
try {
v.at(10);
return false;
} catch (const std::out_of_range&) {
// 期待通り
}
return true;
}
---
3. mapのテスト
3.1 基本操作
bool test_map_insert_find() {
ft::map<int, std::string> ft_m;
std::map<int, std::string> std_m;
// insert
ft_m.insert(ft::make_pair(1, "one"));
ft_m.insert(ft::make_pair(2, "two"));
ft_m.insert(ft::make_pair(3, "three"));
std_m.insert(std::make_pair(1, "one"));
std_m.insert(std::make_pair(2, "two"));
std_m.insert(std::make_pair(3, "three"));
ASSERT_EQ(ft_m.size(), std_m.size());
// find
ft::map<int, std::string>::iterator ft_it = ft_m.find(2);
std::map<int, std::string>::iterator std_it = std_m.find(2);
ASSERT_TRUE(ft_it != ft_m.end());
ASSERT_EQ(ft_it->second, std_it->second);
// 存在しないキー
ASSERT_TRUE(ft_m.find(100) == ft_m.end());
return true;
}
bool test_map_operator_bracket() {
ft::map<std::string, int> m;
m["apple"] = 1;
m["banana"] = 2;
m["cherry"] = 3;
ASSERT_EQ(m["apple"], 1);
ASSERT_EQ(m["banana"], 2);
ASSERT_EQ(m["cherry"], 3);
// 存在しないキーにアクセス → デフォルト値で挿入
ASSERT_EQ(m["date"], 0);
ASSERT_EQ(m.size(), (size_t)4);
return true;
}
3.2 赤黒木の性質チェック
template<typename T>
bool verify_rb_properties(const RBNode<T>* node, const RBNode<T>* nil,
int& black_height) {
if (node == nil) {
black_height = 1; // NILは黒
return true;
}
// 性質4: 赤ノードの子は黒
if (node->color == RED) {
if (node->left->color == RED || node->right->color == RED) {
std::cerr << "Red violation: red node has red child" << std::endl;
return false;
}
}
int left_bh, right_bh;
if (!verify_rb_properties(node->left, nil, left_bh) ||
!verify_rb_properties(node->right, nil, right_bh)) {
return false;
}
// 性質5: 黒高さが等しい
if (left_bh != right_bh) {
std::cerr << "Black height violation" << std::endl;
return false;
}
black_height = left_bh + (node->color == BLACK ? 1 : 0);
return true;
}
bool test_map_rb_properties() {
ft::map<int, int> m;
// ランダムな順序で挿入
int keys[] = {5, 3, 7, 1, 9, 4, 6, 8, 2, 0};
for (int i = 0; i < 10; ++i) {
m.insert(ft::make_pair(keys[i], keys[i] * 10));
// 各挿入後に赤黒木の性質を確認
// (内部構造へのアクセスが必要)
}
return true;
}
---
4. stackのテスト
bool test_stack_basic() {
ft::stack<int> ft_s;
std::stack<int> std_s;
ASSERT_TRUE(ft_s.empty());
for (int i = 0; i < 10; ++i) {
ft_s.push(i);
std_s.push(i);
}
ASSERT_EQ(ft_s.size(), std_s.size());
while (!ft_s.empty()) {
ASSERT_EQ(ft_s.top(), std_s.top());
ft_s.pop();
std_s.pop();
}
ASSERT_TRUE(ft_s.empty());
return true;
}
bool test_stack_comparison() {
ft::stack<int> s1, s2;
for (int i = 0; i < 5; ++i) {
s1.push(i);
s2.push(i);
}
ASSERT_TRUE(s1 == s2);
s1.push(5);
ASSERT_TRUE(s1 > s2);
ASSERT_TRUE(s2 < s1);
return true;
}
---
5. パフォーマンステスト
5.1 時間測定
#include <sys/time.h>
class Timer {
private:
struct timeval _start;
struct timeval _end;
public:
void start() {
gettimeofday(&_start, NULL);
}
double stop() {
gettimeofday(&_end, NULL);
return (_end.tv_sec - _start.tv_sec) * 1000.0 +
(_end.tv_usec - _start.tv_usec) / 1000.0;
}
};
void benchmark_vector() {
const int N = 100000;
Timer timer;
// ft::vector
{
ft::vector<int> v;
timer.start();
for (int i = 0; i < N; ++i) {
v.push_back(i);
}
double ft_time = timer.stop();
std::cout << "ft::vector push_back: " << ft_time << "ms" << std::endl;
}
// std::vector
{
std::vector<int> v;
timer.start();
for (int i = 0; i < N; ++i) {
v.push_back(i);
}
double std_time = timer.stop();
std::cout << "std::vector push_back: " << std_time << "ms" << std::endl;
}
}
void benchmark_map() {
const int N = 100000;
Timer timer;
// ft::map
{
ft::map<int, int> m;
timer.start();
for (int i = 0; i < N; ++i) {
m.insert(ft::make_pair(i, i));
}
double insert_time = timer.stop();
timer.start();
for (int i = 0; i < N; ++i) {
m.find(i);
}
double find_time = timer.stop();
std::cout << "ft::map insert: " << insert_time << "ms, "
<< "find: " << find_time << "ms" << std::endl;
}
// std::map
{
std::map<int, int> m;
timer.start();
for (int i = 0; i < N; ++i) {
m.insert(std::make_pair(i, i));
}
double insert_time = timer.stop();
timer.start();
for (int i = 0; i < N; ++i) {
m.find(i);
}
double find_time = timer.stop();
std::cout << "std::map insert: " << insert_time << "ms, "
<< "find: " << find_time << "ms" << std::endl;
}
}
5.2 メモリ使用量
#include <sys/resource.h>
size_t get_memory_usage() {
struct rusage usage;
getrusage(RUSAGE_SELF, &usage);
return usage.ru_maxrss; // キロバイト単位
}
void benchmark_memory() {
const int N = 100000;
size_t before = get_memory_usage();
ft::vector<int> v;
for (int i = 0; i < N; ++i) {
v.push_back(i);
}
size_t after = get_memory_usage();
std::cout << "Memory used: " << (after - before) << " KB" << std::endl;
}
---
6. 最適化テクニック
6.1 vectorの最適化
// 事前にcapacityを確保
void optimized_push_back(ft::vector<int>& v, int n) {
v.reserve(n); // 再確保を防ぐ
for (int i = 0; i < n; ++i) {
v.push_back(i);
}
}
// swap trickでメモリを解放
void shrink_to_fit(ft::vector<int>& v) {
ft::vector<int>(v).swap(v);
// 一時的なベクトルを作成し、swapする
// 一時ベクトルは現在のsizeぴったりのcapacity
}
6.2 mapの最適化
// hint付きinsert
void optimized_insert(ft::map<int, int>& m) {
ft::map<int, int>::iterator hint = m.begin();
// ソート済みデータの挿入
for (int i = 0; i < 10000; ++i) {
hint = m.insert(hint, ft::make_pair(i, i));
}
// hintが正しければO(1)、そうでなければO(log n)
}
---
7. テストメインファイル
int main() {
std::cout << "=== Vector Tests ===" << std::endl;
RUN_TEST(test_vector_push_pop);
RUN_TEST(test_vector_insert);
RUN_TEST(test_vector_iterators);
RUN_TEST(test_vector_exceptions);
std::cout << "\n=== Map Tests ===" << std::endl;
RUN_TEST(test_map_insert_find);
RUN_TEST(test_map_operator_bracket);
RUN_TEST(test_map_rb_properties);
std::cout << "\n=== Stack Tests ===" << std::endl;
RUN_TEST(test_stack_basic);
RUN_TEST(test_stack_comparison);
std::cout << "\n=== Benchmarks ===" << std::endl;
benchmark_vector();
benchmark_map();
benchmark_memory();
return 0;
}
---
まとめ
本章で学んだこと:
- テスト戦略: 単体、比較、パフォーマンス、エッジケース
- vectorのテスト: 基本操作、イテレータ、例外
- mapのテスト: 挿入、検索、赤黒木の性質
- stackのテスト: 基本操作、比較
- パフォーマンステスト: 時間、メモリ
- 最適化: reserve、hint付きinsert
これでft_containersガイドは完了です。