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
135 lines
3.5 KiB
Markdown
135 lines
3.5 KiB
Markdown
# Testing Rules
|
|
|
|
## Arrange-Act-Assert Pattern
|
|
|
|
Structure every test in three distinct phases:
|
|
|
|
```python
|
|
# 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:
|
|
|
|
```python
|
|
# 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:
|
|
|
|
```python
|
|
# 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:
|
|
1. Red: Write failing test for new behavior
|
|
2. Green: Write minimum code to pass
|
|
3. 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:
|
|
|
|
```python
|
|
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:
|
|
|
|
```python
|
|
# 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.
|