Shell Testing Framework

manutej/luxor-claude-marketplace/plugins/luxor-testing-essentials/skills/shell-testing-framework

作者 manutej1861cc9720d5无许可证62 个星标收录于 2026年10月8日更新于 2026年10月8日仓库3个月前更新

Shell script testing expertise using bash test framework patterns from unix-goto, covering test structure (arrange-act-assert), 4 test categories, assertion patterns, 100% coverage requirements, and performance testing

AI 生成的概览

指导编写 bash shell 脚本测试套件,涵盖 arrange-act-assert 结构、四类测试与性能检查。

功能
该技能提供针对 bash shell 脚本的测试经验,模式来自 unix-goto 项目。它描述了标准测试文件结构、arrange-act-assert 模式、四类测试(单元、集成、边界情况、性能)、断言辅助函数以及测试辅助的初始化与清理流程。内容还涉及 100% 覆盖率规则、测试驱动开发流程、测试独立性和测试报告汇总。
适用场景
适用于为 bash shell 脚本编写或组织测试套件、落实覆盖率要求、创建断言模式,或为 shell 函数添加性能测试。它面向开发与 CI 场景,不用于测试非 shell 应用或一般 QA。
运行要求
仅为说明文档,不附带脚本。它假定存在 bash 环境及被测 shell 函数,示例中引用了 ~/.goto_index、~/.goto_bookmarks 和 ~/.goto_history 等文件。

Shell Testing Framework Expert

Comprehensive testing expertise for bash shell scripts using patterns and methodologies from the unix-goto project, emphasizing 100% test coverage, systematic test organization, and performance validation.

When to Use This Skill

Use this skill when:

  • Writing test suites for bash shell scripts
  • Implementing 100% test coverage requirements
  • Organizing tests into unit, integration, edge case, and performance categories
  • Creating assertion patterns for shell script validation
  • Setting up test infrastructure and helpers
  • Writing performance tests for shell functions
  • Generating test reports and summaries
  • Debugging test failures
  • Validating shell script behavior

Do NOT use this skill for:

  • Testing non-shell applications (use language-specific frameworks)
  • Simple ad-hoc script validation
  • Production testing (use for development/CI only)
  • General QA testing (this is developer-focused unit testing)

Core Testing Philosophy

The 100% Coverage Rule

Every core feature in unix-goto has 100% test coverage. This is NON-NEGOTIABLE.

Coverage Requirements:

  • Core navigation: 100%
  • Cache system: 100%
  • Bookmarks: 100%
  • History: 100%
  • Benchmarks: 100%
  • New features: 100%

What This Means:

  • Every function has tests
  • Every code path is exercised
  • Every error condition is validated
  • Every edge case is covered
  • Every performance target is verified

Test-Driven Development Approach

Workflow:

  1. Write tests FIRST (based on feature spec)
  2. Watch tests FAIL (red)
  3. Implement feature
  4. Watch tests PASS (green)
  5. Refactor if needed
  6. Validate all tests still pass

Core Knowledge

Standard Test File Structure

Every test file follows this exact structure:

bash
#!/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=0TESTS_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 teststest_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

The Arrange-Act-Assert Pattern

EVERY test function MUST follow this three-phase structure:

1. Arrange - Set up test conditions

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

2. Act - Execute the code under test

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

3. Assert - Verify the results

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

Complete Example:

bash
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|1234567890EOF
    # 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}

The Four Test Categories

EVERY feature requires tests in ALL four categories:

Category 1: Unit Tests

Purpose: Test individual functions in isolation

Characteristics:

  • Single function under test
  • Minimal dependencies
  • Fast execution (<1ms per test)
  • Clear, focused assertions

Example - Cache Lookup Unit Test:

bash
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|1234567890EOF
    # 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|1234567890project|/Users/manu/project2|2|1234567891EOF
    # 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}

Unit Test Checklist:

  • Test with valid input
  • Test with invalid input
  • Test with empty input
  • Test with boundary values
  • Test return codes
  • Test output format
Category 2: Integration Tests

Purpose: Test how multiple modules work together

Characteristics:

  • Multiple functions/modules interact
  • Test realistic workflows
  • Validate end-to-end behavior
  • Moderate execution time (<100ms per test)

Example - Navigation Integration Test:

bash
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|1234567890EOF
    # 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}

Integration Test Checklist:

  • Test common user workflows
  • Test module interactions
  • Test data persistence
  • Test state changes
  • Test error propagation
  • Test cleanup behavior
Category 3: Edge Cases

Purpose: Test boundary conditions and unusual scenarios

Characteristics:

  • Unusual but valid inputs
  • Boundary conditions
  • Error scenarios
  • Race conditions
  • Resource limits

Example - Edge Case Tests:

bash
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|fieldsvalid-entry|/valid/path|2|1234567890EOF
    # 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|1234567890EOF
    # 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|1234567890EOF
    # 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}

