325 lines
7.6 KiB
Markdown
325 lines
7.6 KiB
Markdown
# Testing Patterns
|
|
|
|
**Analysis Date:** 2026-02-28
|
|
|
|
## Test Framework
|
|
|
|
**Test Runner:**
|
|
- Rust built-in test framework (via `cargo test`)
|
|
- No external test framework currently integrated
|
|
- Can add `tokio` for async testing if needed later
|
|
|
|
**Assertion Library:**
|
|
- Standard `assert!()` and `assert_eq!()` macros from standard library
|
|
- Can add `pretty_assertions` crate for better failure output if desired
|
|
|
|
**Run Commands:**
|
|
```bash
|
|
cargo test # Run all tests
|
|
cargo test -- --nocapture # Run tests with output visible
|
|
cargo test -- --test-threads=1 # Run tests sequentially
|
|
cargo test --lib # Run only library tests
|
|
cargo test --doc # Run doctests
|
|
```
|
|
|
|
**Watch Mode:**
|
|
```bash
|
|
# Using cargo-watch (install with: cargo install cargo-watch)
|
|
cargo watch -x test # Re-run tests on file changes
|
|
```
|
|
|
|
**Coverage:**
|
|
```bash
|
|
# Using cargo-tarpaulin (install with: cargo install cargo-tarpaulin)
|
|
cargo tarpaulin --out Html # Generate HTML coverage report
|
|
```
|
|
|
|
## Test File Organization
|
|
|
|
**Location - Unit Tests:**
|
|
- Co-located with implementation: Tests in same file as source code
|
|
- Tests go in submodule at end of source file
|
|
|
|
**Location - Integration Tests:**
|
|
- Separate `tests/` directory at workspace root
|
|
- File: `tests/integration_test.rs` or similar
|
|
- Each file is compiled as separate crate
|
|
|
|
**Naming:**
|
|
- Unit test modules: `#[cfg(test)] mod tests`
|
|
- Integration test files: `integration_test.rs`, `e2e_test.rs`
|
|
- Test functions: `test_function_name_description()`
|
|
- Unit tests: `tests::test_parses_valid_input()`
|
|
|
|
**Current Structure:**
|
|
```
|
|
src/
|
|
├── main.rs # Contains #[cfg(test)] mod tests
|
|
├── lib.rs # If created, contains library tests
|
|
└── ...
|
|
|
|
tests/
|
|
├── integration_test.rs
|
|
└── ...
|
|
```
|
|
|
|
## Test Structure
|
|
|
|
**Suite Organization - Unit Test Pattern:**
|
|
```rust
|
|
#[cfg(test)]
|
|
mod tests {
|
|
use super::*;
|
|
|
|
#[test]
|
|
fn test_function_does_something() {
|
|
let input = setup_test_data();
|
|
let result = function_under_test(input);
|
|
assert_eq!(result, expected_value);
|
|
}
|
|
}
|
|
```
|
|
|
|
**Suite Organization - Integration Test Pattern:**
|
|
```rust
|
|
// tests/integration_test.rs
|
|
#[test]
|
|
fn test_system_integrates_correctly() {
|
|
let app = App::new();
|
|
let result = app.process_command("help");
|
|
assert!(result.is_ok());
|
|
}
|
|
```
|
|
|
|
**Patterns:**
|
|
- **Setup:** Create test data or fixtures in test function body
|
|
- **Teardown:** Not typically needed in Rust; cleanup happens automatically when test scope ends
|
|
- **Assertion:** Use `assert!()`, `assert_eq!()`, `assert_ne!()` for conditions
|
|
|
|
## Mocking
|
|
|
|
**Framework:** No explicit mocking framework currently integrated
|
|
|
|
**If mocking becomes necessary:**
|
|
- Use `mockito` crate for HTTP mocking
|
|
- Use `mockall` crate for trait-based mocking
|
|
- Use `proptest` crate for property-based testing
|
|
|
|
**Current Approach:**
|
|
- Manual test doubles: Create simplified implementations of traits for testing
|
|
- Dependency injection: Pass different implementations at test time
|
|
- Stub data: Return hardcoded test data instead of external calls
|
|
|
|
**Example of Manual Test Double:**
|
|
```rust
|
|
#[cfg(test)]
|
|
mod tests {
|
|
struct MockDataStore {
|
|
data: Vec<String>,
|
|
}
|
|
|
|
impl DataStore for MockDataStore {
|
|
fn fetch(&self, id: u32) -> Option<String> {
|
|
self.data.get(id as usize).cloned()
|
|
}
|
|
}
|
|
|
|
#[test]
|
|
fn test_with_mock() {
|
|
let mock = MockDataStore { data: vec!["test".to_string()] };
|
|
let result = process_data(&mock);
|
|
assert!(result.is_ok());
|
|
}
|
|
}
|
|
```
|
|
|
|
**What to Mock:**
|
|
- External service calls (network, file I/O)
|
|
- Database operations
|
|
- Time-dependent behavior
|
|
- Hardware-dependent code
|
|
|
|
**What NOT to Mock:**
|
|
- Core business logic
|
|
- Standard library functions
|
|
- Tested utility functions
|
|
- Pure functions
|
|
|
|
## Fixtures and Factories
|
|
|
|
**Test Data:**
|
|
- Currently: Manual test data created in test functions
|
|
- Pattern: Helper functions to build test objects
|
|
|
|
**Example:**
|
|
```rust
|
|
#[cfg(test)]
|
|
mod tests {
|
|
fn create_test_user() -> User {
|
|
User {
|
|
id: 1,
|
|
name: "Test User".to_string(),
|
|
email: "test@example.com".to_string(),
|
|
}
|
|
}
|
|
|
|
#[test]
|
|
fn test_user_creation() {
|
|
let user = create_test_user();
|
|
assert_eq!(user.name, "Test User");
|
|
}
|
|
}
|
|
```
|
|
|
|
**Location:**
|
|
- Helper functions in same module as tests: `#[cfg(test)] mod tests`
|
|
- If many fixtures needed: Create `src/test_utils.rs` (not included in release build via `#[cfg(test)]`)
|
|
|
|
## Coverage
|
|
|
|
**Requirements:** Not currently enforced
|
|
|
|
**To Enable Coverage Tracking:**
|
|
```bash
|
|
# Using llvm-cov (install with: cargo install cargo-llvm-cov)
|
|
cargo llvm-cov # Generate coverage report
|
|
cargo llvm-cov --html # HTML report in target/llvm-cov/html
|
|
```
|
|
|
|
**Target for new code:**
|
|
- Aim for 80%+ coverage on public APIs
|
|
- 100% coverage on critical paths
|
|
- Document uncovered code with `#[cfg(test)]` or comments
|
|
|
|
## Test Types
|
|
|
|
**Unit Tests:**
|
|
- Scope: Single function or small module in isolation
|
|
- Approach: Test behavior with various inputs including edge cases
|
|
- Location: Same file as implementation in `#[cfg(test)] mod tests`
|
|
- Tools: Standard library assertions
|
|
|
|
**Integration Tests:**
|
|
- Scope: Multiple components working together
|
|
- Approach: Test workflows and interactions between modules
|
|
- Location: `tests/` directory at workspace root
|
|
- Tools: Standard library assertions
|
|
|
|
**Doc Tests:**
|
|
- Scope: Examples in `///` doc comments
|
|
- Approach: Verify example code actually works
|
|
- Location: Documentation strings in source
|
|
- Tools: Built-in `cargo test --doc`
|
|
|
|
**E2E Tests:**
|
|
- Currently: Not applicable (TUI application, no external API)
|
|
- When applicable: Test complete user workflows end-to-end
|
|
|
|
**Example Doc Test:**
|
|
```rust
|
|
/// Calculate the sum of two numbers.
|
|
///
|
|
/// # Examples
|
|
///
|
|
/// ```
|
|
/// assert_eq!(add(2, 2), 4);
|
|
/// ```
|
|
pub fn add(a: i32, b: i32) -> i32 {
|
|
a + b
|
|
}
|
|
```
|
|
|
|
## Common Patterns
|
|
|
|
**Async Testing:**
|
|
- Not currently used (no async in codebase)
|
|
- When needed: Use `#[tokio::test]` attribute with `tokio` crate
|
|
|
|
**Example (future reference):**
|
|
```rust
|
|
#[tokio::test]
|
|
async fn test_async_function() {
|
|
let result = async_operation().await;
|
|
assert!(result.is_ok());
|
|
}
|
|
```
|
|
|
|
**Error Testing:**
|
|
- Test both success and failure paths
|
|
- Use `.is_err()` or `.is_ok()` to test Result types
|
|
- Use `.is_some()` or `.is_none()` to test Option types
|
|
|
|
**Example:**
|
|
```rust
|
|
#[test]
|
|
fn test_handles_invalid_input() {
|
|
let result = parse_config("invalid");
|
|
assert!(result.is_err());
|
|
}
|
|
|
|
#[test]
|
|
fn test_handles_valid_input() {
|
|
let result = parse_config("valid");
|
|
assert!(result.is_ok());
|
|
}
|
|
```
|
|
|
|
**Property-Based Testing (future):**
|
|
If using `proptest`:
|
|
```rust
|
|
#[test]
|
|
fn test_reverse_is_involutive(input in ".*") {
|
|
let reversed = reverse(&input);
|
|
let double_reversed = reverse(&reversed);
|
|
assert_eq!(input, double_reversed);
|
|
}
|
|
```
|
|
|
|
## Test Organization Best Practices
|
|
|
|
**File Structure:**
|
|
```
|
|
src/
|
|
├── main.rs
|
|
├── ui.rs # Contains #[cfg(test)] mod tests at bottom
|
|
├── handler.rs # Contains #[cfg(test)] mod tests at bottom
|
|
└── model.rs # Contains #[cfg(test)] mod tests at bottom
|
|
|
|
tests/
|
|
├── integration_tests.rs # Full-system integration tests
|
|
└── fixtures/ # Test data files if needed
|
|
```
|
|
|
|
**Test Module Template:**
|
|
```rust
|
|
// At end of source file
|
|
#[cfg(test)]
|
|
mod tests {
|
|
use super::*;
|
|
|
|
// Setup helpers
|
|
fn create_test_fixture() -> TestData {
|
|
// ...
|
|
}
|
|
|
|
// Tests organized by function
|
|
mod function_name_tests {
|
|
use super::*;
|
|
|
|
#[test]
|
|
fn test_success_case() {
|
|
// ...
|
|
}
|
|
|
|
#[test]
|
|
fn test_error_case() {
|
|
// ...
|
|
}
|
|
}
|
|
}
|
|
```
|
|
|
|
---
|
|
|
|
*Testing analysis: 2026-02-28*
|