mcpField
S
shell-testing-framework

shell-testing-framework

github.com/manutejテスト・品質62439 回インストール39 週間前に更新
リポジトリ

shell-testing-framework について

unix-gotoのテストパターンに基づく、Bashスクリプト開発者向けのテストスキルです。単体、統合、境界、性能テストの体系化と、再利用可能なアサーションやヘルパーを扱います。

主な機能

  1. 01単体、統合、境界、性能テスト
  2. 02Arrange-Act-Assertのテスト構成
  3. 03再利用可能なアサーションパターン
  4. 04テストヘルパーとスイートの整理
  5. 05カバレッジと性能の検証

活用シーン

  1. 01Bashスクリプト関数のテスト作成
  2. 02動作カテゴリ別のテスト整理
  3. 03出力、終了コード、ファイルの確認
  4. 04シェル関数の性能測定

インストール

コマンドをコピーしてターミナルで実行します。

$npx skills add https://github.com/manutej/luxor-claude-marketplace --skill shell-testing-framework

インストール後、Claude Code で/shell-testing-frameworkを入力して読み込まれたか確認します。

同梱ファイルREADME.md

詳細

シェルテストフレームワークのエキスパート

unix-goto プロジェクトのパターンと方法論を活用した、bash シェルスクリプトの包括的なテストの専門知識です。100%のテストカバレッジ、体系的なテスト構成、パフォーマンス検証を重視します。

このスキルを使用する場合

次の場合にこのスキルを使用します:

  • bash シェルスクリプトのテストスイートを作成するとき
  • 100%のテストカバレッジ要件を実装するとき
  • テストをユニット、統合、エッジケース、パフォーマンスのカテゴリに整理するとき
  • シェルスクリプト検証用のアサーションパターンを作成するとき
  • テストインフラストラクチャとヘルパーを設定するとき
  • シェル関数のパフォーマンステストを書くとき
  • テストレポートとサマリーを生成するとき
  • テストの失敗をデバッグするとき
  • シェルスクリプトの動作を検証するとき

次の場合はこのスキルを使用しないでください:

  • シェル以外のアプリケーションをテストするとき(言語固有のフレームワークを使用してください)
  • 単純なアドホックのスクリプト検証
  • 本番環境のテスト(開発/CI 専用です)
  • 一般的な QA テスト(開発者向けのユニットテストです)

テストの基本理念

100%カバレッジのルール

unix-goto のすべてのコア機能は、100%のテストカバレッジを備えています。これは絶対条件です。

カバレッジ要件:

  • コアナビゲーション:100%
  • キャッシュシステム:100%
  • ブックマーク:100%
  • 履歴:100%
  • ベンチマーク:100%
  • 新機能:100%

これが意味すること:

  • すべての関数にテストがある
  • すべてのコードパスが実行される
  • すべてのエラー条件が検証される
  • すべてのエッジケースが網羅される
  • すべてのパフォーマンス目標が検証される
テスト駆動開発のアプローチ

ワークフロー:

  1. まずテストを書く(機能仕様に基づく)
  2. テストが失敗することを確認する(レッド)
  3. 機能を実装する
  4. テストが成功することを確認する(グリーン)
  5. 必要に応じてリファクタリングする
  6. すべてのテストが引き続き成功することを検証する

コア知識

標準的なテストファイル構成

すべてのテストファイルは、次の正確な構成に従います:

#!/bin/bash
# Test suite for [feature] functionality

set -e  # Exit on error

# ============================================
# Setup
# ============================================

SCRIPT_DIR="$(cd "$(dirname "$0")" && pwd)"
source "$SCRIPT_DIR/lib/module.sh"

# ============================================
# Test Counters
# ============================================

TESTS_PASSED=0
TESTS_FAILED=0

# ============================================
# Test Helpers
# ============================================

pass() {
    echo "✓ PASS: $1"
    ((TESTS_PASSED++))
}

fail() {
    echo "✗ FAIL: $1"
    ((TESTS_FAILED++))
}

# ============================================
# Test Functions
# ============================================

# Test 1: [Category] - [Description]
test_feature_basic() {
    # Arrange
    local input="test"
    local expected="expected_output"

    # Act
    local result=$(function_under_test "$input")

    # Assert
    if [[ "$result" == "$expected" ]]; then
        pass "Basic feature test"
    else
        fail "Basic feature test: expected '$expected', got '$result'"
    fi
}

