Version: 1.0
Last Updated: 2025-12-25
Traceability: Requirement R2 (Test Suite Reconstruction)
This document establishes the testing policy for all RiceCoder crates. It defines where tests live, how they are organized, coverage expectations, and the migration plan for existing inline tests.
All tests MUST be placed in tests/ directories, NOT inline #[cfg(test)] modules.
Each crate follows this structure:
crates/{crate-name}/
├── src/
│ └── lib.rs # No #[cfg(test)] modules here
├── tests/
│ ├── unit/ # Unit tests (isolated, fast)
│ │ ├── mod.rs # Module re-exports
│ │ └── {module}_test.rs
│ ├── integration/ # Integration tests (cross-module, I/O)
│ │ ├── mod.rs
│ │ └── {feature}_test.rs
│ └── fixtures/ # Test data and mocks
│ ├── sample.json
│ └── mock_data.rs
└── Cargo.toml
| Type | File Pattern | Example |
|---|---|---|
| Unit test | {module}_test.rs |
parser_test.rs |
| Integration test | {feature}_test.rs |
api_integration_test.rs |
| Property test | {module}_properties.rs |
validation_properties.rs |
| Fixture data | {name}.json, {name}.yaml |
sample_config.yaml |
| Mock implementations | mock_{trait}.rs |
mock_repository.rs |
Coverage requirements are based on DDD layer classification:
| Layer | Target | Rationale |
|---|---|---|
| Domain | ≥90% | Core business logic; highest risk if bugs exist |
| Application | ≥85% | Use case orchestration; critical for correctness |
| Infrastructure | ≥80% | External I/O; integration tests cover edge cases |
| Presentation | ≥75% | UI/CLI; some paths hard to test deterministically |
| Layer | Required Test Types |
|---|---|
| Domain | Unit tests, property tests, contract tests, invariant tests |
| Application | Use case tests with mocked repositories, service integration |
| Infrastructure | Integration tests, I/O mocking, external API mocking |
| Presentation | Widget tests, CLI command tests, keybinding tests |
// ❌ WRONG - Tests inside src/
// src/parser.rs
pub fn parse(input: &str) -> Result<Ast, Error> { ... }
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn test_parse_valid() { ... }
}Why this is bad:
- Tests mixed with production code
- Harder to discover and run selectively
- Clutters source files
- Inconsistent with Rust community best practices for libraries
// ❌ WRONG - Hardcoded paths
#[test]
fn test_read_file() {
let content = fs::read_to_string("/home/user/test.txt").unwrap();
}Why this is bad:
- Fails on different machines
- Non-deterministic
- Requires manual setup
// ❌ WRONG - No assertions
#[test]
fn test_something() {
let result = do_something();
// No assert! - test always passes
}// ❌ WRONG - Shared mutable state
static mut COUNTER: i32 = 0;
#[test]
fn test_one() {
unsafe { COUNTER += 1; }
}
#[test]
fn test_two() {
unsafe { assert_eq!(COUNTER, 0); } // Flaky!
}// ✅ CORRECT - Tests in tests/ directory
// tests/unit/parser_test.rs
use ricecoder_parser::parse;
#[test]
fn parse_valid_input_returns_ast() {
let result = parse("valid input");
assert!(result.is_ok());
}
#[test]
fn parse_invalid_input_returns_error() {
let result = parse("{{invalid");
assert!(result.is_err());
}// ✅ CORRECT - Use fixtures
// tests/fixtures/mod.rs
pub fn sample_config() -> Config {
serde_json::from_str(include_str!("sample_config.json")).unwrap()
}
// tests/unit/config_test.rs
use crate::fixtures::sample_config;
#[test]
fn config_loads_correctly() {
let config = sample_config();
assert_eq!(config.name, "test");
}// ✅ CORRECT - Property tests for domain
// tests/unit/amount_properties.rs
use proptest::prelude::*;
use ricecoder_domain::Amount;
proptest! {
#[test]
fn amount_add_is_commutative(a in 0i64..1000, b in 0i64..1000) {
let sum1 = Amount::new(a) + Amount::new(b);
let sum2 = Amount::new(b) + Amount::new(a);
prop_assert_eq!(sum1, sum2);
}
}// ✅ CORRECT - Mock repositories for use cases
// tests/unit/create_session_test.rs
use ricecoder_application::CreateSessionUseCase;
use crate::fixtures::MockSessionRepository;
#[test]
fn create_session_stores_in_repository() {
let mock_repo = MockSessionRepository::new();
let use_case = CreateSessionUseCase::new(mock_repo.clone());
let result = use_case.execute("test-session");
assert!(result.is_ok());
assert!(mock_repo.contains("test-session"));
}-
Run inventory script to find all inline
#[cfg(test)]modules:rg "#\[cfg\(test\)\]" --type rust crates/ -l > inline_tests.txt
-
Categorize by crate and priority (Domain > Application > Infrastructure > Presentation)
-
Create migration tracking issue
- For each Domain crate with inline tests:
- Create
tests/unit/directory - Move test functions to
{module}_test.rs - Update imports to use
use crate_name::*instead ofuse super::* - Remove
#[cfg(test)]module from source - Run
cargo testto verify
- Create
- Same process as Phase 2
- Additional: Create mock implementations in
tests/fixtures/
- Same process
- Additional: Move integration tests to
tests/integration/
- Run full
cargo test --workspace - Verify zero inline
#[cfg(test)]modules remain:rg "#\[cfg\(test\)\]" --type rust crates/*/src/ -c # Should return 0 matches or only documented exceptions
Some cases may justify inline tests. These require explicit documentation.
- Macro testing: Macros that generate code may need inline tests to verify expansion
- Compile-time assertions:
constassertions or compile-fail tests - Private API testing: Testing private functions that cannot be accessed from
tests/
Each exception MUST be documented in the crate's README.md:
## Test Exceptions
### `src/macros.rs` - Inline tests
**Reason:** Macro hygiene testing requires inline context to verify expansion.
**Approved by:** [Name]
**Date:** YYYY-MM-DD
**Review date:** YYYY-MM-DD (annual review required)All exceptions are tracked in wiki/Testing-Strategy-and-Policy.md:
| Crate | File | Reason | Approved | Review Date |
|---|---|---|---|---|
| ricecoder-macros | src/derive.rs | Macro expansion testing | 2025-12-25 | 2026-12-25 |
# Run all tests
cargo test --workspace
# Run specific crate tests
cargo test -p ricecoder-domain
# Run with verbose output
cargo test --workspace -- --nocapture
# Run single test
cargo test -p ricecoder-domain test_name# Required CI checks
- cargo build --workspace
- cargo clippy --workspace -- -D warnings
- cargo test --workspace
- cargo audit| Metric | Target | Current |
|---|---|---|
cargo test --workspace |
≤10 minutes | TBD |
| Individual test timeout | ≤60 seconds | N/A |
| Flaky test rate | 0% | TBD |
| Crate | Purpose |
|---|---|
proptest |
Property-based testing |
mockall |
Mock generation |
tempfile |
Temporary file fixtures |
assert_matches |
Pattern matching assertions |
tokio::test |
Async test runtime |
Create ricecoder-test-utils crate for shared testing infrastructure:
crates/ricecoder-test-utils/
├── src/
│ ├── lib.rs
│ ├── fixtures.rs # Common test data
│ ├── mocks.rs # Shared mock implementations
│ └── assertions.rs # Custom assertion macros
└── Cargo.toml
| Version | Date | Changes |
|---|---|---|
| 1.0 | 2025-12-25 | Initial policy document |
- Requirement R2: Test Suite Reconstruction (requirements.md)
- Design Component 2: Test Infrastructure (design.md)
- Wiki: Testing-Strategy-and-Policy.md (to be created)