第7章:ソフトウェア検証の科学 - テスト理論とデバッグ技法
7.1 ソフトウェアテストの歴史と哲学
7.1.1 テストの起源:初期のコンピュータ時代
ソフトウェアテストの歴史は、コンピュータの歴史そのものと密接に結びついている。1940年代、Grace Hopperが蛾(moth)を取り除いた有名なエピソードから「デバッグ」という用語が生まれたが、テストとデバッグの体系化はさらに後の時代を待たねばならなかった。
初期のプログラミングでは、プログラムの正しさは作成者の直感と経験に依存していた:
1940年代-1950年代:手作業によるトレース
- 紙とペンでプログラムの実行を追跡
- 物理的なスイッチとランプで状態を確認
- 「机上デバッグ」(desk checking)が主流
1950年代-1960年代:テストの萌芽
- IBMのテスト部門設立(1957年)
- テストケースの概念の登場
- 「プログラムテスト」が正式な工程に
1970年代:構造化テストの時代
- Glenford Myersの「ソフトウェアテストの技法」(1979)
- 「テストとは、バグを見つける行為である」
- カバレッジの概念の確立
Myersの定義は革命的だった。それまで「テストはプログラムが正しく動作することを示す」と考えられていたのに対し、「テストはプログラムに欠陥があることを発見するために実行する」と再定義したのである。
7.1.2 テストの本質的限界:Dijkstraの警告
1972年、Edsger Dijkstraは有名な言葉を残した:
> "Program testing can be used to show the presence of bugs, but never to show their absence." > (プログラムテストはバグの存在を示すことはできるが、バグの不在を示すことはできない)
この主張の数学的背景を理解しよう。プログラムの入力空間が無限である場合、すべての入力をテストすることは不可能である。例えば、単純な32ビット整数2つを受け取る関数でさえ:
// 可能な入力の組み合わせ
// 2^32 × 2^32 = 2^64 ≈ 1.8 × 10^19 通り
int add(int a, int b)
{
return a + b; // オーバーフローは考慮しない
}
// 全テストに必要な時間(1テスト = 1ナノ秒として)
// 1.8 × 10^19 ナノ秒 ≈ 584年
この「テストの不完全性」は、ソフトウェア工学における根本的な課題である。この限界を認識した上で、いかに効果的なテスト戦略を構築するかが、テスト理論の核心となる。
7.1.3 停止問題とプログラム検証の限界
テストの限界をさらに深く理解するため、Alan Turingの停止問題(Halting Problem)を考察しよう。
停止問題:任意のプログラムPと入力xが与えられたとき、P(x)が停止するかどうかを判定するアルゴリズムは存在しない。
証明(対角線論法による):
1. 停止判定器 H(P, x) が存在すると仮定
H(P, x) = true : P(x)が停止する
H(P, x) = false : P(x)が停止しない
2. 新しいプログラム D を構成:
D(P) = if H(P, P) then loop_forever else halt
3. D(D) を考える:
H(D, D) = true の場合:D(D) はループ(矛盾)
H(D, D) = false の場合:D(D) は停止(矛盾)
4. どちらも矛盾 → H は存在しない ■
この結果は、プログラムの振る舞いに関する多くの性質が「決定不能」であることを意味する。テストはこの限界の中で、実用的な品質保証を目指すのである。
7.1.4 形式的手法とテストの関係
テストの限界を補完するアプローチとして、形式的手法(Formal Methods)がある。これはプログラムの正しさを数学的に証明する試みである。
Hoare論理(1969年、Tony Hoare):
プログラムの正しさを三つ組 {P} S {Q} で表現する:
- P:事前条件(precondition)
- S:プログラム
- Q:事後条件(postcondition)
Hoareの公理体系:
1. 代入公理:
{Q[E/x]} x := E {Q}
2. 順序則:
{P} S1 {R}, {R} S2 {Q}
─────────────────────
{P} S1; S2 {Q}
3. 条件則:
{P ∧ B} S1 {Q}, {P ∧ ¬B} S2 {Q}
─────────────────────────────────
{P} if B then S1 else S2 {Q}
4. ループ不変条件:
{I ∧ B} S {I}
─────────────────────
{I} while B do S {I ∧ ¬B}
この形式的検証をlibftの関数に適用してみよう:
// ft_strlen の形式的仕様
/*
* 事前条件 P: s は有効なnull終端文字列へのポインタ
* ∃n ∈ ℕ. s[n] = '\0' ∧ ∀i < n. s[i] ≠ '\0'
*
* 事後条件 Q: result = n (null終端までの文字数)
*
* ループ不変条件 I: 0 ≤ i ≤ n ∧ ∀j < i. s[j] ≠ '\0'
*/
size_t ft_strlen(const char *s)
{
size_t i;
// {P} → {I[0/i]}(ループ不変条件の初期化)
i = 0;
// {I}
while (s[i] != '\0')
{
// {I ∧ s[i] ≠ '\0'}
i++;
// {I}
}
// {I ∧ s[i] = '\0'} → {i = n} → {Q}
return (i);
}
7.2 テスト理論の数学的基礎
7.2.1 テストケース選択問題
テストケースの選択は、本質的には集合論と組み合わせ論の問題である。
入力ドメインモデル:
D = 入力ドメイン(全可能入力の集合)
T ⊂ D = テストスイート(選択されたテストケースの集合)
F ⊂ D = 失敗を引き起こす入力の集合
テストの目標:
- 理想:F ∩ T ≠ ∅ (失敗を発見)
- 現実:|T| を最小化しつつ F との交差を最大化
ドメイン分割(Domain Partitioning):
入力ドメインを同値クラスに分割し、各クラスから代表的な入力を選ぶ:
D = D₁ ∪ D₂ ∪ ... ∪ Dₙ (互いに素な分割)
仮定:∀i. (Dᵢ ∩ F = ∅) ∨ (Dᵢ ⊆ F)
各クラスは「全て正常」か「全て異常」
この仮定の下では、n個のテストで完全なテストが可能
libftの文字分類関数を例に:
// ft_isalpha のドメイン分割
/*
* D₁ = {-128, ..., -1} : 負の拡張ASCII(FALSE期待)
* D₂ = {0, ..., 64} : 制御文字と記号(FALSE期待)
* D₃ = {65, ..., 90} : 'A'-'Z'(TRUE期待)
* D₄ = {91, ..., 96} : 記号(FALSE期待)
* D₅ = {97, ..., 122} : 'a'-'z'(TRUE期待)
* D₆ = {123, ..., 127} : 記号とDEL(FALSE期待)
* D₇ = {128, ..., 255} : 拡張ASCII(FALSE期待)
*/
// 各クラスから1つ選択 + 境界値
int test_values[] = {
-1, // D₁ from boundary
0, 64, // D₂ boundaries
65, 90, // D₃ boundaries ('A', 'Z')
91, 96, // D₄ boundaries
97, 122, // D₅ boundaries ('a', 'z')
123, 127,// D₆ boundaries
128, 255 // D₇ boundaries
};
7.2.2 境界値分析の数学
Off-by-one エラーの理論:
プログラムの多くのバグは境界付近で発生する。これは条件式の不等号の誤り(<と<=の混同など)に起因することが多い。
境界点 b に対して:
- b 自体
- b - ε(境界の直前)
- b + ε(境界の直後)
ここで ε は最小の有意な差(整数なら1)
なぜ境界にバグが集中するか:
// よくある off-by-one エラー
for (int i = 0; i <= n; i++) // n+1 回の反復(おそらく誤り)
for (int i = 0; i < n; i++) // n 回の反復(意図通り)
// 条件の誤り
if (x > limit) // limit は含まない
if (x >= limit) // limit も含む
// 境界テストで発見可能
test(limit - 1); // 境界前
test(limit); // 境界上
test(limit + 1); // 境界後
7.2.3 カバレッジ理論
構造的カバレッジは、プログラムのどの部分がテストで実行されたかを測定する。
カバレッジ基準の階層:
1. 文カバレッジ(Statement Coverage)
- 各文が少なくとも1回実行される
- 最も弱い基準
2. 分岐カバレッジ(Branch Coverage)
- 各分岐(if-else)の両方向が実行される
- 文カバレッジを包含
3. 条件カバレッジ(Condition Coverage)
- 各論理条件がtrue/falseの両方を取る
4. MC/DC(Modified Condition/Decision Coverage)
- 各条件が独立に結果に影響することを示す
- 航空機ソフトウェアの認証で要求(DO-178B Level A)
5. パスカバレッジ(Path Coverage)
- 全ての実行パスをテスト
- 実用的にはほぼ不可能(パス数が指数的)
グラフ理論による分析:
プログラムを制御フローグラフ(CFG)として表現し、カバレッジを分析する:
G = (V, E) ここで
- V = 基本ブロック(basic blocks)の集合
- E = 制御の流れを表す辺の集合
文カバレッジ = |実行された頂点| / |V|
分岐カバレッジ = |実行された辺| / |E|
// 制御フローグラフの例
size_t ft_strlcpy(char *dst, const char *src, size_t size)
{
size_t i; // Block 1: 初期化
size_t src_len;
src_len = 0; // Block 2
while (src[src_len])
src_len++;
if (size == 0) // Block 3: 分岐点
return (src_len); // Block 4
i = 0; // Block 5
while (i < size - 1 && src[i]) // Block 6: ループ
{
dst[i] = src[i]; // Block 7
i++;
}
dst[i] = '\0'; // Block 8
return (src_len); // Block 9
}
mermaid
graph TD
B1[1: Init] --> B2[2: src_len loop]
B2 --> B2
B2 --> B3[3: if size==0]
B3 -->|True| B4[4: return src_len]
B3 -->|False| B5[5: i=0]
B5 --> B6[6: copy loop]
B6 --> B7[7: copy char]
B7 --> B6
B6 --> B8[8: null terminate]
B8 --> B9[9: return src_len]
7.2.4 変異テスト
変異テスト(Mutation Testing)は、テストスイートの品質を評価する手法である。
原理:
1. プログラム P に小さな変更(変異)を加える → 変異体 P'
2. テストスイート T で P' を実行
3. T が P と P' を区別できれば、変異体は「殺された」
4. 変異スコア = 殺された変異体数 / 全変異体数
典型的な変異演算子:
// 元のコード
if (a < b)
// 変異体たち
if (a <= b) // 関係演算子の変更
if (a > b) // 関係演算子の変更
if (a == b) // 関係演算子の変更
if (a < 0) // オペランドの変更
if (true) // 条件の単純化
if (false) // 条件の単純化
// ft_strlen の変異テスト例
size_t ft_strlen_mutant1(const char *s)
{
size_t i = 0;
while (s[i] != '\0')
i++;
return (i + 1); // 変異:return i → return i + 1
}
size_t ft_strlen_mutant2(const char *s)
{
size_t i = 1; // 変異:i = 0 → i = 1
while (s[i] != '\0')
i++;
return (i);
}
// テストケース "hello" で:
// 正常:5, mutant1:6, mutant2:4
// → このテストで両方の変異体を殺せる
7.3 テスト駆動開発(TDD)の理論と実践
7.3.1 TDDの起源
テスト駆動開発は、Kent Beckによって1990年代後半に体系化された。しかし、その源流は1960年代のNASAのプログラムにまで遡る。
TDDの核心サイクル:
Red → Green → Refactor
1. Red(赤):失敗するテストを書く
- テストが失敗することを確認
- 仕様を実行可能な形で表現
2. Green(緑):テストを通す最小限のコードを書く
- 「動く」ことが最優先
- エレガントさは後回し
3. Refactor(リファクタ):コードを改善
- 重複を除去
- 設計を改善
- テストは緑のまま
7.3.2 TDDの数学的正当化
TDDは、形式的手法の近似として理解できる。
漸進的仕様の構築:
T₀ = {} (空のテストスイート)
T₁ = T₀ ∪ {test₁}
T₂ = T₁ ∪ {test₂}
...
Tₙ = Tₙ₋₁ ∪ {testₙ}
各ステップで、Tᵢ を満たす実装 Pᵢ を構築
Pᵢ ⊆ Pᵢ₊₁(実装の拡張)
最終的に P = Pₙ は Tₙ を満たす
TDDによるlibft開発の例:
// ステップ1:最初のテスト(失敗する)
void test_ft_isdigit_basic(void)
{
assert(ft_isdigit('0') != 0);
assert(ft_isdigit('9') != 0);
assert(ft_isdigit('a') == 0);
}
// ステップ2:最小限の実装(テストを通す)
int ft_isdigit(int c)
{
if (c == '0' || c == '9')
return (1);
return (0); // 'a' のケースも偶然通る
}
// ステップ3:テストを追加(失敗する)
void test_ft_isdigit_all_digits(void)
{
for (int c = '0'; c <= '9'; c++)
assert(ft_isdigit(c) != 0);
}
// ステップ4:実装を拡張
int ft_isdigit(int c)
{
return (c >= '0' && c <= '9');
}
// ステップ5:エッジケースのテスト
void test_ft_isdigit_edge(void)
{
assert(ft_isdigit(-1) == 0);
assert(ft_isdigit(256) == 0);
assert(ft_isdigit('0' - 1) == 0);
assert(ft_isdigit('9' + 1) == 0);
}
// ステップ6:実装は既に正しい(リファクタリング不要)
7.3.3 契約による設計(Design by Contract)
Bertrand Meyerによって提唱された契約による設計(DbC)は、TDDと補完的な関係にある。
三つの契約:
1. 事前条件(Precondition)
- 呼び出し側の責任
- 関数が正しく動作するための前提
2. 事後条件(Postcondition)
- 実装側の責任
- 関数が保証する結果
3. 不変条件(Invariant)
- オブジェクト/モジュールが常に維持すべき条件
// ft_memcpy の契約
/*
* @pre: dst != NULL
* @pre: src != NULL
* @pre: n >= 0
* @pre: dst と src は n バイト内で重複しない
* (重複する場合は ft_memmove を使用)
*
* @post: forall i in [0, n). dst[i] == old(src[i])
* @post: return == dst
*/
void *ft_memcpy(void *dst, const void *src, size_t n)
{
unsigned char *d;
const unsigned char *s;
// 事前条件の検証(デバッグビルドで有効化)
#ifdef DEBUG
assert(dst != NULL && "dst must not be NULL");
assert(src != NULL && "src must not be NULL");
// 重複チェックは複雑なため省略
#endif
d = (unsigned char *)dst;
s = (const unsigned char *)src;
while (n--)
*d++ = *s++;
return (dst);
}
7.4 デバッグの科学
7.4.1 バグの分類学
デバッグを効果的に行うには、バグの種類を理解することが重要である。
Boris Beizerのバグ分類(1990):
1. 構造的バグ(Structural Bugs)
- 制御フローの誤り
- データフローの誤り
- メモリ管理の誤り
2. 機能的バグ(Functional Bugs)
- 仕様の誤解
- 境界条件の見落とし
- 例外処理の欠如
3. データバグ(Data Bugs)
- 型の不一致
- 初期化漏れ
- オーバーフロー
4. 統合バグ(Integration Bugs)
- インターフェースの不一致
- タイミング問題
- リソース競合
C言語特有のバグパターン:
// 1. Off-by-one エラー
for (int i = 0; i <= size; i++) // <= ではなく < が正しい
buffer[i] = 0;
// 2. NULL ポインタ参照
char *str = ft_strdup(NULL); // ft_strdup がNULLチェックしているか?
printf("%s\n", str); // str が NULL なら未定義動作
// 3. バッファオーバーフロー
char buffer[10];
ft_strlcpy(buffer, "hello world", sizeof(buffer)); // OK
ft_strcpy(buffer, "hello world"); // バッファオーバーフロー!
// 4. メモリリーク
char *s1 = ft_strdup("hello");
s1 = ft_strdup("world"); // 最初の割り当てがリーク
// 5. ダングリングポインタ
char *get_string(void)
{
char buffer[100];
ft_strlcpy(buffer, "hello", sizeof(buffer));
return buffer; // ローカル変数へのポインタを返却
}
// 6. 符号付き/符号なしの混同
size_t len = ft_strlen(str);
if (len - 10 < 0) // size_t は常に >= 0、この条件は常に偽
handle_short_string();
7.4.2 科学的デバッグ手法
デバッグは本質的に科学的方法に従う。
科学的デバッグプロセス:
1. 観察(Observation)
- 症状を正確に記録
- 再現条件を特定
2. 仮説形成(Hypothesis)
- 原因についての仮説を立てる
- 複数の仮説を列挙
3. 予測(Prediction)
- 仮説が正しければ何が観察されるべきか
4. 実験(Experiment)
- テストコードまたはデバッガで検証
- 一度に一つの変数のみ変更
5. 結論(Conclusion)
- 仮説を支持するか棄却するか
- 新しい仮説へ、または修正へ
二分探索によるバグ特定:
// バグのある関数(どこかに問題がある)
char *process_string(const char *input)
{
size_t len = ft_strlen(input); // チェックポイント A
char *result = malloc(len + 1); // チェックポイント B
for (size_t i = 0; i < len; i++) // チェックポイント C
{
result[i] = ft_toupper(input[i]);
}
result[len] = '\0'; // チェックポイント D
return result; // チェックポイント E
}
// デバッグ戦略:
// 1. 中間点(C)で状態を確認
// 2. 問題が C より前なら A-B を調査
// 3. 問題が C より後なら D-E を調査
// 4. O(log n) で問題箇所を特定
7.4.3 デバッガの理論と実践
デバッガは、プログラムの実行を観察・制御するツールである。
デバッガの主要機能:
1. ブレークポイント(Breakpoint)
- 指定した位置で実行を停止
- 条件付きブレークポイント
2. ステップ実行
- step into:関数内部に入る
- step over:関数呼び出しをスキップ
- step out:現在の関数から抜ける
3. 状態検査
- 変数の値を表示
- メモリの内容を表示
- コールスタックの表示
4. 状態変更
- 変数の値を変更
- 実行位置を変更
GDBの基本的な使用法:
# コンパイル(デバッグ情報付き)
gcc -g -Wall -Wextra -Werror ft_strlen.c main.c -o test
# GDBの起動
gdb ./test
# 主要なコマンド
(gdb) break ft_strlen # ft_strlen にブレークポイント
(gdb) break main.c:42 # main.c の42行目にブレークポイント
(gdb) run # プログラム開始
(gdb) next # 次の行へ(step over)
(gdb) step # 関数内部へ(step into)
(gdb) print var # 変数 var の値を表示
(gdb) print *ptr@10 # ptr から10要素を表示
(gdb) backtrace # コールスタックを表示
(gdb) watch var # var の変更を監視
(gdb) continue # 実行を再開
(gdb) quit # GDB終了
7.4.4 メモリデバッガ
メモリ関連のバグは最も発見が困難である。専用のツールが不可欠。
Valgrindの動作原理:
Valgrind(特にMemcheck)は動的バイナリ計装を行う:
1. プログラムを中間表現(IR)に変換
2. 計装コードを挿入
3. IRから機械語に再変換
4. 計装されたコードを実行
これにより、すべてのメモリ操作を追跡できる
Valgrindで検出可能なエラー:
// 1. 未初期化値の使用
int x;
if (x > 0) // x は未初期化
do_something();
// 2. 不正なメモリ読み書き
int arr[10];
arr[10] = 42; // 境界外書き込み
// 3. 不正なfree
free(ptr);
free(ptr); // 二重解放
// 4. メモリリーク
void leak(void)
{
char *p = malloc(100);
// free がない
}
// 5. 解放済みメモリの使用
char *p = malloc(100);
free(p);
p[0] = 'x'; // Use after free
# Valgrindの実行
valgrind --leak-check=full --show-leak-kinds=all ./test
# 典型的な出力
==12345== Invalid write of size 1
==12345== at 0x40052F: main (test.c:10)
==12345== Address 0x51fc04a is 0 bytes after a block of size 10 alloc'd
==12345== at 0x4C2AB80: malloc (in /usr/lib/valgrind/...)
==12345== by 0x400520: main (test.c:8)
7.5 libftテストフレームワークの設計
7.5.1 xUnitパターン
Kent Beckによって確立されたxUnitパターンは、テストフレームワークの標準的な設計である。
xUnitの核心概念:
1. Test Case(テストケース)
- 個々のテストメソッド
- セットアップ→実行→検証→後処理
2. Test Suite(テストスイート)
- 関連するテストケースの集合
- 階層的に組織化可能
3. Test Fixture(テストフィクスチャ)
- テストに必要な共通のセットアップ
- 前後処理のコード
4. Assertions(アサーション)
- 期待値と実際の値を比較
- 失敗時に詳細情報を出力
7.5.2 Cでのテストフレームワーク実装
// libft_test.h - テストフレームワーク
#ifndef LIBFT_TEST_H
# define LIBFT_TEST_H
# include <stdio.h>
# include <stdlib.h>
# include <string.h>
# include <time.h>
# include <signal.h>
# include <setjmp.h>
# include "libft.h"
// ANSIカラーコード
# define ANSI_RED "\x1b[31m"
# define ANSI_GREEN "\x1b[32m"
# define ANSI_YELLOW "\x1b[33m"
# define ANSI_BLUE "\x1b[34m"
# define ANSI_CYAN "\x1b[36m"
# define ANSI_RESET "\x1b[0m"
// テスト統計
typedef struct s_test_stats {
int total;
int passed;
int failed;
int skipped;
double elapsed_time;
} t_test_stats;
// テストコンテキスト(現在のテスト情報)
typedef struct s_test_context {
const char *test_name;
const char *file;
int line;
jmp_buf jump_buffer;
int in_test;
} t_test_context;
// グローバル変数
extern t_test_stats g_stats;
extern t_test_context g_context;
// テスト開始/終了マクロ
# define TEST_BEGIN(name) do { \
g_context.test_name = name; \
g_context.file = __FILE__; \
g_context.line = __LINE__; \
g_context.in_test = 1; \
if (setjmp(g_context.jump_buffer) == 0) { \
clock_t _start = clock();
# define TEST_END() \
g_stats.elapsed_time += (double)(clock() - _start) / CLOCKS_PER_SEC; \
} \
g_context.in_test = 0; \
} while (0)
// アサーションマクロ
# define ASSERT_TRUE(cond) do { \
g_stats.total++; \
if (cond) { \
g_stats.passed++; \
printf(ANSI_GREEN "." ANSI_RESET); \
} else { \
g_stats.failed++; \
printf(ANSI_RED "\n FAIL: %s\n" ANSI_RESET, #cond); \
printf(" at %s:%d in %s\n", __FILE__, __LINE__, g_context.test_name); \
} \
} while (0)
# define ASSERT_EQ(expected, actual) do { \
g_stats.total++; \
long long _exp = (long long)(expected); \
long long _act = (long long)(actual); \
if (_exp == _act) { \
g_stats.passed++; \
printf(ANSI_GREEN "." ANSI_RESET); \
} else { \
g_stats.failed++; \
printf(ANSI_RED "\n FAIL: Expected %lld, got %lld\n" ANSI_RESET, _exp, _act); \
printf(" at %s:%d in %s\n", __FILE__, __LINE__, g_context.test_name); \
} \
} while (0)
# define ASSERT_STR_EQ(expected, actual) do { \
g_stats.total++; \
const char *_exp = (expected); \
const char *_act = (actual); \
if (_exp == NULL && _act == NULL) { \
g_stats.passed++; \
printf(ANSI_GREEN "." ANSI_RESET); \
} else if (_exp == NULL || _act == NULL) { \
g_stats.failed++; \
printf(ANSI_RED "\n FAIL: One string is NULL\n" ANSI_RESET); \
printf(" Expected: %s\n", _exp ? _exp : "(NULL)"); \
printf(" Actual: %s\n", _act ? _act : "(NULL)"); \
printf(" at %s:%d in %s\n", __FILE__, __LINE__, g_context.test_name); \
} else if (strcmp(_exp, _act) == 0) { \
g_stats.passed++; \
printf(ANSI_GREEN "." ANSI_RESET); \
} else { \
g_stats.failed++; \
printf(ANSI_RED "\n FAIL: Strings differ\n" ANSI_RESET); \
printf(" Expected: \"%s\"\n", _exp); \
printf(" Actual: \"%s\"\n", _act); \
printf(" at %s:%d in %s\n", __FILE__, __LINE__, g_context.test_name); \
} \
} while (0)
# define ASSERT_MEM_EQ(expected, actual, size) do { \
g_stats.total++; \
const void *_exp = (expected); \
const void *_act = (actual); \
size_t _size = (size); \
if (memcmp(_exp, _act, _size) == 0) { \
g_stats.passed++; \
printf(ANSI_GREEN "." ANSI_RESET); \
} else { \
g_stats.failed++; \
printf(ANSI_RED "\n FAIL: Memory differs\n" ANSI_RESET); \
print_memory_diff(_exp, _act, _size); \
printf(" at %s:%d in %s\n", __FILE__, __LINE__, g_context.test_name); \
} \
} while (0)
// ヘルパー関数
void print_memory_diff(const void *expected, const void *actual, size_t size);
void print_test_summary(void);
void run_all_tests(void);
#endif
7.5.3 テストフレームワークの実装
// libft_test.c
#include "libft_test.h"
t_test_stats g_stats = {0, 0, 0, 0, 0.0};
t_test_context g_context = {NULL, NULL, 0, {0}, 0};
// メモリ比較のビジュアル出力
void print_memory_diff(const void *expected, const void *actual, size_t size)
{
const unsigned char *exp = expected;
const unsigned char *act = actual;
int diff_count = 0;
printf(" Memory comparison (showing first 5 differences):\n");
for (size_t i = 0; i < size && diff_count < 5; i++)
{
if (exp[i] != act[i])
{
printf(" [%zu]: expected 0x%02X ('%c'), got 0x%02X ('%c')\n",
i, exp[i], (exp[i] >= 32 && exp[i] < 127) ? exp[i] : '.',
act[i], (act[i] >= 32 && act[i] < 127) ? act[i] : '.');
diff_count++;
}
}
if (diff_count == 0)
printf(" (No visible differences in first %zu bytes)\n", size);
}
// テスト結果のサマリー
void print_test_summary(void)
{
printf("\n\n" ANSI_CYAN "═══════════════════════════════════════\n");
printf(" TEST SUMMARY\n");
printf("═══════════════════════════════════════\n" ANSI_RESET);
printf("Total assertions: %d\n", g_stats.total);
printf(ANSI_GREEN "Passed: %d\n" ANSI_RESET, g_stats.passed);
if (g_stats.failed > 0)
printf(ANSI_RED "Failed: %d\n" ANSI_RESET, g_stats.failed);
else
printf("Failed: 0\n");
if (g_stats.skipped > 0)
printf(ANSI_YELLOW "Skipped: %d\n" ANSI_RESET, g_stats.skipped);
double pass_rate = g_stats.total > 0
? (double)g_stats.passed / g_stats.total * 100.0
: 0.0;
printf("Pass rate: ");
if (pass_rate == 100.0)
printf(ANSI_GREEN "%.1f%%\n" ANSI_RESET, pass_rate);
else if (pass_rate >= 90.0)
printf(ANSI_YELLOW "%.1f%%\n" ANSI_RESET, pass_rate);
else
printf(ANSI_RED "%.1f%%\n" ANSI_RESET, pass_rate);
printf("Time: %.3f seconds\n", g_stats.elapsed_time);
printf(ANSI_CYAN "═══════════════════════════════════════\n" ANSI_RESET);
}
// シグナルハンドラ(クラッシュ時)
static void crash_handler(int sig)
{
const char *sig_name = sig == SIGSEGV ? "SIGSEGV" :
sig == SIGBUS ? "SIGBUS" :
sig == SIGABRT ? "SIGABRT" :
sig == SIGFPE ? "SIGFPE" : "UNKNOWN";
printf(ANSI_RED "\n\n CRASH: %s in test '%s'\n" ANSI_RESET,
sig_name, g_context.test_name ? g_context.test_name : "unknown");
printf(" at %s:%d\n",
g_context.file ? g_context.file : "unknown",
g_context.line);
g_stats.failed++;
g_stats.total++;
if (g_context.in_test)
longjmp(g_context.jump_buffer, 1);
else
exit(1);
}
// シグナルハンドラの設定
static void setup_signal_handlers(void)
{
struct sigaction sa;
sa.sa_handler = crash_handler;
sigemptyset(&sa.sa_mask);
sa.sa_flags = 0;
sigaction(SIGSEGV, &sa, NULL);
sigaction(SIGBUS, &sa, NULL);
sigaction(SIGABRT, &sa, NULL);
sigaction(SIGFPE, &sa, NULL);
}
7.6 libft関数のテスト実装
7.6.1 文字分類関数のテスト
// test_ctype.c - 文字関数のテスト
#include "libft_test.h"
#include <ctype.h> // 標準ライブラリとの比較用
// ft_isalpha のテスト
void test_ft_isalpha(void)
{
TEST_BEGIN("ft_isalpha");
printf("\n Testing ft_isalpha:\n");
// 基本ケース
printf(" Basic cases: ");
ASSERT_TRUE(ft_isalpha('a') != 0);
ASSERT_TRUE(ft_isalpha('z') != 0);
ASSERT_TRUE(ft_isalpha('A') != 0);
ASSERT_TRUE(ft_isalpha('Z') != 0);
ASSERT_EQ(0, ft_isalpha('0'));
ASSERT_EQ(0, ft_isalpha(' '));
ASSERT_EQ(0, ft_isalpha('@'));
// 境界値
printf("\n Boundary values: ");
ASSERT_TRUE(ft_isalpha('a') != 0); // 下限
ASSERT_TRUE(ft_isalpha('z') != 0); // 上限
ASSERT_TRUE(ft_isalpha('A') != 0); // 下限
ASSERT_TRUE(ft_isalpha('Z') != 0); // 上限
ASSERT_EQ(0, ft_isalpha('a' - 1)); // 境界外
ASSERT_EQ(0, ft_isalpha('z' + 1)); // 境界外
ASSERT_EQ(0, ft_isalpha('A' - 1)); // 境界外
ASSERT_EQ(0, ft_isalpha('Z' + 1)); // 境界外
// エッジケース
printf("\n Edge cases: ");
ASSERT_EQ(0, ft_isalpha(0)); // NULL文字
ASSERT_EQ(0, ft_isalpha(-1)); // 負値
ASSERT_EQ(0, ft_isalpha(128)); // 拡張ASCII
ASSERT_EQ(0, ft_isalpha(255)); // 最大バイト値
// 標準ライブラリとの網羅的比較
printf("\n Exhaustive comparison with standard isalpha: ");
int mismatches = 0;
for (int c = -128; c <= 255; c++)
{
int expected = (isalpha(c) != 0);
int actual = (ft_isalpha(c) != 0);
if (expected != actual)
mismatches++;
}
ASSERT_EQ(0, mismatches);
TEST_END();
}
// ft_isdigit のテスト
void test_ft_isdigit(void)
{
TEST_BEGIN("ft_isdigit");
printf("\n Testing ft_isdigit:\n");
// 全数字のテスト
printf(" All digits: ");
for (char c = '0'; c <= '9'; c++)
ASSERT_TRUE(ft_isdigit(c) != 0);
// 非数字
printf("\n Non-digits: ");
ASSERT_EQ(0, ft_isdigit('a'));
ASSERT_EQ(0, ft_isdigit('A'));
ASSERT_EQ(0, ft_isdigit(' '));
ASSERT_EQ(0, ft_isdigit('\n'));
ASSERT_EQ(0, ft_isdigit('0' - 1));
ASSERT_EQ(0, ft_isdigit('9' + 1));
// エッジケース
printf("\n Edge cases: ");
ASSERT_EQ(0, ft_isdigit(-1));
ASSERT_EQ(0, ft_isdigit(256));
TEST_END();
}
// ft_isalnum のテスト
void test_ft_isalnum(void)
{
TEST_BEGIN("ft_isalnum");
printf("\n Testing ft_isalnum:\n");
// isalpha と isdigit の和集合であることを検証
printf(" Property: isalnum = isalpha ∪ isdigit: ");
for (int c = 0; c < 128; c++)
{
int expected = (ft_isalpha(c) != 0) || (ft_isdigit(c) != 0);
int actual = (ft_isalnum(c) != 0);
ASSERT_EQ(expected, actual);
}
TEST_END();
}
// ft_toupper / ft_tolower のテスト
void test_ft_toupper_tolower(void)
{
TEST_BEGIN("ft_toupper and ft_tolower");
printf("\n Testing ft_toupper:\n");
// 小文字→大文字の変換
printf(" Lowercase to uppercase: ");
for (char c = 'a'; c <= 'z'; c++)
ASSERT_EQ(c - 32, ft_toupper(c));
// 大文字はそのまま
printf("\n Uppercase unchanged: ");
for (char c = 'A'; c <= 'Z'; c++)
ASSERT_EQ(c, ft_toupper(c));
// 非アルファベットはそのまま
printf("\n Non-alpha unchanged: ");
ASSERT_EQ('0', ft_toupper('0'));
ASSERT_EQ(' ', ft_toupper(' '));
ASSERT_EQ('@', ft_toupper('@'));
printf("\n\n Testing ft_tolower:\n");
// 大文字→小文字の変換
printf(" Uppercase to lowercase: ");
for (char c = 'A'; c <= 'Z'; c++)
ASSERT_EQ(c + 32, ft_tolower(c));
// 可逆性のテスト
printf("\n Reversibility property: ");
for (int c = 0; c < 128; c++)
{
// toupper(tolower(x)) == toupper(x)
// tolower(toupper(x)) == tolower(x)
ASSERT_EQ(ft_toupper(ft_tolower(c)), ft_toupper(c));
ASSERT_EQ(ft_tolower(ft_toupper(c)), ft_tolower(c));
}
TEST_END();
}
7.6.2 文字列関数のテスト
// test_string.c - 文字列関数のテスト
#include "libft_test.h"
void test_ft_strlen(void)
{
TEST_BEGIN("ft_strlen");
printf("\n Testing ft_strlen:\n");
// 基本ケース
printf(" Basic cases: ");
ASSERT_EQ(0, ft_strlen(""));
ASSERT_EQ(1, ft_strlen("a"));
ASSERT_EQ(5, ft_strlen("hello"));
ASSERT_EQ(11, ft_strlen("hello world"));
// 特殊文字
printf("\n Special characters: ");
ASSERT_EQ(1, ft_strlen("\n"));
ASSERT_EQ(1, ft_strlen("\t"));
ASSERT_EQ(3, ft_strlen("\n\t\r"));
// 埋め込みnull(最初のnullまで)
printf("\n Embedded null: ");
ASSERT_EQ(5, ft_strlen("hello\0world"));
// 長い文字列
printf("\n Long string: ");
char *long_str = malloc(10001);
if (long_str)
{
memset(long_str, 'x', 10000);
long_str[10000] = '\0';
ASSERT_EQ(10000, ft_strlen(long_str));
free(long_str);
}
// パフォーマンス比較
printf("\n Performance comparison with strlen:\n");
size_t sizes[] = {10, 100, 1000, 10000};
for (int s = 0; s < 4; s++)
{
char *test_str = malloc(sizes[s] + 1);
if (!test_str) continue;
memset(test_str, 'A', sizes[s]);
test_str[sizes[s]] = '\0';
clock_t start = clock();
for (int i = 0; i < 100000; i++)
{
volatile size_t len = ft_strlen(test_str);
(void)len;
}
double ft_time = (double)(clock() - start) / CLOCKS_PER_SEC;
start = clock();
for (int i = 0; i < 100000; i++)
{
volatile size_t len = strlen(test_str);
(void)len;
}
double std_time = (double)(clock() - start) / CLOCKS_PER_SEC;
printf(" Size %zu: ft=%.4fs, std=%.4fs (ratio: %.2fx)\n",
sizes[s], ft_time, std_time, ft_time / std_time);
free(test_str);
}
TEST_END();
}
void test_ft_strcpy(void)
{
TEST_BEGIN("ft_strcpy");
printf("\n Testing ft_strcpy:\n");
char dest[100];
// 基本ケース
printf(" Basic cases: ");
ASSERT_STR_EQ("hello", ft_strcpy(dest, "hello"));
ASSERT_STR_EQ("", ft_strcpy(dest, ""));
ASSERT_STR_EQ("a", ft_strcpy(dest, "a"));
// 戻り値がdstであることを確認
printf("\n Return value is dst: ");
ASSERT_TRUE(ft_strcpy(dest, "test") == dest);
// null終端の確認
printf("\n Null termination: ");
memset(dest, 'X', sizeof(dest));
ft_strcpy(dest, "hi");
ASSERT_EQ('\0', dest[2]);
ASSERT_EQ('X', dest[3]); // 余分な書き込みがないこと
TEST_END();
}
void test_ft_strncpy(void)
{
TEST_BEGIN("ft_strncpy");
printf("\n Testing ft_strncpy:\n");
char dest[20];
// n < strlen(src)
printf(" n < strlen(src): ");
memset(dest, 'X', sizeof(dest));
ft_strncpy(dest, "hello", 3);
ASSERT_EQ('h', dest[0]);
ASSERT_EQ('e', dest[1]);
ASSERT_EQ('l', dest[2]);
ASSERT_EQ('X', dest[3]); // null終端されない
// n == strlen(src)
printf("\n n == strlen(src): ");
memset(dest, 'X', sizeof(dest));
ft_strncpy(dest, "hello", 5);
ASSERT_EQ('o', dest[4]);
ASSERT_EQ('X', dest[5]); // null終端されない
// n > strlen(src)
printf("\n n > strlen(src) (zero padding): ");
memset(dest, 'X', sizeof(dest));
ft_strncpy(dest, "hi", 5);
ASSERT_EQ('h', dest[0]);
ASSERT_EQ('i', dest[1]);
ASSERT_EQ('\0', dest[2]);
ASSERT_EQ('\0', dest[3]);
ASSERT_EQ('\0', dest[4]);
ASSERT_EQ('X', dest[5]); // 余分な書き込みがない
// n == 0
printf("\n n == 0: ");
memset(dest, 'X', sizeof(dest));
ft_strncpy(dest, "hello", 0);
ASSERT_EQ('X', dest[0]); // 何も書き込まれない
TEST_END();
}
void test_ft_strlcpy(void)
{
TEST_BEGIN("ft_strlcpy");
printf("\n Testing ft_strlcpy:\n");
char dest[20];
// 基本ケース
printf(" Basic cases: ");
memset(dest, 'X', sizeof(dest));
ASSERT_EQ(5, ft_strlcpy(dest, "hello", sizeof(dest)));
ASSERT_STR_EQ("hello", dest);
// 切り詰め
printf("\n Truncation: ");
memset(dest, 'X', sizeof(dest));
ASSERT_EQ(10, ft_strlcpy(dest, "0123456789", 5)); // 戻り値はsrc長
ASSERT_STR_EQ("0123", dest); // 4文字 + null
ASSERT_EQ('\0', dest[4]);
// size == 0
printf("\n size == 0: ");
memset(dest, 'X', sizeof(dest));
ASSERT_EQ(5, ft_strlcpy(dest, "hello", 0));
ASSERT_EQ('X', dest[0]); // 何も書き込まれない
// size == 1
printf("\n size == 1: ");
memset(dest, 'X', sizeof(dest));
ASSERT_EQ(5, ft_strlcpy(dest, "hello", 1));
ASSERT_EQ('\0', dest[0]); // nullのみ
// 空文字列
printf("\n Empty source: ");
ASSERT_EQ(0, ft_strlcpy(dest, "", sizeof(dest)));
ASSERT_STR_EQ("", dest);
TEST_END();
}
void test_ft_strdup(void)
{
TEST_BEGIN("ft_strdup");
printf("\n Testing ft_strdup:\n");
char *result;
// 基本ケース
printf(" Basic cases: ");
result = ft_strdup("hello");
ASSERT_STR_EQ("hello", result);
free(result);
result = ft_strdup("");
ASSERT_STR_EQ("", result);
free(result);
// 独立性の確認(元の文字列との独立)
printf("\n Independence from source: ");
char original[] = "test";
result = ft_strdup(original);
original[0] = 'X';
ASSERT_EQ('t', result[0]); // 元の変更に影響されない
free(result);
// メモリリークテスト(大量の割り当て/解放)
printf("\n Memory leak test (1000 alloc/free): ");
for (int i = 0; i < 1000; i++)
{
result = ft_strdup("leak test string");
free(result);
}
ASSERT_TRUE(1); // クラッシュしなければOK
TEST_END();
}
void test_ft_strjoin(void)
{
TEST_BEGIN("ft_strjoin");
printf("\n Testing ft_strjoin:\n");
char *result;
// 基本ケース
printf(" Basic cases: ");
result = ft_strjoin("hello", " world");
ASSERT_STR_EQ("hello world", result);
free(result);
// 空文字列との結合
printf("\n Empty strings: ");
result = ft_strjoin("", "test");
ASSERT_STR_EQ("test", result);
free(result);
result = ft_strjoin("test", "");
ASSERT_STR_EQ("test", result);
free(result);
result = ft_strjoin("", "");
ASSERT_STR_EQ("", result);
free(result);
// 長い文字列
printf("\n Long strings: ");
char *long1 = malloc(1001);
char *long2 = malloc(1001);
if (long1 && long2)
{
memset(long1, 'A', 1000);
long1[1000] = '\0';
memset(long2, 'B', 1000);
long2[1000] = '\0';
result = ft_strjoin(long1, long2);
ASSERT_EQ(2000, ft_strlen(result));
ASSERT_EQ('A', result[0]);
ASSERT_EQ('A', result[999]);
ASSERT_EQ('B', result[1000]);
ASSERT_EQ('B', result[1999]);
free(result);
}
free(long1);
free(long2);
TEST_END();
}
void test_ft_split(void)
{
TEST_BEGIN("ft_split");
printf("\n Testing ft_split:\n");
char **result;
// 基本ケース
printf(" Basic split: ");
result = ft_split("hello world test", ' ');
ASSERT_STR_EQ("hello", result[0]);
ASSERT_STR_EQ("world", result[1]);
ASSERT_STR_EQ("test", result[2]);
ASSERT_TRUE(result[3] == NULL);
for (int i = 0; result[i]; i++)
free(result[i]);
free(result);
// 先頭/末尾/連続のデリミタ
printf("\n Multiple delimiters: ");
result = ft_split(" hello world ", ' ');
ASSERT_STR_EQ("hello", result[0]);
ASSERT_STR_EQ("world", result[1]);
ASSERT_TRUE(result[2] == NULL);
for (int i = 0; result[i]; i++)
free(result[i]);
free(result);
// 空文字列
printf("\n Empty string: ");
result = ft_split("", ' ');
ASSERT_TRUE(result[0] == NULL);
free(result);
// デリミタのみ
printf("\n Only delimiters: ");
result = ft_split(" ", ' ');
ASSERT_TRUE(result[0] == NULL);
free(result);
// デリミタなし
printf("\n No delimiter found: ");
result = ft_split("hello", ' ');
ASSERT_STR_EQ("hello", result[0]);
ASSERT_TRUE(result[1] == NULL);
free(result[0]);
free(result);
TEST_END();
}
7.6.3 メモリ関数のテスト
// test_memory.c - メモリ関数のテスト
#include "libft_test.h"
void test_ft_memset(void)
{
TEST_BEGIN("ft_memset");
printf("\n Testing ft_memset:\n");
unsigned char buffer[100];
unsigned char expected[100];
// 基本ケース
printf(" Basic cases: ");
memset(expected, 'A', 50);
ft_memset(buffer, 'A', 50);
ASSERT_MEM_EQ(expected, buffer, 50);
// 0での初期化
printf("\n Zero fill: ");
memset(expected, 0, 100);
ft_memset(buffer, 0, 100);
ASSERT_MEM_EQ(expected, buffer, 100);
// n == 0
printf("\n n == 0: ");
memset(buffer, 'X', 10);
ft_memset(buffer, 'Y', 0);
ASSERT_EQ('X', buffer[0]); // 変更されない
// 戻り値
printf("\n Return value: ");
ASSERT_TRUE(ft_memset(buffer, 'Z', 10) == buffer);
// アラインメントテスト
printf("\n Alignment test: ");
for (int offset = 0; offset < 8; offset++)
{
memset(buffer, 0, sizeof(buffer));
memset(expected, 0, sizeof(expected));
memset(expected + offset, 0xFF, 32);
ft_memset(buffer + offset, 0xFF, 32);
ASSERT_MEM_EQ(expected, buffer, sizeof(buffer));
}
TEST_END();
}
void test_ft_memcpy(void)
{
TEST_BEGIN("ft_memcpy");
printf("\n Testing ft_memcpy:\n");
unsigned char src[100];
unsigned char dst[100];
unsigned char expected[100];
// テストデータ準備
for (int i = 0; i < 100; i++)
src[i] = i;
// 基本ケース
printf(" Basic cases: ");
memset(dst, 0, sizeof(dst));
memcpy(expected, src, 50);
ft_memcpy(dst, src, 50);
ASSERT_MEM_EQ(expected, dst, 50);
// n == 0
printf("\n n == 0: ");
memset(dst, 'X', sizeof(dst));
ft_memcpy(dst, src, 0);
ASSERT_EQ('X', dst[0]); // 変更されない
// 戻り値
printf("\n Return value: ");
ASSERT_TRUE(ft_memcpy(dst, src, 10) == dst);
// アラインメントの組み合わせ
printf("\n Alignment combinations: ");
for (int src_off = 0; src_off < 4; src_off++)
{
for (int dst_off = 0; dst_off < 4; dst_off++)
{
memset(dst, 0, sizeof(dst));
memset(expected, 0, sizeof(expected));
memcpy(expected + dst_off, src + src_off, 32);
ft_memcpy(dst + dst_off, src + src_off, 32);
ASSERT_MEM_EQ(expected, dst, sizeof(dst));
}
}
TEST_END();
}
void test_ft_memmove(void)
{
TEST_BEGIN("ft_memmove");
printf("\n Testing ft_memmove:\n");
unsigned char buffer[100];
unsigned char expected[100];
// 重複しないコピー
printf(" Non-overlapping: ");
for (int i = 0; i < 100; i++)
{
buffer[i] = i;
expected[i] = i;
}
memmove(expected + 50, expected + 10, 20);
ft_memmove(buffer + 50, buffer + 10, 20);
ASSERT_MEM_EQ(expected, buffer, 100);
// 前方への重複(dst < src)
printf("\n Forward overlap (dst < src): ");
for (int i = 0; i < 100; i++)
{
buffer[i] = i;
expected[i] = i;
}
memmove(expected + 5, expected + 10, 30);
ft_memmove(buffer + 5, buffer + 10, 30);
ASSERT_MEM_EQ(expected, buffer, 100);
// 後方への重複(dst > src)
printf("\n Backward overlap (dst > src): ");
for (int i = 0; i < 100; i++)
{
buffer[i] = i;
expected[i] = i;
}
memmove(expected + 10, expected + 5, 30);
ft_memmove(buffer + 10, buffer + 5, 30);
ASSERT_MEM_EQ(expected, buffer, 100);
// 完全重複(dst == src)
printf("\n Complete overlap (dst == src): ");
for (int i = 0; i < 100; i++)
buffer[i] = i;
ft_memmove(buffer, buffer, 50);
for (int i = 0; i < 50; i++)
ASSERT_EQ(i, buffer[i]);
// n == 0
printf("\n n == 0: ");
memset(buffer, 'X', sizeof(buffer));
ft_memmove(buffer, buffer + 10, 0);
ASSERT_EQ('X', buffer[0]);
TEST_END();
}
void test_ft_memchr(void)
{
TEST_BEGIN("ft_memchr");
printf("\n Testing ft_memchr:\n");
unsigned char buffer[] = "hello\0world";
// 基本ケース
printf(" Basic cases: ");
ASSERT_TRUE(ft_memchr(buffer, 'h', 11) == buffer);
ASSERT_TRUE(ft_memchr(buffer, 'e', 11) == buffer + 1);
ASSERT_TRUE(ft_memchr(buffer, 'o', 11) == buffer + 4);
// null文字を検索
printf("\n Find null character: ");
ASSERT_TRUE(ft_memchr(buffer, '\0', 11) == buffer + 5);
// 見つからない
printf("\n Not found: ");
ASSERT_TRUE(ft_memchr(buffer, 'z', 11) == NULL);
// n が小さい場合
printf("\n Limited search: ");
ASSERT_TRUE(ft_memchr(buffer, 'o', 3) == NULL); // 'o'は位置4
// n == 0
printf("\n n == 0: ");
ASSERT_TRUE(ft_memchr(buffer, 'h', 0) == NULL);
TEST_END();
}
void test_ft_memcmp(void)
{
TEST_BEGIN("ft_memcmp");
printf("\n Testing ft_memcmp:\n");
// 等しい
printf(" Equal memory: ");
ASSERT_EQ(0, ft_memcmp("hello", "hello", 5));
ASSERT_EQ(0, ft_memcmp("hello", "help", 3)); // 最初の3バイトは同じ
// 異なる
printf("\n Different memory: ");
ASSERT_TRUE(ft_memcmp("hello", "hallo", 5) > 0); // 'e' > 'a'
ASSERT_TRUE(ft_memcmp("hallo", "hello", 5) < 0); // 'a' < 'e'
// null文字を含む
printf("\n With null characters: ");
ASSERT_EQ(0, ft_memcmp("hel\0lo", "hel\0lo", 6));
ASSERT_TRUE(ft_memcmp("hel\0lo", "hel\0la", 6) > 0);
// n == 0
printf("\n n == 0: ");
ASSERT_EQ(0, ft_memcmp("hello", "world", 0));
// 符号なし比較
printf("\n Unsigned comparison: ");
unsigned char a[] = {200, 100};
unsigned char b[] = {100, 200};
ASSERT_TRUE(ft_memcmp(a, b, 2) > 0); // 200 > 100(符号なしとして)
TEST_END();
}
7.6.4 連結リスト関数のテスト
// test_list.c - 連結リスト関数のテスト
#include "libft_test.h"
// テスト用のヘルパー関数
static void del_content(void *content)
{
free(content);
}
static void *dup_content(void *content)
{
return ft_strdup((char *)content);
}
void test_ft_lstnew(void)
{
TEST_BEGIN("ft_lstnew");
printf("\n Testing ft_lstnew:\n");
// 基本ケース
printf(" Basic cases: ");
char *content = "hello";
t_list *node = ft_lstnew(content);
ASSERT_TRUE(node != NULL);
ASSERT_TRUE(node->content == content);
ASSERT_TRUE(node->next == NULL);
free(node);
// NULLコンテンツ
printf("\n NULL content: ");
node = ft_lstnew(NULL);
ASSERT_TRUE(node != NULL);
ASSERT_TRUE(node->content == NULL);
ASSERT_TRUE(node->next == NULL);
free(node);
TEST_END();
}
void test_ft_lstadd_front(void)
{
TEST_BEGIN("ft_lstadd_front");
printf("\n Testing ft_lstadd_front:\n");
t_list *head = NULL;
t_list *node1 = ft_lstnew("first");
t_list *node2 = ft_lstnew("second");
t_list *node3 = ft_lstnew("third");
// 空リストへの追加
printf(" Add to empty list: ");
ft_lstadd_front(&head, node1);
ASSERT_TRUE(head == node1);
ASSERT_STR_EQ("first", head->content);
// 先頭への追加
printf("\n Add to front: ");
ft_lstadd_front(&head, node2);
ASSERT_TRUE(head == node2);
ASSERT_STR_EQ("second", head->content);
ASSERT_TRUE(head->next == node1);
ft_lstadd_front(&head, node3);
ASSERT_TRUE(head == node3);
ASSERT_STR_EQ("third", head->content);
// 順序確認
printf("\n Order check: ");
ASSERT_STR_EQ("third", head->content);
ASSERT_STR_EQ("second", head->next->content);
ASSERT_STR_EQ("first", head->next->next->content);
// クリーンアップ
free(node1);
free(node2);
free(node3);
TEST_END();
}
void test_ft_lstsize(void)
{
TEST_BEGIN("ft_lstsize");
printf("\n Testing ft_lstsize:\n");
// 空リスト
printf(" Empty list: ");
ASSERT_EQ(0, ft_lstsize(NULL));
// 1要素
printf("\n One element: ");
t_list *node1 = ft_lstnew("1");
ASSERT_EQ(1, ft_lstsize(node1));
// 複数要素
printf("\n Multiple elements: ");
t_list *node2 = ft_lstnew("2");
t_list *node3 = ft_lstnew("3");
node1->next = node2;
node2->next = node3;
ASSERT_EQ(3, ft_lstsize(node1));
free(node1);
free(node2);
free(node3);
TEST_END();
}
void test_ft_lstlast(void)
{
TEST_BEGIN("ft_lstlast");
printf("\n Testing ft_lstlast:\n");
// 空リスト
printf(" Empty list: ");
ASSERT_TRUE(ft_lstlast(NULL) == NULL);
// 1要素
printf("\n One element: ");
t_list *node1 = ft_lstnew("first");
ASSERT_TRUE(ft_lstlast(node1) == node1);
// 複数要素
printf("\n Multiple elements: ");
t_list *node2 = ft_lstnew("second");
t_list *node3 = ft_lstnew("last");
node1->next = node2;
node2->next = node3;
ASSERT_TRUE(ft_lstlast(node1) == node3);
ASSERT_STR_EQ("last", ft_lstlast(node1)->content);
free(node1);
free(node2);
free(node3);
TEST_END();
}
void test_ft_lstclear(void)
{
TEST_BEGIN("ft_lstclear");
printf("\n Testing ft_lstclear:\n");
// リストの作成(動的に割り当てたコンテンツ)
t_list *head = ft_lstnew(ft_strdup("first"));
head->next = ft_lstnew(ft_strdup("second"));
head->next->next = ft_lstnew(ft_strdup("third"));
printf(" Clear list with 3 elements: ");
ft_lstclear(&head, del_content);
ASSERT_TRUE(head == NULL);
// 空リスト
printf("\n Clear empty list: ");
head = NULL;
ft_lstclear(&head, del_content); // クラッシュしないこと
ASSERT_TRUE(head == NULL);
TEST_END();
}
void test_ft_lstmap(void)
{
TEST_BEGIN("ft_lstmap");
printf("\n Testing ft_lstmap:\n");
// リストの作成
t_list *src = ft_lstnew("hello");
src->next = ft_lstnew("world");
src->next->next = ft_lstnew("test");
// マップ
printf(" Map with strdup: ");
t_list *dst = ft_lstmap(src, dup_content, del_content);
ASSERT_TRUE(dst != NULL);
ASSERT_STR_EQ("hello", dst->content);
ASSERT_STR_EQ("world", dst->next->content);
ASSERT_STR_EQ("test", dst->next->next->content);
ASSERT_TRUE(dst->next->next->next == NULL);
// 独立性(srcとdstは別のメモリ)
printf("\n Independence check: ");
ASSERT_TRUE(dst->content != src->content);
// クリーンアップ
ft_lstclear(&dst, del_content);
free(src->next->next);
free(src->next);
free(src);
// 空リスト
printf("\n Map empty list: ");
dst = ft_lstmap(NULL, dup_content, del_content);
ASSERT_TRUE(dst == NULL);
TEST_END();
}
7.7 高度なテスト技法
7.7.1 プロパティベーステスト
プロパティベーステストは、具体的な値ではなく、満たすべき性質をテストする。
// test_property.c - プロパティベーステスト
#include "libft_test.h"
// 擬似乱数生成(再現性のため)
static unsigned int g_seed = 12345;
static unsigned int rand_next(void)
{
g_seed = g_seed * 1103515245 + 12345;
return (g_seed / 65536) % 32768;
}
static char *random_string(size_t max_len)
{
size_t len = rand_next() % (max_len + 1);
char *str = malloc(len + 1);
if (!str)
return NULL;
for (size_t i = 0; i < len; i++)
str[i] = 32 + (rand_next() % 95);
str[len] = '\0';
return str;
}
void test_property_strlen(void)
{
TEST_BEGIN("Property: strlen");
printf("\n Testing strlen properties:\n");
// プロパティ1: strlen(s) >= 0
printf(" Property: result >= 0: ");
for (int i = 0; i < 100; i++)
{
char *s = random_string(100);
if (s)
{
size_t len = ft_strlen(s);
ASSERT_TRUE(len >= 0); // size_t は常に >= 0
free(s);
}
}
// プロパティ2: strlen(s) <= allocated_size - 1
printf("\n Property: result <= max_len: ");
for (int i = 0; i < 100; i++)
{
size_t max_len = rand_next() % 100 + 1;
char *s = random_string(max_len);
if (s)
{
ASSERT_TRUE(ft_strlen(s) <= max_len);
free(s);
}
}
// プロパティ3: s[strlen(s)] == '\0'
printf("\n Property: s[strlen(s)] == '\\0': ");
for (int i = 0; i < 100; i++)
{
char *s = random_string(50);
if (s)
{
ASSERT_EQ('\0', s[ft_strlen(s)]);
free(s);
}
}
TEST_END();
}
void test_property_memcpy(void)
{
TEST_BEGIN("Property: memcpy");
printf("\n Testing memcpy properties:\n");
// プロパティ1: コピー後、dst == src (内容)
printf(" Property: dst content == src content: ");
for (int i = 0; i < 100; i++)
{
size_t size = rand_next() % 100 + 1;
unsigned char *src = malloc(size);
unsigned char *dst = malloc(size);
if (src && dst)
{
for (size_t j = 0; j < size; j++)
src[j] = rand_next() % 256;
ft_memcpy(dst, src, size);
ASSERT_MEM_EQ(src, dst, size);
}
free(src);
free(dst);
}
// プロパティ2: 戻り値 == dst
printf("\n Property: return value == dst: ");
unsigned char src[10], dst[10];
ASSERT_TRUE(ft_memcpy(dst, src, 10) == dst);
TEST_END();
}
void test_property_strjoin(void)
{
TEST_BEGIN("Property: strjoin");
printf("\n Testing strjoin properties:\n");
// プロパティ1: strlen(result) == strlen(s1) + strlen(s2)
printf(" Property: len(result) = len(s1) + len(s2): ");
for (int i = 0; i < 100; i++)
{
char *s1 = random_string(50);
char *s2 = random_string(50);
if (s1 && s2)
{
char *result = ft_strjoin(s1, s2);
if (result)
{
ASSERT_EQ(ft_strlen(s1) + ft_strlen(s2), ft_strlen(result));
free(result);
}
}
free(s1);
free(s2);
}
// プロパティ2: result は s1 で始まる
printf("\n Property: result starts with s1: ");
for (int i = 0; i < 50; i++)
{
char *s1 = random_string(30);
char *s2 = random_string(30);
if (s1 && s2)
{
char *result = ft_strjoin(s1, s2);
if (result)
{
ASSERT_EQ(0, ft_strncmp(result, s1, ft_strlen(s1)));
free(result);
}
}
free(s1);
free(s2);
}
// プロパティ3: result は s2 で終わる
printf("\n Property: result ends with s2: ");
for (int i = 0; i < 50; i++)
{
char *s1 = random_string(30);
char *s2 = random_string(30);
if (s1 && s2 && ft_strlen(s1) > 0)
{
char *result = ft_strjoin(s1, s2);
if (result)
{
size_t s1_len = ft_strlen(s1);
ASSERT_STR_EQ(s2, result + s1_len);
free(result);
}
}
free(s1);
free(s2);
}
TEST_END();
}
7.7.2 ファズテスト
// test_fuzz.c - ファジングテスト
#include "libft_test.h"
// ランダムなメモリ領域を生成
static void *random_memory(size_t size)
{
unsigned char *mem = malloc(size);
if (!mem)
return NULL;
for (size_t i = 0; i < size; i++)
mem[i] = rand() % 256;
return mem;
}
void test_fuzz_memory_functions(void)
{
TEST_BEGIN("Fuzz: Memory Functions");
printf("\n Fuzzing memory functions (1000 iterations):\n");
srand(time(NULL));
int crashes = 0;
for (int iter = 0; iter < 1000; iter++)
{
size_t size = rand() % 1000 + 1;
void *src = random_memory(size);
void *dst = malloc(size);
if (src && dst)
{
// memset
int c = rand() % 256;
ft_memset(dst, c, size);
// memcpy
ft_memcpy(dst, src, size);
// memmove(重複ケース)
size_t offset = rand() % (size / 2 + 1);
ft_memmove(dst + offset, dst, size - offset);
// memchr
int target = rand() % 256;
ft_memchr(src, target, size);
// memcmp
ft_memcmp(src, dst, size);
}
free(src);
free(dst);
}
printf(" Completed without crashes: ");
ASSERT_EQ(0, crashes);
TEST_END();
}
void test_fuzz_string_functions(void)
{
TEST_BEGIN("Fuzz: String Functions");
printf("\n Fuzzing string functions (1000 iterations):\n");
srand(time(NULL));
for (int iter = 0; iter < 1000; iter++)
{
// ランダムな長さの文字列を生成
size_t len1 = rand() % 100;
size_t len2 = rand() % 100;
char *s1 = malloc(len1 + 1);
char *s2 = malloc(len2 + 1);
char *dst = malloc(len1 + len2 + 100);
if (s1 && s2 && dst)
{
// 印刷可能な文字でランダム文字列を生成
for (size_t i = 0; i < len1; i++)
s1[i] = 32 + (rand() % 95);
s1[len1] = '\0';
for (size_t i = 0; i < len2; i++)
s2[i] = 32 + (rand() % 95);
s2[len2] = '\0';
// 各関数をテスト
ft_strlen(s1);
ft_strcpy(dst, s1);
ft_strncpy(dst, s1, len1 / 2);
ft_strlcpy(dst, s1, len1 + 1);
ft_strcat(dst, s2);
ft_strncat(dst, s2, len2 / 2);
ft_strcmp(s1, s2);
ft_strncmp(s1, s2, len1);
ft_strchr(s1, s1[len1 / 2]);
ft_strrchr(s1, s1[len1 / 2]);
ft_strstr(s1, len2 > 3 ? s2 : "");
char *dup = ft_strdup(s1);
free(dup);
char *join = ft_strjoin(s1, s2);
free(join);
}
free(s1);
free(s2);
free(dst);
}
printf(" Completed without crashes: ");
ASSERT_TRUE(1);
TEST_END();
}
void test_fuzz_split(void)
{
TEST_BEGIN("Fuzz: ft_split");
printf("\n Fuzzing ft_split (500 iterations):\n");
srand(time(NULL));
for (int iter = 0; iter < 500; iter++)
{
// ランダムな文字列とデリミタ
size_t len = rand() % 200;
char *str = malloc(len + 1);
char delim = 32 + (rand() % 95);
if (str)
{
for (size_t i = 0; i < len; i++)
{
if (rand() % 5 == 0)
str[i] = delim;
else
str[i] = 32 + (rand() % 95);
}
str[len] = '\0';
char **result = ft_split(str, delim);
if (result)
{
// 全要素を解放
for (int i = 0; result[i]; i++)
free(result[i]);
free(result);
}
}
free(str);
}
printf(" Completed without crashes or leaks: ");
ASSERT_TRUE(1);
TEST_END();
}
7.8 デバッグユーティリティ
7.8.1 メモリ追跡システム
// debug_memory.h - メモリ追跡
#ifndef DEBUG_MEMORY_H
# define DEBUG_MEMORY_H
# include <stdlib.h>
# include <stdio.h>
// メモリ割り当て記録
typedef struct s_mem_record {
void *ptr;
size_t size;
const char *file;
int line;
const char *func;
struct s_mem_record *next;
} t_mem_record;
// デバッグ用malloc/free
# ifdef DEBUG_MEMORY
# define malloc(size) debug_malloc(size, __FILE__, __LINE__, __func__)
# define free(ptr) debug_free(ptr, __FILE__, __LINE__, __func__)
# endif
void *debug_malloc(size_t size, const char *file, int line, const char *func);
void debug_free(void *ptr, const char *file, int line, const char *func);
void debug_memory_report(void);
void debug_memory_check(void *ptr);
#endif
// debug_memory.c
#include "debug_memory.h"
#include <string.h>
static t_mem_record *g_records = NULL;
static size_t g_total_allocated = 0;
static size_t g_total_freed = 0;
static int g_alloc_count = 0;
static int g_free_count = 0;
void *debug_malloc(size_t size, const char *file, int line, const char *func)
{
// 標準のmallocを直接呼び出し
void *ptr = (malloc)(size); // 括弧でマクロ展開を防ぐ
if (ptr)
{
// 記録を作成
t_mem_record *record = (malloc)(sizeof(t_mem_record));
if (record)
{
record->ptr = ptr;
record->size = size;
record->file = file;
record->line = line;
record->func = func;
record->next = g_records;
g_records = record;
g_total_allocated += size;
g_alloc_count++;
// デバッグ出力
fprintf(stderr, "[MALLOC] %p: %zu bytes at %s:%d in %s\n",
ptr, size, file, line, func);
}
}
return ptr;
}
void debug_free(void *ptr, const char *file, int line, const char *func)
{
if (!ptr)
{
fprintf(stderr, "[FREE] NULL pointer at %s:%d in %s\n",
file, line, func);
return;
}
// 記録を検索
t_mem_record **current = &g_records;
while (*current)
{
if ((*current)->ptr == ptr)
{
t_mem_record *to_free = *current;
*current = (*current)->next;
g_total_freed += to_free->size;
g_free_count++;
fprintf(stderr, "[FREE] %p: %zu bytes at %s:%d in %s "
"(allocated at %s:%d)\n",
ptr, to_free->size, file, line, func,
to_free->file, to_free->line);
(free)(to_free);
(free)(ptr);
return;
}
current = &(*current)->next;
}
// 記録にない(二重解放または不正なポインタ)
fprintf(stderr, "[FREE] Unknown pointer %p at %s:%d in %s\n",
ptr, file, line, func);
}
void debug_memory_report(void)
{
fprintf(stderr, "\n========== MEMORY REPORT ==========\n");
fprintf(stderr, "Total allocations: %d (%zu bytes)\n",
g_alloc_count, g_total_allocated);
fprintf(stderr, "Total frees: %d (%zu bytes)\n",
g_free_count, g_total_freed);
fprintf(stderr, "Balance: %zu bytes\n",
g_total_allocated - g_total_freed);
if (g_records)
{
fprintf(stderr, "\n--- LEAKED MEMORY ---\n");
size_t leaked = 0;
for (t_mem_record *r = g_records; r; r = r->next)
{
fprintf(stderr, " %p: %zu bytes - %s:%d in %s\n",
r->ptr, r->size, r->file, r->line, r->func);
leaked += r->size;
}
fprintf(stderr, "Total leaked: %zu bytes\n", leaked);
}
else
{
fprintf(stderr, "\nNo memory leaks detected!\n");
}
fprintf(stderr, "===================================\n");
}
7.8.2 ヘックスダンプユーティリティ
// debug_hexdump.c
#include <stdio.h>
#include <ctype.h>
void hexdump(const void *data, size_t size, const char *label)
{
const unsigned char *p = data;
fprintf(stderr, "\n=== %s (%zu bytes) ===\n", label, size);
for (size_t i = 0; i < size; i += 16)
{
// オフセット
fprintf(stderr, "%08zx ", i);
// 16進数部分
for (size_t j = 0; j < 16; j++)
{
if (i + j < size)
fprintf(stderr, "%02x ", p[i + j]);
else
fprintf(stderr, " ");
if (j == 7)
fprintf(stderr, " ");
}
fprintf(stderr, " |");
// ASCII部分
for (size_t j = 0; j < 16 && i + j < size; j++)
{
unsigned char c = p[i + j];
fprintf(stderr, "%c", isprint(c) ? c : '.');
}
fprintf(stderr, "|\n");
}
fprintf(stderr, "\n");
}
// 2つのメモリ領域の差分を表示
void hexdiff(const void *expected, const void *actual, size_t size)
{
const unsigned char *exp = expected;
const unsigned char *act = actual;
fprintf(stderr, "\n=== MEMORY DIFF ===\n");
fprintf(stderr, "%-10s %-20s %-20s\n", "Offset", "Expected", "Actual");
int diff_count = 0;
for (size_t i = 0; i < size && diff_count < 20; i++)
{
if (exp[i] != act[i])
{
fprintf(stderr, "0x%08zx: 0x%02x ('%c') -> 0x%02x ('%c')\n",
i,
exp[i], isprint(exp[i]) ? exp[i] : '.',
act[i], isprint(act[i]) ? act[i] : '.');
diff_count++;
}
}
if (diff_count == 20)
fprintf(stderr, "... (more differences)\n");
else if (diff_count == 0)
fprintf(stderr, "(no differences)\n");
}
7.9 テストの自動化
7.9.1 テストランナー
// test_runner.c - メインテストランナー
#include "libft_test.h"
// 外部テスト関数の宣言
extern void test_ft_isalpha(void);
extern void test_ft_isdigit(void);
extern void test_ft_isalnum(void);
extern void test_ft_toupper_tolower(void);
extern void test_ft_strlen(void);
extern void test_ft_strcpy(void);
extern void test_ft_strncpy(void);
extern void test_ft_strlcpy(void);
extern void test_ft_strdup(void);
extern void test_ft_strjoin(void);
extern void test_ft_split(void);
extern void test_ft_memset(void);
extern void test_ft_memcpy(void);
extern void test_ft_memmove(void);
extern void test_ft_memchr(void);
extern void test_ft_memcmp(void);
extern void test_ft_lstnew(void);
extern void test_ft_lstadd_front(void);
extern void test_ft_lstsize(void);
extern void test_ft_lstlast(void);
extern void test_ft_lstclear(void);
extern void test_ft_lstmap(void);
extern void test_property_strlen(void);
extern void test_property_memcpy(void);
extern void test_property_strjoin(void);
extern void test_fuzz_memory_functions(void);
extern void test_fuzz_string_functions(void);
extern void test_fuzz_split(void);
// テストエントリ
typedef struct s_test_entry {
const char *name;
void (*func)(void);
const char *category;
} t_test_entry;
static t_test_entry g_tests[] = {
// Character functions
{"ft_isalpha", test_ft_isalpha, "ctype"},
{"ft_isdigit", test_ft_isdigit, "ctype"},
{"ft_isalnum", test_ft_isalnum, "ctype"},
{"ft_toupper/tolower", test_ft_toupper_tolower, "ctype"},
// String functions
{"ft_strlen", test_ft_strlen, "string"},
{"ft_strcpy", test_ft_strcpy, "string"},
{"ft_strncpy", test_ft_strncpy, "string"},
{"ft_strlcpy", test_ft_strlcpy, "string"},
{"ft_strdup", test_ft_strdup, "string"},
{"ft_strjoin", test_ft_strjoin, "string"},
{"ft_split", test_ft_split, "string"},
// Memory functions
{"ft_memset", test_ft_memset, "memory"},
{"ft_memcpy", test_ft_memcpy, "memory"},
{"ft_memmove", test_ft_memmove, "memory"},
{"ft_memchr", test_ft_memchr, "memory"},
{"ft_memcmp", test_ft_memcmp, "memory"},
// List functions
{"ft_lstnew", test_ft_lstnew, "list"},
{"ft_lstadd_front", test_ft_lstadd_front, "list"},
{"ft_lstsize", test_ft_lstsize, "list"},
{"ft_lstlast", test_ft_lstlast, "list"},
{"ft_lstclear", test_ft_lstclear, "list"},
{"ft_lstmap", test_ft_lstmap, "list"},
// Property-based tests
{"property: strlen", test_property_strlen, "property"},
{"property: memcpy", test_property_memcpy, "property"},
{"property: strjoin", test_property_strjoin, "property"},
// Fuzz tests
{"fuzz: memory", test_fuzz_memory_functions, "fuzz"},
{"fuzz: string", test_fuzz_string_functions, "fuzz"},
{"fuzz: split", test_fuzz_split, "fuzz"},
{NULL, NULL, NULL}
};
void run_all_tests(void)
{
const char *current_category = NULL;
for (int i = 0; g_tests[i].name; i++)
{
// カテゴリヘッダー
if (!current_category || strcmp(current_category, g_tests[i].category) != 0)
{
current_category = g_tests[i].category;
printf("\n" ANSI_CYAN "━━━ %s ━━━" ANSI_RESET "\n", current_category);
}
// テスト実行
g_tests[i].func();
}
}
int main(int argc, char **argv)
{
// 使用方法
if (argc > 1 && strcmp(argv[1], "--help") == 0)
{
printf("Usage: %s [options]\n", argv[0]);
printf("Options:\n");
printf(" --help Show this help\n");
printf(" --list List all tests\n");
printf(" --filter Run tests matching pattern\n");
return 0;
}
// テスト一覧
if (argc > 1 && strcmp(argv[1], "--list") == 0)
{
printf("Available tests:\n");
for (int i = 0; g_tests[i].name; i++)
printf(" [%s] %s\n", g_tests[i].category, g_tests[i].name);
return 0;
}
// ヘッダー
printf(ANSI_CYAN);
printf("╔═══════════════════════════════════════╗\n");
printf("║ LIBFT TEST SUITE v1.0 ║\n");
printf("╚═══════════════════════════════════════╝\n");
printf(ANSI_RESET);
// シグナルハンドラ設定
setup_signal_handlers();
// 全テスト実行
run_all_tests();
// サマリー
print_test_summary();
return g_stats.failed > 0 ? 1 : 0;
}
7.9.2 Makefile
# Makefile for libft tests
NAME = test_libft
CC = cc
CFLAGS = -Wall -Wextra -Werror -g
LIBFT_DIR = ..
LIBFT = $(LIBFT_DIR)/libft.a
# ソースファイル
SRCS = test_runner.c \
libft_test.c \
test_ctype.c \
test_string.c \
test_memory.c \
test_list.c \
test_property.c \
test_fuzz.c
OBJS = $(SRCS:.c=.o)
# インクルード
INCLUDES = -I$(LIBFT_DIR) -I.
all: $(NAME)
$(NAME): $(OBJS) $(LIBFT)
$(CC) $(CFLAGS) $(OBJS) $(LIBFT) -o $(NAME)
$(LIBFT):
$(MAKE) -C $(LIBFT_DIR)
%.o: %.c
$(CC) $(CFLAGS) $(INCLUDES) -c ___CODE_BLOCK_44___lt; -o $@
# テスト実行
test: $(NAME)
./$(NAME)
# Valgrindでテスト
valgrind: $(NAME)
valgrind --leak-check=full --show-leak-kinds=all ./$(NAME)
# カバレッジ
coverage: CFLAGS += --coverage
coverage: clean $(NAME)
./$(NAME)
gcov $(SRCS)
# クリーン
clean:
rm -f $(OBJS)
rm -f *.gcda *.gcno *.gcov
fclean: clean
rm -f $(NAME)
re: fclean all
.PHONY: all test valgrind coverage clean fclean re
まとめ
この章では、ソフトウェアテストの理論的基礎から実践的なテスト実装まで、包括的に学んだ。
学んだ概念
- テスト理論の基礎
- テスト設計技法
- テスト開発手法
- デバッグ技法
- 実践的テスト実装
重要な教訓
- テストはバグの不在を証明できないが、存在を発見できる
- 効果的なテストは、理論に基づいた戦略的なケース選択が鍵
- デバッグは科学的プロセスであり、仮説検証の繰り返し
- 自動化されたテストは、コードの品質と開発速度の両方を向上させる