# ============================================
# Test Execution
# ============================================

# Run all tests
test_feature_basic

# ============================================
# Summary
# ============================================

echo ""
echo "═══════════════════════════════════════"
echo "Tests passed: $TESTS_PASSED"
echo "Tests failed: $TESTS_FAILED"
echo "═══════════════════════════════════════"

# Exit with proper code
[ $TESTS_FAILED -eq 0 ] && exit 0 || exit 1
Arrange-Act-Assert パターン

すべてのテスト関数は、必ず次の3段階の構成に従います:

1. Arrange - テスト条件を設定する

# Arrange
local input="test-value"
local expected="expected-result"
local temp_file=$(mktemp)
echo "test data" > "$temp_file"

2. Act - テスト対象のコードを実行する

# Act
local result=$(function_under_test "$input")
local exit_code=$?

3. Assert - 結果を検証する

# Assert
if [[ "$result" == "$expected" && $exit_code -eq 0 ]]; then
    pass "Test description"
else
    fail "Test failed: expected '$expected', got '$result'"
fi

完全な例:

test_cache_lookup_single_match() {
    # Arrange - Create cache with single match
    local cache_file="$HOME/.goto_index"
    cat > "$cache_file" << EOF
# unix-goto folder index cache
#---
unix-goto|/Users/manu/Git_Repos/unix-goto|2|1234567890
EOF

    # Act - Lookup folder
    local result=$(__goto_cache_lookup "unix-goto")
    local exit_code=$?

    # Assert - Should return exact path
    local expected="/Users/manu/Git_Repos/unix-goto"
    if [[ "$result" == "$expected" && $exit_code -eq 0 ]]; then
        pass "Cache lookup returns single match"
    else
        fail "Expected '$expected' with code 0, got '$result' with code $exit_code"
    fi
}
4つのテストカテゴリ

すべての機能で、4つすべてのカテゴリのテストが必要です:

カテゴリ1:ユニットテスト

目的: 個々の関数を分離してテストする

特徴:

  • テスト対象の関数は1つ
  • 依存関係は最小限
  • 実行が高速(テストあたり1ms未満)
  • 明確で的を絞ったアサーション

例 - キャッシュ検索のユニットテスト:

test_cache_lookup_not_found() {
    # Arrange
    local cache_file="$HOME/.goto_index"
    cat > "$cache_file" << EOF
# unix-goto folder index cache
#---
unix-goto|/Users/manu/Git_Repos/unix-goto|2|1234567890
EOF

    # Act
    local result=$(__goto_cache_lookup "nonexistent")
    local exit_code=$?

    # Assert
    if [[ -z "$result" && $exit_code -eq 1 ]]; then
        pass "Cache lookup not found returns code 1"
    else
        fail "Expected empty result with code 1, got '$result' with code $exit_code"
    fi
}

test_cache_lookup_multiple_matches() {
    # Arrange
    local cache_file="$HOME/.goto_index"
    cat > "$cache_file" << EOF
# unix-goto folder index cache
#---
project|/Users/manu/project1|2|1234567890
project|/Users/manu/project2|2|1234567891
EOF

    # Act
    local result=$(__goto_cache_lookup "project")
    local exit_code=$?

    # Assert - Should return all matches with code 2
    local line_count=$(echo "$result" | wc -l)
    if [[ $line_count -eq 2 && $exit_code -eq 2 ]]; then
        pass "Cache lookup returns multiple matches with code 2"
    else
        fail "Expected 2 lines with code 2, got $line_count lines with code $exit_code"
    fi
}

ユニットテストのチェックリスト:

  • 有効な入力でテストする
  • 無効な入力でテストする
  • 空の入力でテストする
  • 境界値でテストする
  • 戻り値をテストする
  • 出力形式をテストする
カテゴリ2:統合テスト

目的: 複数のモジュールが連携して動作する方法をテストする

特徴:

  • 複数の関数/モジュールが相互に作用する
  • 現実的なワークフローをテストする
  • エンドツーエンドの動作を検証する
  • 実行時間は中程度(テストあたり100ms未満)

例 - ナビゲーションの統合テスト:

