Concerns (6 files): - coding-style.md (163 lines): patterns, anti-patterns, error handling, SOLID - naming.md (105 lines): naming conventions table per language - documentation.md (149 lines): docstrings, WHY vs WHAT, README standards - testing.md (134 lines): AAA pattern, mocking philosophy, TDD - git-workflow.md (118 lines): conventional commits, branch naming, PR format - project-structure.md (82 lines): directory layout, entry points, config placement Languages (4 files): - python.md (224 lines): uv, ruff, pyright, pytest, pydantic, idioms, anti-patterns - typescript.md (150 lines): strict mode, discriminated unions, satisfies, as const - nix.md (129 lines): flake structure, module patterns, alejandra, anti-patterns - shell.md (100 lines): set -euo pipefail, shellcheck, quoting, POSIX Frameworks (1 file): - n8n.md (42 lines): workflow design, node patterns, Error Trigger, security Context budget: 975 lines (concerns + python) < 1500 limit Refs: T6-T16 of rules-system plan
3.5 KiB
Testing Rules
Arrange-Act-Assert Pattern
Structure every test in three distinct phases:
# Arrange: Set up the test data and conditions
user = User(name="Alice", role="admin")
session = create_test_session(user.id)
# Act: Execute the behavior under test
result = grant_permission(session, "read_documents")
# Assert: Verify the expected outcome
assert result.granted is True
assert result.permissions == ["read_documents"]
Never mix phases. Comment each phase clearly for complex setups. Keep Act phase to one line if possible.
Behavior vs Implementation Testing
Test behavior, not implementation details:
# GOOD: Tests the observable behavior
def test_user_can_login():
response = login("alice@example.com", "password123")
assert response.status_code == 200
assert "session_token" in response.cookies
# BAD: Tests internal implementation
def test_login_sets_database_flag():
login("alice@example.com", "password123")
user = User.get(email="alice@example.com")
assert user._logged_in_flag is True # Private field
Focus on inputs and outputs. Test public contracts. Refactor internals freely without breaking tests.
Mocking Philosophy
Mock external dependencies, not internal code:
# GOOD: Mock external services
@patch("requests.post")
def test_sends_notification_to_slack(mock_post):
send_notification("Build complete!")
mock_post.assert_called_once_with(
"https://slack.com/api/chat.postMessage",
json={"text": "Build complete!"}
)
# BAD: Mock internal methods
@patch("NotificationService._format_message")
def test_notification_formatting(mock_format):
# Don't mock private methods
send_notification("Build complete!")
Mock when:
- Dependency is slow (database, network, file system)
- Dependency is unreliable (external APIs)
- Dependency is expensive (third-party services)
Don't mock when:
- Testing the dependency itself
- The dependency is fast and stable
- The mock becomes more complex than real implementation
Coverage Expectations
Write tests for:
- Critical business logic (aim for 90%+)
- Edge cases and error paths (aim for 80%+)
- Public APIs and contracts (aim for 100%)
Don't obsess over:
- Trivial getters/setters
- Generated code
- One-line wrappers
Coverage is a floor, not a ceiling. A test suite at 100% coverage that doesn't verify behavior is worthless.
Test-Driven Development
Follow the red-green-refactor cycle:
- Red: Write failing test for new behavior
- Green: Write minimum code to pass
- Refactor: improve code while tests stay green
Write tests first for new features. Write tests after for bug fixes. Never refactor without tests.
Test Organization
Group tests by feature or behavior, not by file structure. Name tests to describe the scenario:
class TestUserAuthentication:
def test_valid_credentials_succeeds(self):
pass
def test_invalid_credentials_fails(self):
pass
def test_locked_account_fails(self):
pass
Each test should stand alone. Avoid shared state between tests. Use fixtures or setup methods to reduce duplication.
Test Data
Use realistic test data that reflects production scenarios:
# GOOD: Realistic values
user = User(
email="alice@example.com",
name="Alice Smith",
age=28
)
# BAD: Placeholder values
user = User(
email="test@test.com",
name="Test User",
age=999
)
Avoid magic strings and numbers. Use named constants for expected values that change often.