Edge Case Test Checklist:

  • Empty inputs
  • Missing files
  • Malformed data
  • Very large inputs
  • Special characters
  • Concurrent access
  • Resource exhaustion
  • Permission errors
Category 4: Performance Tests

Purpose: Validate performance targets are met

Characteristics:

  • Measure execution time
  • Compare against targets
  • Use statistical analysis
  • Test at scale

Example - Performance Tests:

bash
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"}

Performance Test Checklist:

  • Measure critical path operations
  • Compare against defined targets
  • Test at realistic scale
  • Test with maximum load
  • Calculate statistics (min/max/mean/median)
  • Verify no performance regressions

Assertion Patterns

Basic Assertions

String Equality:

bash
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}
# Usageassert_equal "expected" "$result" "Function returns expected value"

Exit Code Assertions:

bash
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}
# Usagesome_commandassert_success "Command executed successfully"

Numeric Comparisons:

bash
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}
# Usageassert_less_than $duration 100 "Cache lookup time"
File System Assertions

File Existence:

bash
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}
# Usageassert_file_exists "$HOME/.goto_index" "Cache file created"

File Content Assertions:

bash
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}
# Usageassert_file_contains "$HOME/.goto_bookmarks" "work|/path/to/work"assert_line_count "$HOME/.goto_history" 10
Output Assertions

Contains Pattern:

bash
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}
# Usageoutput=$(goto recent)assert_output_contains "$output" "/Users/manu/work" "Recent shows work directory"

Empty Output:

bash
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}
# Usageoutput=$(goto nonexistent 2>&1)assert_output_empty "$output"

Test Helper Functions

Create a reusable test helpers library:

bash
#!/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}

Examples

Example 1: Complete Cache Test Suite

bash
#!/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=0TESTS_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|1234567890EOF
    # 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|1234567890project|/Users/manu/project2|2|1234567891EOF
    # 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|1234567890EOF
    # 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_matchtest_cache_lookup_not_foundtest_cache_lookup_multiple_matches
test_cache_build_and_lookup
test_empty_cache_filetest_special_characters
test_cache_lookup_speedtest_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

Example 2: Benchmark Test Suite

bash
#!/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=0TESTS_FAILED=0
pass() { echo "✓ PASS: $1"; ((TESTS_PASSED++)); }fail() { echo "✗ FAIL: $1"; ((TESTS_FAILED++)); }
# Unit Teststest_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 teststest_bench_time_mstest_bench_calculate_statstest_bench_create_workspace
echo ""echo "Tests passed: $TESTS_PASSED"echo "Tests failed: $TESTS_FAILED"
[ $TESTS_FAILED -eq 0 ] && exit 0 || exit 1

Best Practices

Test Organization

File Naming Convention:

test-cache.sh          # Test cache systemtest-bookmark.sh       # Test bookmarkstest-navigation.sh     # Test navigationtest-benchmark.sh      # Test benchmarks

Test Function Naming:

test_[category]_[feature]_[scenario]
Examples:test_unit_cache_lookup_single_matchtest_integration_navigation_with_cachetest_edge_empty_inputtest_performance_cache_speed

Test Independence

Each test must be completely independent:

bash
# Good - Independent testtest_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 statetest_feature_bad() {    # Assumes something from previous test    result=$(function_under_test)  # May fail if run alone}

Meaningful Failure Messages

bash
# Good - Detailed failure messageif [[ "$result" != "$expected" ]]; then    fail "Cache lookup failed: expected '$expected', got '$result', exit code: $exit_code"fi
# Bad - Vague failure messageif [[ "$result" != "$expected" ]]; then    fail "Test failed"fi

Test Execution Speed

Keep tests FAST:

  • Unit tests: <1ms each
  • Integration tests: <100ms each
  • Edge cases: <10ms each
  • Performance tests: As needed for measurement

Total test suite should run in <5 seconds.

Quick Reference

Test Template Checklist

  • Shebang and set -e
  • Source required modules
  • Initialize test counters
  • Define pass/fail helpers
  • Organize tests by category
  • Use arrange-act-assert pattern
  • Print summary with exit code

Coverage Checklist

  • All public functions tested
  • All code paths exercised
  • All return codes validated
  • All error conditions tested
  • All edge cases covered
  • Performance targets verified

Essential Test Commands

bash
# Run single test suitebash test-cache.sh
# Run all testsbash test-cache.sh && bash test-bookmark.sh && bash test-navigation.sh
# Run with verbose outputset -x; bash test-cache.sh; set +x
# Run specific test functionbash -c 'source test-cache.sh; test_cache_lookup_single_match'

Skill Version: 1.0 Last Updated: October 2025 Maintained By: Manu Tej + Claude Code Source: unix-goto testing patterns and methodologies

来源与署名

来源:manutej/luxor-claude-marketplace位于plugins/luxor-testing-essentials/skills/shell-testing-framework提交1861cc9

许可证: 无许可证

内容归原作者所有。SourceWeft 从公开仓库中收录这些内容。

举报或申请下架