| name | quality-unit-testing |
| description | Write high-quality Rust unit tests following best practices. Use when writing new tests, reviewing test code, or improving test quality. Emphasizes clear naming, AAA pattern, isolation, and deployment confidence. |
Quality Unit Testing for Rust
Expert guidance for writing unit tests that catch real bugs and provide deployment confidence.
Core Philosophy
Quality over coverage: Tests should catch real bugs and enable fearless deployment, not just boost coverage percentages.
When to Use This Skill
Use for:
- Writing new unit tests
- Reviewing test code quality
- Improving existing test suites
- Establishing testing standards
- Evaluating test effectiveness
Quick Reference
Test Naming: test_<function>_<scenario>_<expected_behavior>
Use descriptive three-part names for most tests. Well-named two-part names are acceptable for very simple tests:
Preferred (three-part):
#[test]
fn test_process_payment_insufficient_funds_returns_error()
#[test]
fn test_calculate_discount_new_customer_returns_zero()
#[tokio::test]
async fn test_withdraw_valid_amount_decreases_balance()
Acceptable for simple tests (two-part):
#[test]
fn test_new_account_initializes_fields()
#[test]
fn test_default_context_values()
AAA Pattern: Arrange-Act-Assert
Structure tests with clear sections. AAA comments are recommended for complex tests (>10 lines, multiple setup steps, async operations), optional for simple tests:
Complex test (AAA comments recommended):
#[test]
fn test_account_withdraw_valid_amount_decreases_balance() {
let mut account = Account::new(100);
let result = account.withdraw(30);
assert!(result.is_ok());
assert_eq!(account.balance(), 70);
}
Simple test (AAA comments optional):
#[test]
fn test_new_account_starts_with_zero_balance() {
let account = Account::new(0);
assert_eq!(account.balance(), 0);
}
Isolation: Mock External Dependencies
- ✅ Mock: APIs, databases, file systems, time/date, external services
- ❌ Don't mock: Value types, pure functions, the code under test
Single Responsibility
Each test verifies ONE behavior with ONE reason to fail.
Speed Target
Milliseconds per test. Unit tests should run instantly.
Detailed Guidance
For detailed information on specific topics, see:
- Test Naming Patterns:
reference/naming-conventions.md
- AAA Structure Details:
reference/aaa-pattern.md
- Async Testing:
reference/async-testing.md
- Test Builders:
reference/test-builders.md
- Anti-Patterns to Avoid:
reference/anti-patterns.md
Test Quality Analysis
Installation
The analysis script requires Python 3.8+ and optionally the tomli library for TOML configuration support:
pip install -r scripts/requirements.txt
pip install tomli
Note: Python 3.11+ includes tomllib in the standard library, so no dependencies are needed. For earlier versions, install tomli to use TOML configuration files.
Usage
To analyze test file quality:
python scripts/analyze-test-quality.py memory-core/src/lib.rs
python scripts/analyze-test-quality.py memory-core/src/lib.rs --lenient
python scripts/analyze-test-quality.py memory-core/src/lib.rs \
--allow-two-part-names \
--max-assertions 7
python scripts/analyze-test-quality.py memory-core/src/lib.rs \
--config=.test-quality.toml
Configuration Options
Create .test-quality.toml in your project root:
[naming]
require_three_parts = false
min_parts = 2
[structure]
require_aaa_comments = "complex"
complex_test_threshold = 10
[focus]
max_assertions = 7
max_assertions_error = 10
[severity]
naming_violation = "medium"
missing_aaa_simple = "low"
missing_aaa_complex = "medium"
Templates
Use pre-built templates for consistent test structure:
- Basic unit test:
templates/unit-test.md
- Async test:
templates/async-test.md
- Test builder pattern:
templates/test-builder.md
Quality Checklists
Before committing tests, verify against:
- Pre-commit checklist:
checklists/pre-commit.md
- Code review checklist:
checklists/review.md
Key Success Metrics
You're succeeding when:
- ✅ You deploy without manual testing
- ✅ Test failures pinpoint exact problems
- ✅ Refactoring doesn't break unrelated tests
- ✅ Tests run in milliseconds
- ✅ Every failure is actionable
You need improvement when:
- ❌ Tests are skipped because they're slow
- ❌ Test failures require investigation
- ❌ High coverage but low deployment confidence
- ❌ Flaky tests train team to ignore failures
Rust-Specific Best Practices
1. Use #[tokio::test] for Async Tests
#[tokio::test]
async fn test_async_operation() {
let result = async_function().await;
assert!(result.is_ok());
}
2. Use Result<()> for Tests with Error Handling
#[test]
fn test_operation() -> anyhow::Result<()> {
let result = fallible_operation()?;
assert_eq!(result, expected);
Ok(())
}
3. Use Test Builders for Complex Setup
let episode = TestEpisodeBuilder::new()
.with_task("Test task")
.with_context(context)
.completed(true)
.build();
4. Clean Up with RAII (Drop)
struct TestDb(TempDir);
impl Drop for TestDb {
fn drop(&mut self) {
}
}
5. Use tempfile for File System Tests
#[test]
fn test_file_operation() {
let dir = TempDir::new().unwrap();
let path = dir.path().join("test.db");
}
Project-Specific Patterns
Testing Async Code with Tokio
#[tokio::test]
async fn test_start_episode_valid_task_creates_episode() {
let memory = create_test_memory().await;
let context = TaskContext::default();
let episode_id = memory.start_episode("Test task", context).await?;
assert!(!episode_id.is_empty());
let episode = memory.get_episode(&episode_id).await?;
assert_eq!(episode.task_description, "Test task");
}
Testing Error Cases
#[tokio::test]
async fn test_get_episode_invalid_id_returns_error() {
let memory = create_test_memory().await;
let result = memory.get_episode("invalid_id").await;
assert!(result.is_err());
assert!(matches!(result.unwrap_err(), Error::EpisodeNotFound(_)));
}
Using Test Utilities
use test_utils::*;
#[test]
fn test_episode_completion() {
let episode = create_completed_episode("Test task", true);
assert!(episode.is_complete());
assert_eq!(episode.verdict, Some(Verdict::Success));
}
Workflow for Creating Tests
- Understand the code: What behavior needs verification?
- Identify risks: What could break in production?
- Write failing test first (red-green-refactor)
- Apply AAA pattern with clear naming
- Isolate dependencies with proper mocking
- Verify test speed (should be milliseconds)
- Check quality against checklists
- Ensure value: Does this catch real bugs?
Workflow for Reviewing Tests
- Run analysis script:
scripts/analyze-test-quality.py
- Check against review checklist:
checklists/review.md
- Verify naming follows conventions
- Ensure proper isolation
- Confirm single responsibility
- Check for anti-patterns
- Validate test value
Remember
The goal is deployment confidence, not coverage theater.
Focus testing effort where failures hurt most:
- High Priority: Business logic, data integrity, async correctness
- Medium Priority: Integration points, error handling
- Low Priority: Simple getters/setters, trivial code
Integration with Other Skills
- Use test-runner to execute tests:
cargo test
- Use test-fix when tests fail and need diagnosis
- Use code-quality to ensure test code meets standards
- Use rust-code-quality for comprehensive review