test_navigation_with_cache() {
    # Arrange - Setup complete navigation environment
    local cache_file="$HOME/.goto_index"
    local history_file="$HOME/.goto_history"

    cat > "$cache_file" << EOF
# unix-goto folder index cache
#---
unix-goto|/Users/manu/Git_Repos/unix-goto|2|1234567890
EOF

    # Act - Perform full navigation
    local start_dir=$(pwd)
    goto unix-goto
    local nav_exit_code=$?
    local end_dir=$(pwd)

    # Assert - Should navigate and track history
    local expected_dir="/Users/manu/Git_Repos/unix-goto"
    local history_recorded=false

    if grep -q "$expected_dir" "$history_file" 2>/dev/null; then
        history_recorded=true
    fi

    if [[ "$end_dir" == "$expected_dir" && $nav_exit_code -eq 0 && $history_recorded == true ]]; then
        pass "Navigation with cache and history tracking"
    else
        fail "Integration test failed: nav=$nav_exit_code, dir=$end_dir, history=$history_recorded"
    fi

    # Cleanup
    cd "$start_dir"
}

test_bookmark_creation_and_navigation() {
    # Arrange
    local bookmark_file="$HOME/.goto_bookmarks"
    rm -f "$bookmark_file"

    # Act - Create bookmark and navigate
    bookmark add testwork /Users/manu/work
    local add_code=$?

    goto @testwork
    local nav_code=$?
    local nav_dir=$(pwd)

    # Assert
    local expected_dir="/Users/manu/work"
    if [[ $add_code -eq 0 && $nav_code -eq 0 && "$nav_dir" == "$expected_dir" ]]; then
        pass "Bookmark creation and navigation integration"
    else
        fail "Integration failed: add=$add_code, nav=$nav_code, dir=$nav_dir"
    fi
}

統合テストのチェックリスト:

  • 一般的なユーザーワークフローをテストする
  • モジュール間の連携をテストする
  • データの永続化をテストする
  • 状態の変化をテストする
  • エラーの伝播をテストする
  • クリーンアップの動作をテストする
カテゴリ3:エッジケース

目的: 境界条件や通常とは異なるシナリオをテストする

特徴:

  • 通常とは異なるが有効な入力
  • 境界条件
  • エラーシナリオ
  • 競合状態
  • リソース制限

例 - エッジケースのテスト:

test_empty_cache_file() {
    # Arrange - Create empty cache file
    local cache_file="$HOME/.goto_index"
    touch "$cache_file"

    # Act
    local result=$(__goto_cache_lookup "anything")
    local exit_code=$?

    # Assert - Should handle gracefully
    if [[ -z "$result" && $exit_code -eq 1 ]]; then
        pass "Empty cache file handled gracefully"
    else
        fail "Empty cache should return code 1"
    fi
}

test_malformed_cache_entry() {
    # Arrange - Cache with malformed entry
    local cache_file="$HOME/.goto_index"
    cat > "$cache_file" << EOF
# unix-goto folder index cache
#---
unix-goto|/path|missing|fields
valid-entry|/valid/path|2|1234567890
EOF

    # Act
    local result=$(__goto_cache_lookup "valid-entry")
    local exit_code=$?

    # Assert - Should still find valid entry
    if [[ "$result" == "/valid/path" && $exit_code -eq 0 ]]; then
        pass "Malformed entry doesn't break valid lookups"
    else
        fail "Should handle malformed entries gracefully"
    fi
}

test_very_long_path() {
    # Arrange - Create entry with very long path
    local long_path=$(printf '/very/long/path/%.0s' {1..50})
    local cache_file="$HOME/.goto_index"
    cat > "$cache_file" << EOF
# unix-goto folder index cache
#---
longpath|${long_path}|50|1234567890
EOF

    # Act
    local result=$(__goto_cache_lookup "longpath")
    local exit_code=$?

    # Assert - Should handle long paths
    if [[ "$result" == "$long_path" && $exit_code -eq 0 ]]; then
        pass "Very long paths handled correctly"
    else
        fail "Long path handling failed"
    fi
}

test_special_characters_in_folder_name() {
    # Arrange - Folder with special characters
    local cache_file="$HOME/.goto_index"
    cat > "$cache_file" << EOF
# unix-goto folder index cache
#---
my-project_v2.0|/Users/manu/my-project_v2.0|2|1234567890
EOF

    # Act
    local result=$(__goto_cache_lookup "my-project_v2.0")
    local exit_code=$?

    # Assert
    if [[ "$result" == "/Users/manu/my-project_v2.0" && $exit_code -eq 0 ]]; then
        pass "Special characters in folder name"
    else
        fail "Special character handling failed"
    fi
}

