Skip to main content
Proper testing ensures your scripts work correctly and catch errors before they cause problems. Learn the testing approaches used in TheTeleporter Scripts.

Testing Workflow

1

Syntax validation

Check for syntax errors without executing the script:
bash -n script.sh
This performs a dry-run that only parses the script without executing it.
2

Static analysis

Use ShellCheck to find common issues and anti-patterns:
shellcheck script.sh
ShellCheck catches issues like:
  • Unquoted variables
  • Useless cats and pipes
  • Missing error handling
  • Deprecated syntax
3

Debug mode testing

Run the script in debug mode to see what it would do:
./script.sh --debug
Debug mode should:
  • Show what would be executed
  • Print debug messages
  • Not make actual changes
4

Manual testing

Test the actual functionality:
# Test with valid inputs
./script.sh --source /path/to/source

# Test error handling
./script.sh --source /nonexistent

# Test help flag
./script.sh --help

Syntax Validation

The bash -n command checks syntax without execution:
bash -n my-script.sh

What it catches:

  • Missing quotes or brackets
  • Unclosed strings or heredocs
  • Invalid command syntax
  • Incorrect function definitions

Example:

# Bad syntax - missing closing quote
echo "Hello world

# Check syntax
$ bash -n bad-script.sh
bad-script.sh: line 2: unexpected EOF while looking for matching `"'
bad-script.sh: line 3: syntax error: unexpected end of file
Run bash -n after any changes to catch syntax errors immediately.

Static Analysis with ShellCheck

ShellCheck is a static analysis tool that finds bugs and style issues:

Installation

# Ubuntu/Debian
sudo apt install shellcheck

# macOS
brew install shellcheck

# Arch
sudo pacman -S shellcheck

Basic usage:

shellcheck script.sh

Example output:

$ shellcheck backup.sh

In backup.sh line 15:
rm -rf $TEMP_DIR
       ^-------^ SC2086: Double quote to prevent globbing and word splitting.

In backup.sh line 23:
if [ $STATUS = "success" ]; then
     ^-----^ SC2086: Double quote to prevent globbing and word splitting.
   ^ SC2039: In POSIX sh, == in place of = is undefined.

Common issues ShellCheck finds:

Unquoted variables:
# Bad
rm -rf $DIR

# Good
rm -rf "$DIR"
Useless use of cat:
# Bad
cat file.txt | grep "pattern"

# Good
grep "pattern" file.txt
Missing error handling:
# Bad
cd /some/directory
rm -rf *

# Good
cd /some/directory || exit 1
rm -rf *

Ignoring specific warnings:

If you need to ignore a warning:
# shellcheck disable=SC2086
for file in $FILES; do
  echo "$file"
done
Only disable warnings when you understand why they’re triggered and know it’s safe to ignore them.

Debug Mode Testing

All scripts should support debug mode via the --debug flag:

Implementation:

DEBUG=0

while [[ $# -gt 0 ]]; do
  case $1 in
    -d|--debug)
      DEBUG=1
      shift
      ;;
  esac
done

main() {
  if [[ $DEBUG -eq 1 ]]; then
    debug "Would delete: $FILE"
    warning "This is a dry-run, no changes will be made"
  else
    rm "$FILE"
    success "Deleted: $FILE"
  fi
}

Testing with debug mode:

# See what would happen without making changes
./script.sh --debug

# Example output:
# [DEBUG] Would delete: /tmp/cache
# ⚠ This is a dry-run, no changes will be made

Debug function:

The template includes a debug function that only prints in debug mode:
debug() {
  if [[ $DEBUG -eq 1 ]]; then
    echo -e "${DIM}[DEBUG]${NC} $*"
  fi
}

# Usage
debug "Variable value: $VAR"
debug "Would execute: rm -rf $DIR"

Manual Testing

Test Cases

Create a checklist of scenarios to test:
  1. Happy path - Script works with valid inputs
  2. Missing arguments - Script handles missing required arguments
  3. Invalid arguments - Script rejects invalid options
  4. File not found - Script handles missing files gracefully
  5. Permission denied - Script handles permission errors
  6. Edge cases - Empty inputs, large files, special characters

Example test script:

#!/bin/bash
# test-backup.sh - Test backup script

SCRIPT="./backup.sh"

echo "Testing backup script..."

# Test 1: Help flag
echo "Test 1: Help flag"
$SCRIPT --help

# Test 2: Missing arguments
echo "Test 2: Missing arguments"
$SCRIPT && echo "FAIL: Should require arguments" || echo "PASS: Rejected missing args"

# Test 3: Valid backup
echo "Test 3: Valid backup"
TEMP_DIR=$(mktemp -d)
echo "test" > "$TEMP_DIR/test.txt"
$SCRIPT --source "$TEMP_DIR" --dest /tmp
if [[ -f /tmp/backup_*.tar.gz ]]; then
  echo "PASS: Backup created"
else
  echo "FAIL: Backup not created"
fi
rm -rf "$TEMP_DIR"

# Test 4: Debug mode
echo "Test 4: Debug mode"
$SCRIPT --source /tmp --debug

echo "Tests complete"

Interactive Testing

For interactive scripts (like gdrive_ingest.sh), test:

UI elements:

  • Arrow key navigation works
  • Enter/selection works
  • Quit/cancel works
  • Cursor visibility (hidden during UI, shown after)

Progress indicators:

  • Progress bars update correctly
  • Percentages are accurate
  • Cleanup happens on interruption

Error recovery:

  • Ctrl+C handling
  • Cleanup on exit
  • Cursor restoration

Testing Traps and Cleanup

Test that cleanup happens correctly:
#!/bin/bash

TEMP_DIR=$(mktemp -d)

cleanup() {
  echo "Cleaning up..."
  rm -rf "$TEMP_DIR"
}
trap cleanup EXIT INT TERM

# Do work
echo "Working in $TEMP_DIR"
sleep 5

# Try interrupting with Ctrl+C - cleanup should still run
Test scenarios:
  1. Normal exit - cleanup runs
  2. Ctrl+C (SIGINT) - cleanup runs
  3. Script error - cleanup runs
  4. Kill signal (SIGTERM) - cleanup runs

Testing Error Handling

Verify error handling works correctly:

Test with invalid inputs:

# Missing required file
./script.sh --file /nonexistent
# Expected: Error message + exit 1

# Invalid option
./script.sh --invalid-flag
# Expected: Error message + exit 1

# Missing dependency
# (temporarily rename a required command)
mv /usr/bin/jq /usr/bin/jq.bak
./script.sh
mv /usr/bin/jq.bak /usr/bin/jq
# Expected: Error message about missing jq + exit 1

Test error propagation:

# Script should exit on command failure
set -eo pipefail

# This should exit the script
false
echo "This should not print"

Testing Output

Verify color codes:

./script.sh | cat -A
# Should show ANSI escape codes like ^[[0;32m

Test stderr vs stdout:

# Errors should go to stderr
./script.sh --invalid 2>&1 | grep "Unknown option"

# Success messages should go to stdout
./script.sh > output.txt
cat output.txt

Test with no color support:

# Some environments don't support colors
TERM=dumb ./script.sh

Continuous Testing

Integrate testing into your workflow:

Pre-commit hook:

Create .git/hooks/pre-commit:
#!/bin/bash
# Pre-commit hook to validate scripts

echo "Running tests..."

for script in $(git diff --cached --name-only | grep '\.sh$'); do
  echo "Testing $script..."
  
  # Syntax check
  bash -n "$script" || exit 1
  
  # ShellCheck
  if command -v shellcheck &>/dev/null; then
    shellcheck "$script" || exit 1
  fi
done

echo "All tests passed"
Make it executable:
chmod +x .git/hooks/pre-commit

Testing Checklist

Before considering a script complete:
  • Syntax validated with bash -n
  • Static analysis with shellcheck
  • Help flag displays correctly
  • Debug mode works without making changes
  • Error messages are clear and actionable
  • Missing arguments handled gracefully
  • Invalid arguments rejected with error
  • File operations handle missing files
  • Cleanup runs on exit/interruption
  • Colors display correctly
  • No hardcoded paths (use variables)
  • Dependencies validated on startup

Common Issues and Solutions

Issue: Script works in terminal but fails in cron

Solution: Source the full environment or use absolute paths
# Bad - relies on PATH
jq .data file.json

# Good - absolute path
/usr/bin/jq .data file.json

Issue: Variables with spaces break commands

Solution: Always quote variables
# Bad
rm -rf $DIR

# Good
rm -rf "$DIR"

Issue: Pipeline failures not caught

Solution: Use set -o pipefail
set -o pipefail

# This will now exit on curl failure
curl http://example.com | jq .data

Tools and Resources

Essential tools:

  • bash -n - Syntax validation (built-in)
  • ShellCheck - Static analysis (shellcheck.net)
  • bashdb - Bash debugger for complex scripts
  • shellspec - Testing framework for bash

Online resources:

Next Steps

Build docs developers (and LLMs) love