Back to catalog

quality-unit-testing

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.

  • Category: discovered
  • Tags: github, community
  • Version: 1.0.0
  • Author: walid
  • Downloads: 0
  • GitHub stars: 2
  • Updated: 2025-11-21

Download the latest version (v1.0.0) from this page.

SKILL.md

---
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):**
```rust
#[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):**
```rust
#[test]
fn test_new_account_initializes_fields()  // When behavior is obvious

#[test]
fn test_default_context_values()  // When scenario is implicit
```

### 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):**
```rust
#[test]
fn test_account_withdraw_valid_amount_decreases_balance() {
    // Arrange - Set up test context
    let mut account = Account::new(100);

    // Act - Execute behavior
    let result = account.withdraw(30);

    // Assert - Verify outcome
    assert!(result.is_ok());
    assert_eq!(account.balance(), 70);
}
```

**Simple test (AAA comments optional):**
```rust
#[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:

```bash
# Install dependencies (optional but recommended)
pip install -r scripts/requirements.txt

# Or install manually
pip install tomli  # Only needed for Python < 3.11
```

**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:
```bash
# Basic analysis
python scripts/analyze-test-quality.py memory-core/src/lib.rs

# Lenient mode (recommended for existing codebases)
python scripts/analyze-test-quality.py memory-core/src/lib.rs --lenient

# With specific options
python scripts/analyze-test-quality.py memory-core/src/lib.rs \
    --allow-two-part-names \
    --max-assertions 7

# Using config file (requires tomli for Python < 3.11)
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:
```toml
# Test quality analysis configuration
[naming]
require_three_parts = false  # Allow two-part names for simple tests
min_parts = 2

[structure]
require_aaa_comments = "complex"  # "always", "complex", or "never"
complex_test_threshold = 10  # Lines count for "complex"

[focus]
max_assertions = 7  # Warning threshold
max_assertions_error = 10  # Error threshold

[severity]
naming_violation = "medium"  # "high", "medium", or "low"
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
```rust
#[tokio::test]
async fn test_async_operation() {
    let result = async_function().await;
    assert!(result.is_ok());
}
```

### 2. Use Result<()> for Tests with Error Handling
```rust
#[test]
fn test_operation() -> anyhow::Result<()> {
    let result = fallible_operation()?;
    assert_eq!(result, expected);
    Ok(())
}
```

### 3. Use Test Builders for Complex Setup
```rust
let episode = TestEpisodeBuilder::new()
    .with_task("Test task")
    .with_context(context)
    .completed(true)
    .build();
```

### 4. Clean Up with RAII (Drop)
```rust
struct TestDb(TempDir);

impl Drop for TestDb {
    fn drop(&mut self) {
        // Cleanup happens automatically
    }
}
```

### 5. Use `tempfile` for File System Tests
```rust
#[test]
fn test_file_operation() {
    let dir = TempDir::new().unwrap();
    let path = dir.path().join("test.db");
    // Test with path
    // Cleanup happens automatically when dir is dropped
}
```

## Project-Specific Patterns

### Testing Async Code with Tokio
```rust
#[tokio::test]
async fn test_start_episode_valid_task_creates_episode() {
    // Arrange
    let memory = create_test_memory().await;
    let context = TaskContext::default();

    // Act
    let episode_id = memory.start_episode("Test task", context).await?;

    // Assert
    assert!(!episode_id.is_empty());
    let episode = memory.get_episode(&episode_id).await?;
    assert_eq!(episode.task_description, "Test task");
}
```

### Testing Error Cases
```rust
#[tokio::test]
async fn test_get_episode_invalid_id_returns_error() {
    // Arrange
    let memory = create_test_memory().await;

    // Act
    let result = memory.get_episode("invalid_id").await;

    // Assert
    assert!(result.is_err());
    assert!(matches!(result.unwrap_err(), Error::EpisodeNotFound(_)));
}
```

### Using Test Utilities
```rust
use test_utils::*;

#[test]
fn test_episode_completion() {
    // Arrange
    let episode = create_completed_episode("Test task", true);

    // Act & Assert
    assert!(episode.is_complete());
    assert_eq!(episode.verdict, Some(Verdict::Success));
}
```

## Workflow for Creating Tests

1. **Understand the code**: What behavior needs verification?
2. **Identify risks**: What could break in production?
3. **Write failing test first** (red-green-refactor)
4. **Apply AAA pattern** with clear naming
5. **Isolate dependencies** with proper mocking
6. **Verify test speed** (should be milliseconds)
7. **Check quality** against checklists
8. **Ensure value**: Does this catch real bugs?

## Workflow for Reviewing Tests

1. Run analysis script: `scripts/analyze-test-quality.py`
2. Check against review checklist: `checklists/review.md`
3. Verify naming follows conventions
4. Ensure proper isolation
5. Confirm single responsibility
6. Check for anti-patterns
7. 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