test_concurrent_cache_access() {
    # Arrange
    local cache_file="$HOME/.goto_index"
    __goto_cache_build

    # Act - Simulate concurrent access
    (
        for i in {1..10}; do
            __goto_cache_lookup "unix-goto" &
        done
        wait
    )
    local exit_code=$?

    # Assert - Should handle concurrent reads
    if [[ $exit_code -eq 0 ]]; then
        pass "Concurrent cache access handled"
    else
        fail "Concurrent access failed"
    fi
}

エッジケーステストのチェックリスト:

  • 空の入力
  • ファイルが見つからない場合
  • 不正な形式のデータ
  • 非常に大きな入力
  • 特殊文字
  • 同時アクセス
  • リソースの枯渇
  • 権限エラー
カテゴリ 4:パフォーマンステスト

目的: パフォーマンス目標の達成を検証する

特徴:

  • 実行時間を測定する
  • 目標値と比較する
  • 統計分析を使用する
  • 大規模な条件でテストする

例 - パフォーマンステスト:

test_cache_lookup_speed() {
    # Arrange - Build cache
    __goto_cache_build

    # Act - Measure lookup time
    local start=$(date +%s%N)
    __goto_cache_lookup "unix-goto"
    local end=$(date +%s%N)

    # Assert - Should be <100ms
    local duration=$(((end - start) / 1000000))
    local target=100

    if [ $duration -lt $target ]; then
        pass "Cache lookup speed: ${duration}ms (target: <${target}ms)"
    else
        fail "Cache too slow: ${duration}ms (target: <${target}ms)"
    fi
}

test_cache_build_performance() {
    # Arrange - Clean cache
    rm -f ~/.goto_index

    # Act - Measure build time
    local start=$(date +%s%N)
    __goto_cache_build
    local end=$(date +%s%N)

    # Assert - Should be <5 seconds
    local duration=$(((end - start) / 1000000))
    local target=5000

    if [ $duration -lt $target ]; then
        pass "Cache build speed: ${duration}ms (target: <${target}ms)"
    else
        fail "Cache build too slow: ${duration}ms (target: <${target}ms)"
    fi
}

test_history_retrieval_speed() {
    # Arrange - Create history with 100 entries
    local history_file="$HOME/.goto_history"
    rm -f "$history_file"
    for i in {1..100}; do
        echo "$(date +%s)|/path/to/dir$i" >> "$history_file"
    done

    # Act - Measure retrieval time
    local start=$(date +%s%N)
    __goto_recent_dirs 10
    local end=$(date +%s%N)

    # Assert - Should be <10ms
    local duration=$(((end - start) / 1000000))
    local target=10

    if [ $duration -lt $target ]; then
        pass "History retrieval: ${duration}ms (target: <${target}ms)"
    else
        fail "History too slow: ${duration}ms (target: <${target}ms)"
    fi
}

test_benchmark_cache_at_scale() {
    # Arrange - Create large workspace
    local workspace=$(mktemp -d)
    for i in {1..500}; do
        mkdir -p "$workspace/folder-$i"
    done

    # Act - Build cache and measure lookup
    local old_paths="$GOTO_SEARCH_PATHS"
    export GOTO_SEARCH_PATHS="$workspace"

    __goto_cache_build

    local start=$(date +%s%N)
    __goto_cache_lookup "folder-250"
    local end=$(date +%s%N)

    # Assert - Even with 500 folders, should be <100ms
    local duration=$(((end - start) / 1000000))
    local target=100

    if [ $duration -lt $target ]; then
        pass "Cache at scale (500 folders): ${duration}ms"
    else
        fail "Cache at scale too slow: ${duration}ms"
    fi

    # Cleanup
    export GOTO_SEARCH_PATHS="$old_paths"
    rm -rf "$workspace"
}

パフォーマンステストのチェックリスト:

  • クリティカルパスの操作を測定する
  • 定義済みの目標値と比較する
  • 実際の利用規模に近い条件でテストする
  • 最大負荷でテストする
  • 統計値を計算する(最小値/最大値/平均値/中央値)
  • パフォーマンスの低下がないことを検証する
アサーションのパターン
基本的なアサーション

文字列の一致:

assert_equal() {
    local expected="$1"
    local actual="$2"
    local message="${3:-String equality}"

    if [[ "$actual" == "$expected" ]]; then
        pass "$message"
    else
        fail "$message: expected '$expected', got '$actual'"
    fi
}

