
test-strategy
by jarosser06
Quality assurance for AI-augmented codebases - validates project structure and AI agent collaboration patterns through custom rules.
SKILL.md
name: test-strategy description: Plans pytest test strategies for Drift's 90%+ coverage requirement using coverage analysis, edge case identification, and test suite design. Use when determining what tests to write, analyzing coverage gaps, or planning test strategies. skills:
- testing
- python-basics
Test Strategy Skill
Learn how to plan comprehensive test coverage for the Drift project.
When to Use This Skill
Use test-strategy when:
- Planning tests for a new feature or code change
- Analyzing why coverage is below 90%
- Identifying missing edge cases
- Organizing tests for a new module
- Reviewing test suites for completeness
Use testing skill when:
- Writing the actual pytest code
- Creating fixtures and mocks
- Running tests and interpreting results
How to Identify What Tests to Write
From Requirements to Test Cases
Follow this process:
- Extract Requirements - What should the code do?
- Identify Behaviors - What are all the possible behaviors?
- Map to Test Cases - One test per behavior
- Prioritize by Risk - Critical path first
Example: Validator Implementation
Given: "Create a validator that checks if code blocks exceed maximum line count"
Step 1: Extract Requirements
- R1: Find code blocks in markdown files
- R2: Count lines in each code block
- R3: Compare against max_count parameter
- R4: Return None if under limit
- R5: Return failure if over limit
- R6: Handle files without code blocks
Step 2: Map Behaviors to Tests
# From R2, R3, R4, R5:
def test_passes_when_block_under_max():
"""Block with 50 lines, max 100."""
def test_fails_when_block_exceeds_max():
"""Block with 150 lines, max 100."""
def test_passes_when_block_equals_max(): # Boundary case!
"""Block with exactly 100 lines, max 100."""
# From R6:
def test_passes_when_no_code_blocks():
"""File with no code blocks."""
# Additional behaviors discovered:
def test_handles_empty_code_block():
"""Empty code block (no lines)."""
def test_handles_multiple_code_blocks():
"""File with multiple code blocks, one exceeds."""
def test_counts_only_block_content_not_fence():
"""Doesn't count ``` fence lines."""
When to Write Each Test Type
Unit Tests (80-90%): Individual validators, utility functions, model validation, parsers, formatters
Integration Tests (10-15%): Validator with DocumentBundle, analyzer with multiple phases, config loading
E2E Tests (<5%): Full drift --no-llm workflow, complete analysis pipeline
See Test Identification Guide for examples of each type.
How to Achieve Good Coverage
Coverage ≠ Quality
Understanding 90% coverage:
- ✅ Means: 90% of lines are executed during tests
- ❌ Does NOT mean: 90% of behaviors are tested
Coverage Analysis Workflow
When coverage is below 90%:
-
Run coverage with missing lines
pytest --cov=src/drift --cov-report=term-missingOutput shows:
Name Stmts Miss Cover Missing src/drift/parser.py 45 3 93% 67-69 src/drift/detector.py 120 15 88% 45, 78-92 -
Analyze uncovered lines
# Read the file, look at lines 67-69 # Ask: What behavior do these lines represent? # - Error handling? # - Branch condition? # - Edge case? -
Write tests for behaviors, not just line hits
# BAD: Just hitting lines def test_coverage_filler(): validator.validate(rule, bundle) # Just runs the code # GOOD: Testing actual behavior def test_fails_when_schema_validation_fails(): """Schema validation error path (lines 67-69).""" invalid_rule = ValidationRule(schema={}) result = validator.validate(invalid_rule, bundle) assert result is not None assert "schema validation failed" in result.observed_issue
Use Branch Coverage
# Check that both paths of if/else are tested
pytest --cov-branch --cov=src/drift --cov-report=term-missing
This ensures:
# Both paths tested
if condition:
path_a() # Covered
else:
path_b() # Also covered
Coverage Patterns
Pattern 1: Test all validator outcomes
def test_passes_when_valid(): # Happy path
"""Valid input returns None."""
def test_fails_when_invalid(): # Failure path
"""Invalid input returns failure."""
def test_handles_file_not_found(): # Error path
"""Missing file raises appropriate error."""
def test_handles_missing_params(): # Configuration error
"""Missing required params detected."""
Pattern 2: Test exception paths
def test_missing_package(monkeypatch): # ImportError
"""Handles missing optional dependency."""
monkeypatch.setattr("sys.modules", {"package": None})
def test_file_read_error(monkeypatch): # IOError
"""Handles file read permissions error."""
def test_invalid_yaml(tmp_path): # YAMLError
"""Handles malformed YAML gracefully."""
How to Identify Edge Cases
Equivalence Partitioning
Concept: Divide input domain into classes that should behave similarly.
Example: Line count validator
Input: code block line count (integer)
Step 1: Identify Partitions
- No code blocks
- Valid range (min to max)
- Above maximum
Step 2: Test one value from each partition + boundaries
# Partition 1: No code blocks
def test_passes_when_no_code_blocks():
"""File with no code blocks."""
content = "# Just markdown\nNo code here"
assert validator.check(content, max_count=100) is None
# Partition 2: Valid range
def test_passes_when_in_valid_range():
"""Code block with 50 lines (middle of range)."""
content = "```\n" + "\n".join([f"line{i}" for i in range(50)]) + "\n```"
assert validator.check(content, max_count=100) is None
# Partition 3: Above max
def test_fails_when_above_maximum():
"""Code block with 150 lines exceeds max."""
content = "```\n" + "\n".join([f"line{i}" for i in range(150)]) + "\n```"
result = validator.check(content, max_count=100)
assert result is not None
Boundary Value Analysis
Critical insight: Bugs cluster at boundaries!
For any constraint, test:
- Just below boundary
- At boundary
- Just above boundary
Example: max_count = 100
def test_passes_with_99_lines(): # Just below
"""99 lines is under limit."""
content = create_code_block(99)
assert validator.check(content, max_count=100) is None
def test_passes_with_100_lines(): # At boundary
"""Exactly 100 lines is at limit (inclusive)."""
content = create_code_block(100)
assert validator.check(content, max_count=100) is None
def test_fails_with_101_lines(): # Just above
"""101 lines exceeds limit."""
content = create_code_block(101)
result = validator.check(content, max_count=100)
assert result is not None
Common boundaries in Drift:
- Empty collections:
[],"",{},None - Zero values:
0,0.0 - File boundaries: empty file, no code blocks
- String boundaries:
"","a", very long string
Type-Specific Edge Cases
Strings:
@pytest.mark.parametrize("value", [
"", # Empty
" ", # Whitespace only
"\n\t", # Special chars
"a" * 10000, # Very long
])
def test_handles_string_edge_cases(value):
"""Test with various string edge cases."""
result = process_string(value)
assert result is not None
Lists/Arrays:
@pytest.mark.parametrize("items", [
[], # Empty
[single_item], # Single item
many_items, # Many items
])
def test_handles_list_edge_cases(items):
"""Test with various list sizes."""
result = process_list(items)
assert isinstance(result, list)
Files:
def test_handles_missing_file():
"""File doesn't exist."""
with pytest.raises(FileNotFoundError):
load_file("missing.txt")
def test_handles_empty_file(tmp_path):
"""File exists but is empty."""
empty = tmp_path / "empty.txt"
empty.touch()
result = load_file(str(empty))
assert result == ""
def test_handles_malformed_file(tmp_path):
"""File has invalid format."""
bad_file = tmp_path / "bad.yaml"
bad_file.write_text("{invalid yaml")
with pytest.raises(ValueError):
load_yaml(str(bad_file))
Numbers:
@pytest.mark.parametrize("count", [
0, # Zero
-1, # Negative
MAX_COUNT, # Boundary
MAX_COUNT + 1, # Overflow
])
def test_handles_number_edge_cases(count):
"""Test with various number edge cases."""
result = validate_count(count, max_count=MAX_COUNT)
# Assertions depend on expected behavior
Error Guessing Technique
Based on Drift patterns, anticipate these common edge cases:
1. File operations:
def test_file_doesnt_exist() # Path("missing.txt")
def test_file_is_empty() # Empty file
def test_file_unreadable() # Permission denied
def test_file_malformed() # Invalid content
2. YAML/JSON parsing:
def test_unclosed_frontmatter() # Missing ---
def test_invalid_yaml_syntax() # {bad: yaml
def test_empty_frontmatter() # --- ---
def test_missing_required_fields() # No 'name' field
3. Validation logic:
def test_missing_required_params() # No max_count provided
def test_invalid_parameter_types() # max_count="not_a_number"
def test_boundary_conditions() # Exactly at limit
def test_resource_not_found() # Referenced file missing
4. Bundle operations:
def test_empty_bundle_files() # bundle.files = []
def test_missing_file_path() # rule.file_path is None
def test_all_bundles_none() # all_bundles parameter = None
How to Organize Tests
File Naming Conventions
Follow Drift's pattern:
tests/
├── unit/
│ ├── test_<validator_name>_validator.py
│ └── test_<module>_<feature>.py
├── integration/
│ └── test_<workflow>_integration.py
└── conftest.py
Examples:
test_block_line_count_validator.pytest_yaml_frontmatter_validator.pytest_analyzer_multi_phase.py
Test Class Organization
See Test Organization Patterns for detailed class organization examples.
Test Method Naming
Use descriptive names that explain the scenario:
# Good names
def test_passes_when_block_under_max()
def test_fails_when_block_exceeds_max()
def test_handles_empty_code_block()
def test_handles_multiple_blocks_one_exceeds()
# Avoid vague names
def test_validator() # What about it?
def test_case_1() # What is case 1?
def test_edge() # Which edge?
Test Planning Checklist
When planning tests for a feature:
-
Requirements Analysis
- Listed all requirements
- Identified all behaviors
- Mapped behaviors to test cases
-
Edge Cases
- Tested boundary values
- Tested empty/null cases
- Tested error conditions
- Used equivalence partitioning
-
Coverage
- Achieves 90%+ line coverage
- Tests all branches (if/else)
- Tests error handling paths
- Tests are meaningful, not just line hits
-
Organization
- Tests organized by type (unit/integration/e2e)
- Test files named consistently
- Fixtures reused effectively
- Test names are descriptive
-
Balance
- Mostly unit tests (fast feedback)
- Some integration tests (confidence)
- Minimal e2e tests (coverage of critical paths)
Resources
All test strategy resources are available in the resources/ directory:
📖 Test Identification Guide
Step-by-step process for identifying what tests to write from requirements.
Use when: Planning tests for a new feature or analyzing what's missing.
📖 Coverage Strategies
Techniques for achieving and maintaining 90%+ coverage meaningfully.
Use when: Coverage is below target or analyzing coverage reports.
📖 Edge Case Techniques
Detailed guide on equivalence partitioning, boundary analysis, and error guessing.
Use when: Identifying missing edge cases or reviewing test completeness.
📖 Test Organization Patterns
Patterns for organizing test files, classes, and fixtures.
Use when: Setting up tests for a new module or refactoring test structure.
📖 Mocking Strategies
Patterns for mocking dependencies, external APIs, and file systems.
Use when: Testing code with external dependencies or complex interactions.
Score
Total Score
Based on repository quality metrics
SKILL.mdファイルが含まれている
ライセンスが設定されている
100文字以上の説明がある
GitHub Stars 100以上
3ヶ月以内に更新
10回以上フォークされている
オープンIssueが50未満
プログラミング言語が設定されている
1つ以上のタグが設定されている
Reviews
Reviews coming soon


