@sebastienrousseau/dotfiles
Version:
The Trusted Shell Platform — Universal dotfiles managed by Chezmoi. Features Bash & Zsh for macOS, Linux & WSL. Rust modern tooling & enterprise-grade security.
217 lines (159 loc) • 6.72 kB
Markdown
render_with_liquid: false
# Testing strategy
## Overview
The repo uses a multi-layer testing approach: unit tests for individual functions, integration tests for system-wide behavior, and performance benchmarks for resource efficiency.
## Quick start
```bash
# Run all unit tests
./tests/framework/test_runner.sh
# Run a specific test suite
./tests/framework/test_runner.sh extract
# Run integration tests
RUN_INTEGRATION=1 ./tests/framework/test_runner.sh
# Run performance benchmarks
./tests/performance/benchmark_runner.sh
# Run unit tests in parallel (parsed per-file output, deterministic order)
./tests/framework/test_runner.sh --jobs auto
# Strict mode: promote silent `command not found` / `unbound variable`
# inside cov_exercise_functions_file to test failures. Default is
# tolerant (the helper sources dot command files in a clean shell where
# their lib/dot/ helpers are intentionally unresolved). Use STRICT=1
# locally before pushing to catch the class of bug that escaped
# review pre-v0.2.503 (e.g. agent.sh: _agent_repo_root falling
# through to a missing require_source_dir).
DOT_STRICT=1 ./tests/framework/test_runner.sh
```
The runner's FINAL SUMMARY lists which test files failed (not just
the total count) so locating a regression in a 4000+ assertion run
doesn't require grepping back through the full log. Per-file failure
counts and crashed-file markers appear inline.
## Test structure
```text
tests/
├── framework/ # Test framework
│ ├── assertions.sh # 16 assertion functions
│ ├── mocks.sh # Mock utilities
│ └── test_runner.sh # Test executor
├── unit/ # 425 unit test files
├── integration/ # 11 integration test files
└── performance/ # Benchmarks
```
## Writing tests
### Test file template
```bash
#!/usr/bin/env bash
SCRIPT_DIR="$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)"
source "$SCRIPT_DIR/../framework/assertions.sh"
# Source the function under test
source "$HOME/.dotfiles/.chezmoitemplates/functions/myfunction.sh"
# Test cases
test_start "function_basic_usage"
assert_exit_code 0 "myfunction arg1"
test_start "function_no_args"
assert_exit_code 1 "myfunction"
```
### Available assertions
| Function | Description |
|----------|-------------|
| `assert_equals expected actual [msg]` | Two values are equal |
| `assert_not_equals unexpected actual [msg]` | Two values differ |
| `assert_exit_code code command` | Command exits with given code |
| `assert_output_contains needle command` | Output contains string |
| `assert_output_not_contains needle command` | Output lacks string |
| `assert_output_matches pattern command` | Output matches regex |
| `assert_file_exists path [msg]` | File exists |
| `assert_file_not_exists path [msg]` | File doesn't exist |
| `assert_dir_exists path [msg]` | Directory exists |
| `assert_dir_not_exists path [msg]` | Directory doesn't exist |
| `assert_true condition [msg]` | Condition is true |
| `assert_false condition [msg]` | Condition is false |
| `assert_empty value [msg]` | String is empty |
| `assert_not_empty value [msg]` | String isn't empty |
| `assert_file_contains file needle [msg]` | File contains text |
| `assert_file_not_contains file needle [msg]` | File lacks text |
### Mock utilities
| Function | Description |
|----------|-------------|
| `mock_init` | Initialize mock environment |
| `mock_command name output [exit_code]` | Create a mock command |
| `mock_command_spy name [output] [exit_code]` | Create a mock that records calls |
| `mock_get_calls name` | Get spy call history |
| `mock_call_count name` | Get number of spy calls |
| `mock_file content [filename]` | Create temp file with content |
| `mock_dir [prefix]` | Create temp directory |
| `mock_archive type [content]` | Create a mock archive file |
| `mock_env var_name value` | Set an environment variable |
| `mock_cleanup` | Clean up all mocks |
## Test categories
### Unit tests (`tests/unit/`)
Test individual functions in isolation. Each file follows the `test_*.sh` naming convention and is discoverable with `ls tests/unit/`.
### Integration tests (`tests/integration/`)
Test complete workflows like the installation script and end-to-end apply behavior.
### Performance tests (`tests/performance/`)
Measure resource efficiency with shell startup benchmarks and load tests.
## Coverage goals
| Category | Target | Current |
|----------|--------|---------|
| Module coverage | >=95% | 100% |
| Unit test files | - | 425 |
| Integration test files | - | 11 |
| Total test files | - | 436 |
| Named tests (`test_start`) | - | 2149 |
| Unit test pass rate | 100% | 100% |
CI enforces module coverage via:
```bash
MIN_COVERAGE=95 ./tests/framework/module_coverage.sh
```
For a current local baseline, run:
```bash
bash ./scripts/qa/coverage-baseline.sh --with-module-coverage
```
For core internal behavior traceability, run:
```bash
bash ./scripts/qa/traceability-coverage.sh
```
## CI integration
Tests run automatically on every push to main, every pull request, and weekly scheduled runs (Monday 6 AM UTC).
### GitHub Actions example
```yaml
- name: Run Tests
run: |
chmod +x ./tests/framework/test_runner.sh
./tests/framework/test_runner.sh
- name: Run Integration Tests
run: |
RUN_INTEGRATION=1 ./tests/framework/test_runner.sh
- name: Run Performance Benchmarks
run: |
./tests/performance/benchmark_runner.sh
```
## Environment variables
| Variable | Default | Description |
|----------|---------|-------------|
| `RUN_INTEGRATION` | `0` | Set to `1` to include integration tests |
| `VERBOSE` | `0` | Set to `1` for verbose output |
| `REPO_ROOT` | Auto-detected | Repository root directory |
| `TESTS_DIR` | Auto-detected | Tests directory |
## Best practices
1. **Isolation** -- Each test should be independent and self-contained.
2. **Cleanup** -- Use mocks that auto-cleanup via traps.
3. **No sleep** -- Avoid `sleep` or hardcoded delays.
4. **Descriptive names** -- Test names should describe what's being verified.
5. **Edge cases** -- Test error conditions, not just happy paths.
6. **Security** -- Include tests for dangerous input rejection.
## Troubleshooting
### Tests not found
Make sure test files match the `test_*.sh` pattern and are executable:
```bash
chmod +x tests/unit/test_*.sh
chmod +x tests/integration/test_*.sh
```
### Function not available
If a test reports "function not available", verify the source file exists:
```bash
ls -la .chezmoitemplates/functions/
```
### Mock cleanup issues
If mocks aren't cleaning up, make sure you aren't running with `set -e` before mock operations that might intentionally fail.