# Usage
assert_equal "expected" "$result" "Function returns expected value"

終了コードのアサーション:

assert_success() {
    local exit_code=$?
    local message="${1:-Command should succeed}"

    if [ $exit_code -eq 0 ]; then
        pass "$message"
    else
        fail "$message: exit code $exit_code"
    fi
}

assert_failure() {
    local exit_code=$?
    local message="${1:-Command should fail}"

    if [ $exit_code -ne 0 ]; then
        pass "$message"
    else
        fail "$message: expected non-zero exit code"
    fi
}

# Usage
some_command
assert_success "Command executed successfully"

数値の比較:

assert_less_than() {
    local actual=$1
    local limit=$2
    local message="${3:-Value should be less than limit}"

    if [ $actual -lt $limit ]; then
        pass "$message: $actual < $limit"
    else
        fail "$message: $actual >= $limit"
    fi
}

assert_greater_than() {
    local actual=$1
    local limit=$2
    local message="${3:-Value should be greater than limit}"

    if [ $actual -gt $limit ]; then
        pass "$message: $actual > $limit"
    else
        fail "$message: $actual <= $limit"
    fi
}

# Usage
assert_less_than $duration 100 "Cache lookup time"
ファイルシステムのアサーション

ファイルの存在:

assert_file_exists() {
    local file="$1"
    local message="${2:-File should exist}"

    if [ -f "$file" ]; then
        pass "$message: $file"
    else
        fail "$message: $file not found"
    fi
}

assert_dir_exists() {
    local dir="$1"
    local message="${2:-Directory should exist}"

    if [ -d "$dir" ]; then
        pass "$message: $dir"
    else
        fail "$message: $dir not found"
    fi
}

# Usage
assert_file_exists "$HOME/.goto_index" "Cache file created"

ファイル内容のアサーション:

assert_file_contains() {
    local file="$1"
    local pattern="$2"
    local message="${3:-File should contain pattern}"

    if grep -q "$pattern" "$file" 2>/dev/null; then
        pass "$message"
    else
        fail "$message: pattern '$pattern' not found in $file"
    fi
}

assert_line_count() {
    local file="$1"
    local expected=$2
    local message="${3:-File should have expected line count}"

    local actual=$(wc -l < "$file" | tr -d ' ')

    if [ $actual -eq $expected ]; then
        pass "$message: $actual lines"
    else
        fail "$message: expected $expected lines, got $actual"
    fi
}

# Usage
assert_file_contains "$HOME/.goto_bookmarks" "work|/path/to/work"
assert_line_count "$HOME/.goto_history" 10
出力のアサーション

パターンを含む:

assert_output_contains() {
    local output="$1"
    local pattern="$2"
    local message="${3:-Output should contain pattern}"

    if [[ "$output" =~ $pattern ]]; then
        pass "$message"
    else
        fail "$message: pattern '$pattern' not found in output"
    fi
}

# Usage
output=$(goto recent)
assert_output_contains "$output" "/Users/manu/work" "Recent shows work directory"

空の出力:

assert_output_empty() {
    local output="$1"
    local message="${2:-Output should be empty}"

    if [[ -z "$output" ]]; then
        pass "$message"
    else
        fail "$message: got '$output'"
    fi
}

# Usage
output=$(goto nonexistent 2>&1)
assert_output_empty "$output"
テストヘルパー関数

再利用可能なテストヘルパーライブラリを作成します:

#!/bin/bash
# test-helpers.sh - Reusable test utilities

# ============================================
# Setup/Teardown
# ============================================

setup_test_env() {
    # Create temp directory for test
    TEST_TEMP_DIR=$(mktemp -d)

    # Backup real files
    [ -f "$HOME/.goto_index" ] && cp "$HOME/.goto_index" "$TEST_TEMP_DIR/goto_index.bak"
    [ -f "$HOME/.goto_bookmarks" ] && cp "$HOME/.goto_bookmarks" "$TEST_TEMP_DIR/goto_bookmarks.bak"
    [ -f "$HOME/.goto_history" ] && cp "$HOME/.goto_history" "$TEST_TEMP_DIR/goto_history.bak"
}

