docs: map existing codebase
This commit is contained in:
@@ -0,0 +1,324 @@
|
||||
# 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*
|
||||
Reference in New Issue
Block a user