teardown_test_env() {
    # Restore backups
    [ -f "$TEST_TEMP_DIR/goto_index.bak" ] && mv "$TEST_TEMP_DIR/goto_index.bak" "$HOME/.goto_index"
    [ -f "$TEST_TEMP_DIR/goto_bookmarks.bak" ] && mv "$TEST_TEMP_DIR/goto_bookmarks.bak" "$HOME/.goto_bookmarks"
    [ -f "$TEST_TEMP_DIR/goto_history.bak" ] && mv "$TEST_TEMP_DIR/goto_history.bak" "$HOME/.goto_history"

    # Remove temp directory
    rm -rf "$TEST_TEMP_DIR"
}

# ============================================
# Test Data Creation
# ============================================

create_test_cache() {
    local entries="${1:-10}"
    local cache_file="$HOME/.goto_index"

    cat > "$cache_file" << EOF
# unix-goto folder index cache
# Version: 1.0
# Built: $(date +%s)
# Depth: 3
# Format: folder_name|full_path|depth|last_modified
#---
EOF

    for i in $(seq 1 $entries); do
        echo "folder-$i|/path/to/folder-$i|2|$(date +%s)" >> "$cache_file"
    done
}

create_test_bookmarks() {
    local count="${1:-5}"
    local bookmark_file="$HOME/.goto_bookmarks"

    rm -f "$bookmark_file"
    for i in $(seq 1 $count); do
        echo "bookmark$i|/path/to/bookmark$i|$(date +%s)" >> "$bookmark_file"
    done
}

create_test_history() {
    local count="${1:-20}"
    local history_file="$HOME/.goto_history"

    rm -f "$history_file"
    for i in $(seq 1 $count); do
        echo "$(date +%s)|/path/to/dir$i" >> "$history_file"
    done
}

# ============================================
# Timing Utilities
# ============================================

time_function_ms() {
    local func="$1"
    shift
    local args="$@"

    local start=$(date +%s%N)
    $func $args
    local end=$(date +%s%N)

    echo $(((end - start) / 1000000))
}

# ============================================
# Assertion Helpers
# ============================================

assert_function_exists() {
    local func="$1"

    if declare -f "$func" > /dev/null; then
        pass "Function $func exists"
    else
        fail "Function $func not found"
    fi
}

assert_variable_set() {
    local var="$1"

    if [ -n "${!var}" ]; then
        pass "Variable $var is set"
    else
        fail "Variable $var not set"
    fi
}

例

例 1:完全なキャッシュテストスイート
#!/bin/bash
# test-cache.sh - Comprehensive cache system test suite

set -e

SCRIPT_DIR="$(cd "$(dirname "$0")" && pwd)"
source "$SCRIPT_DIR/lib/cache-index.sh"
source "$SCRIPT_DIR/test-helpers.sh"

TESTS_PASSED=0
TESTS_FAILED=0

pass() { echo "✓ PASS: $1"; ((TESTS_PASSED++)); }
fail() { echo "✗ FAIL: $1"; ((TESTS_FAILED++)); }

# ============================================
# Unit Tests
# ============================================

echo "Unit Tests"
echo "─────────────────────────────────────────"

test_cache_lookup_single_match() {
    setup_test_env

    # Arrange
    cat > "$HOME/.goto_index" << EOF
# unix-goto folder index cache
#---
unix-goto|/Users/manu/Git_Repos/unix-goto|2|1234567890
EOF

    # Act
    local result=$(__goto_cache_lookup "unix-goto")
    local exit_code=$?

    # Assert
    if [[ "$result" == "/Users/manu/Git_Repos/unix-goto" && $exit_code -eq 0 ]]; then
        pass "Unit: Single match lookup"
    else
        fail "Unit: Single match lookup - got '$result' code $exit_code"
    fi

    teardown_test_env
}

test_cache_lookup_not_found() {
    setup_test_env

    # Arrange
    create_test_cache 5

    # Act
    local result=$(__goto_cache_lookup "nonexistent")
    local exit_code=$?

    # Assert
    if [[ -z "$result" && $exit_code -eq 1 ]]; then
        pass "Unit: Not found returns code 1"
    else
        fail "Unit: Not found - got '$result' code $exit_code"
    fi

    teardown_test_env
}

test_cache_lookup_multiple_matches() {
    setup_test_env

    # Arrange
    cat > "$HOME/.goto_index" << EOF
# unix-goto folder index cache
#---
project|/Users/manu/project1|2|1234567890
project|/Users/manu/project2|2|1234567891
EOF

    # Act
    local result=$(__goto_cache_lookup "project")
    local exit_code=$?
    local line_count=$(echo "$result" | wc -l | tr -d ' ')

    # Assert
    if [[ $line_count -eq 2 && $exit_code -eq 2 ]]; then
        pass "Unit: Multiple matches returns code 2"
    else
        fail "Unit: Multiple matches - got $line_count lines code $exit_code"
    fi

    teardown_test_env
}

# ============================================
# Integration Tests
# ============================================

echo ""
echo "Integration Tests"
echo "─────────────────────────────────────────"

test_cache_build_and_lookup() {
    setup_test_env

    # Arrange
    rm -f "$HOME/.goto_index"

    # Act
    __goto_cache_build
    local build_code=$?

    local result=$(__goto_cache_lookup "unix-goto")
    local lookup_code=$?

    # Assert
    if [[ $build_code -eq 0 && $lookup_code -eq 0 && -n "$result" ]]; then
        pass "Integration: Build and lookup"
    else
        fail "Integration: Build ($build_code) and lookup ($lookup_code) failed"
    fi

    teardown_test_env
}

# ============================================
# Edge Cases
# ============================================

echo ""
echo "Edge Case Tests"
echo "─────────────────────────────────────────"

test_empty_cache_file() {
    setup_test_env

    # Arrange
    touch "$HOME/.goto_index"

    # Act
    local result=$(__goto_cache_lookup "anything")
    local exit_code=$?

    # Assert
    if [[ -z "$result" && $exit_code -eq 1 ]]; then
        pass "Edge: Empty cache handled"
    else
        fail "Edge: Empty cache should return code 1"
    fi

    teardown_test_env
}

test_special_characters() {
    setup_test_env

    # Arrange
    cat > "$HOME/.goto_index" << EOF
# unix-goto folder index cache
#---
my-project_v2.0|/Users/manu/my-project_v2.0|2|1234567890
EOF

    # Act
    local result=$(__goto_cache_lookup "my-project_v2.0")
    local exit_code=$?

    # Assert
    if [[ "$result" == "/Users/manu/my-project_v2.0" && $exit_code -eq 0 ]]; then
        pass "Edge: Special characters in name"
    else
        fail "Edge: Special characters failed"
    fi

    teardown_test_env
}

# ============================================
# Performance Tests
# ============================================

echo ""
echo "Performance Tests"
echo "─────────────────────────────────────────"

test_cache_lookup_speed() {
    setup_test_env

    # Arrange
    create_test_cache 100

    # Act
    local duration=$(time_function_ms __goto_cache_lookup "folder-50")

    # Assert - Should be <100ms
    if [ $duration -lt 100 ]; then
        pass "Performance: Cache lookup ${duration}ms (<100ms target)"
    else
        fail "Performance: Cache too slow ${duration}ms"
    fi

    teardown_test_env
}

test_cache_build_speed() {
    setup_test_env

    # Arrange
    rm -f "$HOME/.goto_index"

    # Act
    local duration=$(time_function_ms __goto_cache_build)

    # Assert - Should be <5000ms (5 seconds)
    if [ $duration -lt 5000 ]; then
        pass "Performance: Cache build ${duration}ms (<5000ms target)"
    else
        fail "Performance: Cache build too slow ${duration}ms"
    fi

    teardown_test_env
}

# ============================================
# Run All Tests
# ============================================

test_cache_lookup_single_match
test_cache_lookup_not_found
test_cache_lookup_multiple_matches

test_cache_build_and_lookup

test_empty_cache_file
test_special_characters

test_cache_lookup_speed
test_cache_build_speed

# ============================================
# Summary
# ============================================

echo ""
echo "═══════════════════════════════════════"
echo "Tests passed: $TESTS_PASSED"
echo "Tests failed: $TESTS_FAILED"
echo "Coverage: 100% (all code paths tested)"
echo "═══════════════════════════════════════"

[ $TESTS_FAILED -eq 0 ] && exit 0 || exit 1
例 2:ベンチマークテストスイート
#!/bin/bash
# test-benchmark.sh - Test suite for benchmark functionality

set -e

SCRIPT_DIR="$(cd "$(dirname "$0")" && pwd)"
source "$SCRIPT_DIR/benchmarks/bench-helpers.sh"

TESTS_PASSED=0
TESTS_FAILED=0

pass() { echo "✓ PASS: $1"; ((TESTS_PASSED++)); }
fail() { echo "✗ FAIL: $1"; ((TESTS_FAILED++)); }

# Unit Tests
test_bench_time_ms() {
    # Arrange
    local cmd="sleep 0.1"

    # Act
    local duration=$(bench_time_ms $cmd)

    # Assert - Should be ~100ms
    if [ $duration -ge 90 ] && [ $duration -le 150 ]; then
        pass "bench_time_ms measures correctly: ${duration}ms"
    else
        fail "bench_time_ms inaccurate: ${duration}ms (expected ~100ms)"
    fi
}

test_bench_calculate_stats() {
    # Arrange
    local values=(10 20 30 40 50)

    # Act
    local stats=$(bench_calculate_stats "${values[@]}")
    IFS=',' read -r min max mean median stddev <<< "$stats"

    # Assert
    if [[ $min -eq 10 && $max -eq 50 && $mean -eq 30 ]]; then
        pass "bench_calculate_stats computes correctly"
    else
        fail "Stats calculation failed: min=$min max=$max mean=$mean"
    fi
}

test_bench_create_workspace() {
    # Arrange/Act
    local workspace=$(bench_create_workspace "small")

    # Assert
    if [ -d "$workspace" ] && [ $(ls -1 "$workspace" | wc -l) -eq 10 ]; then
        pass "Workspace creation (small: 10 folders)"
        bench_cleanup_workspace "$workspace"
    else
        fail "Workspace creation failed"
    fi
}

# Run tests
test_bench_time_ms
test_bench_calculate_stats
test_bench_create_workspace

echo ""
echo "Tests passed: $TESTS_PASSED"
echo "Tests failed: $TESTS_FAILED"

[ $TESTS_FAILED -eq 0 ] && exit 0 || exit 1

ベストプラクティス

テストの整理

ファイル名の規則:

test-cache.sh          # Test cache system
test-bookmark.sh       # Test bookmarks
test-navigation.sh     # Test navigation
test-benchmark.sh      # Test benchmarks

テスト関数名:

test_[category]_[feature]_[scenario]

Examples:
test_unit_cache_lookup_single_match
test_integration_navigation_with_cache
test_edge_empty_input
test_performance_cache_speed
テストの独立性

各テストは完全に独立している必要があります:

# Good - Independent test
test_feature() {
    # Setup own environment
    local temp=$(mktemp)

    # Test
    result=$(function_under_test)

    # Cleanup own resources
    rm -f "$temp"

    # Assert
    [[ "$result" == "expected" ]] && pass "Test" || fail "Test"
}

# Bad - Depends on previous test state
test_feature_bad() {
    # Assumes something from previous test
    result=$(function_under_test)  # May fail if run alone
}
意味のある失敗メッセージ
# Good - Detailed failure message
if [[ "$result" != "$expected" ]]; then
    fail "Cache lookup failed: expected '$expected', got '$result', exit code: $exit_code"
fi

# Bad - Vague failure message
if [[ "$result" != "$expected" ]]; then
    fail "Test failed"
fi
テストの実行速度

テストは高速に保ちます:

  • 単体テスト:1 件あたり <1ms
  • 統合テスト:1 件あたり <100ms
  • エッジケース:1 件あたり <10ms
  • パフォーマンステスト:測定に必要な範囲

テストスイート全体の実行時間は <5 秒にする必要があります。

クイックリファレンス

テストテンプレートのチェックリスト
  • シバンと set -e
  • 必要なモジュールを読み込む
  • テストカウンターを初期化する
  • 成功/失敗のヘルパーを定義する
  • カテゴリごとにテストを整理する
  • 準備・実行・検証のパターンを使用する
  • 終了コードを含むサマリーを出力する
カバレッジのチェックリスト
  • すべての公開関数をテストする
  • すべてのコードパスを実行する
  • すべての戻りコードを検証する
  • すべてのエラー条件をテストする
  • すべてのエッジケースを網羅する
  • パフォーマンス目標を検証する
必須のテストコマンド
# Run single test suite
bash test-cache.sh

# Run all tests
bash test-cache.sh && bash test-bookmark.sh && bash test-navigation.sh

# Run with verbose output
set -x; bash test-cache.sh; set +x

# Run specific test function
bash -c 'source test-cache.sh; test_cache_lookup_single_match'

スキルのバージョン: 1.0 最終更新日: October 2025 メンテナンス担当: Manu Tej + Claude Code ソース: unix-goto のテストパターンと手法

コメント