feat(rules): add initial rule files for concerns, languages, and frameworks
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
This commit is contained in:
163
rules/concerns/coding-style.md
Normal file
163
rules/concerns/coding-style.md
Normal file
@@ -0,0 +1,163 @@
|
||||
# Coding Style
|
||||
|
||||
## Critical Rules (MUST follow)
|
||||
|
||||
Always prioritize readability over cleverness. Never write code that requires mental gymnastics to understand.
|
||||
Always fail fast and explicitly. Never silently swallow errors or hide exceptions.
|
||||
Always keep functions under 20 lines. Never create monolithic functions that do multiple things.
|
||||
Always validate inputs at function boundaries. Never trust external data implicitly.
|
||||
|
||||
## Formatting
|
||||
|
||||
Prefer consistent indentation throughout the codebase. Never mix tabs and spaces.
|
||||
Prefer meaningful variable names over short abbreviations. Never use single letters except for loop counters.
|
||||
|
||||
### Correct:
|
||||
```lang
|
||||
const maxRetryAttempts = 3;
|
||||
const connectionTimeout = 5000;
|
||||
|
||||
for (let attempt = 1; attempt <= maxRetryAttempts; attempt++) {
|
||||
// process attempt
|
||||
}
|
||||
```
|
||||
|
||||
### Incorrect:
|
||||
```lang
|
||||
const m = 3;
|
||||
const t = 5000;
|
||||
|
||||
for (let i = 1; i <= m; i++) {
|
||||
// process attempt
|
||||
}
|
||||
```
|
||||
|
||||
## Patterns and Anti-Patterns
|
||||
|
||||
Never repeat yourself. Always extract duplicated logic into reusable functions.
|
||||
Prefer composition over inheritance. Never create deep inheritance hierarchies.
|
||||
Always use guard clauses to reduce nesting. Never write arrow-shaped code.
|
||||
|
||||
### Correct:
|
||||
```lang
|
||||
def process_user(user):
|
||||
if not user:
|
||||
return None
|
||||
if not user.is_active:
|
||||
return None
|
||||
return user.calculate_score()
|
||||
```
|
||||
|
||||
### Incorrect:
|
||||
```lang
|
||||
def process_user(user):
|
||||
if user:
|
||||
if user.is_active:
|
||||
return user.calculate_score()
|
||||
else:
|
||||
return None
|
||||
else:
|
||||
return None
|
||||
```
|
||||
|
||||
## Error Handling
|
||||
|
||||
Always handle specific exceptions. Never use broad catch-all exception handlers.
|
||||
Always log error context, not just the error message. Never let errors vanish without trace.
|
||||
|
||||
### Correct:
|
||||
```lang
|
||||
try:
|
||||
data = fetch_resource(url)
|
||||
return parse_data(data)
|
||||
except NetworkError as e:
|
||||
log_error(f"Network failed for {url}: {e}")
|
||||
raise
|
||||
except ParseError as e:
|
||||
log_error(f"Parse failed for {url}: {e}")
|
||||
return fallback_data
|
||||
```
|
||||
|
||||
### Incorrect:
|
||||
```lang
|
||||
try:
|
||||
data = fetch_resource(url)
|
||||
return parse_data(data)
|
||||
except Exception:
|
||||
pass
|
||||
```
|
||||
|
||||
## Type Safety
|
||||
|
||||
Always use type annotations where supported. Never rely on implicit type coercion.
|
||||
Prefer explicit type checks over duck typing for public APIs. Never assume type behavior.
|
||||
|
||||
### Correct:
|
||||
```lang
|
||||
function calculateTotal(price: number, quantity: number): number {
|
||||
return price * quantity;
|
||||
}
|
||||
```
|
||||
|
||||
### Incorrect:
|
||||
```lang
|
||||
function calculateTotal(price, quantity) {
|
||||
return price * quantity;
|
||||
}
|
||||
```
|
||||
|
||||
## Function Design
|
||||
|
||||
Always write pure functions when possible. Never mutate arguments unless required.
|
||||
Always limit function parameters to 3 or fewer. Never pass objects to hide parameter complexity.
|
||||
|
||||
### Correct:
|
||||
```lang
|
||||
def create_user(name: str, email: str) -> User:
|
||||
return User(name=name, email=email, created_at=now())
|
||||
```
|
||||
|
||||
### Incorrect:
|
||||
```lang
|
||||
def create_user(config: dict) -> User:
|
||||
return User(
|
||||
name=config['name'],
|
||||
email=config['email'],
|
||||
created_at=config['timestamp']
|
||||
)
|
||||
```
|
||||
|
||||
## SOLID Principles
|
||||
|
||||
Never let classes depend on concrete implementations. Always depend on abstractions.
|
||||
Always ensure classes are open for extension but closed for modification. Never change working code to add features.
|
||||
Prefer many small interfaces over one large interface. Never force clients to depend on methods they don't use.
|
||||
|
||||
### Correct:
|
||||
```lang
|
||||
class EmailSender {
|
||||
send(message: Message): void {
|
||||
// implementation
|
||||
}
|
||||
}
|
||||
|
||||
class NotificationService {
|
||||
constructor(private sender: EmailSender) {}
|
||||
}
|
||||
```
|
||||
|
||||
### Incorrect:
|
||||
```lang
|
||||
class NotificationService {
|
||||
sendEmail(message: Message): void { }
|
||||
sendSMS(message: Message): void { }
|
||||
sendPush(message: Message): void { }
|
||||
}
|
||||
```
|
||||
|
||||
## Critical Rules (REPEAT)
|
||||
|
||||
Always write self-documenting code. Never rely on comments to explain complex logic.
|
||||
Always refactor when you see code smells. Never let technical debt accumulate.
|
||||
Always test edge cases explicitly. Never assume happy path only behavior.
|
||||
Never commit commented-out code. Always remove it or restore it.
|
||||
149
rules/concerns/documentation.md
Normal file
149
rules/concerns/documentation.md
Normal file
@@ -0,0 +1,149 @@
|
||||
# Documentation Rules
|
||||
|
||||
## When to Document
|
||||
|
||||
**Document public APIs**. Every public function, class, method, and module needs documentation. Users need to know how to use your code.
|
||||
**Document complex logic**. Algorithms, state machines, and non-obvious implementations need explanations. Future readers will thank you.
|
||||
**Document business rules**. Encode domain knowledge directly in comments. Don't make anyone reverse-engineer requirements from code.
|
||||
**Document trade-offs**. When you choose between alternatives, explain why. Help future maintainers understand the decision context.
|
||||
**Do NOT document obvious code**. Comments like `// get user` add noise. Delete them.
|
||||
|
||||
## Docstring Formats
|
||||
|
||||
### Python (Google Style)
|
||||
|
||||
```python
|
||||
def calculate_price(quantity: int, unit_price: float, discount: float = 0.0) -> float:
|
||||
"""Calculate total price after discount.
|
||||
Args:
|
||||
quantity: Number of items ordered.
|
||||
unit_price: Price per item in USD.
|
||||
discount: Decimal discount rate (0.0 to 1.0).
|
||||
Returns:
|
||||
Final price in USD.
|
||||
Raises:
|
||||
ValueError: If quantity is negative.
|
||||
"""
|
||||
```
|
||||
|
||||
### JavaScript/TypeScript (JSDoc)
|
||||
|
||||
```javascript
|
||||
/**
|
||||
* Validates user input against security rules.
|
||||
* @param {string} input - Raw user input from form.
|
||||
* @param {Object} rules - Validation constraints.
|
||||
* @param {number} rules.maxLength - Maximum allowed length.
|
||||
* @returns {boolean} True if input passes all rules.
|
||||
* @throws {ValidationError} If input violates security constraints.
|
||||
*/
|
||||
function validateInput(input, rules) {
|
||||
```
|
||||
|
||||
### Bash
|
||||
|
||||
```bash
|
||||
#!/usr/bin/env bash
|
||||
# Deploy application to production environment.
|
||||
#
|
||||
# Usage: ./deploy.sh [environment]
|
||||
#
|
||||
# Args:
|
||||
# environment: Target environment (staging|production). Default: staging.
|
||||
#
|
||||
# Exits:
|
||||
# 0 on success, 1 on deployment failure.
|
||||
```
|
||||
|
||||
## Inline Comments: WHY Not WHAT
|
||||
|
||||
**Incorrect:**
|
||||
```python
|
||||
# Iterate through all users
|
||||
for user in users:
|
||||
# Check if user is active
|
||||
if user.active:
|
||||
# Increment counter
|
||||
count += 1
|
||||
```
|
||||
|
||||
**Correct:**
|
||||
```python
|
||||
# Count only active users to calculate monthly revenue
|
||||
for user in users:
|
||||
if user.active:
|
||||
count += 1
|
||||
```
|
||||
|
||||
**Incorrect:**
|
||||
```javascript
|
||||
// Set timeout to 5000
|
||||
setTimeout(() => {
|
||||
// Show error message
|
||||
alert('Error');
|
||||
}, 5000);
|
||||
```
|
||||
|
||||
**Correct:**
|
||||
```javascript
|
||||
// 5000ms delay prevents duplicate alerts during rapid retries
|
||||
setTimeout(() => {
|
||||
alert('Error');
|
||||
}, 5000);
|
||||
```
|
||||
|
||||
**Incorrect:**
|
||||
```bash
|
||||
# Remove temporary files
|
||||
rm -rf /tmp/app/*
|
||||
```
|
||||
|
||||
**Correct:**
|
||||
```bash
|
||||
# Clear temp directory before batch import to prevent partial state
|
||||
rm -rf /tmp/app/*
|
||||
```
|
||||
|
||||
**Rule:** Describe the intent and context. Never describe what the code obviously does.
|
||||
|
||||
## README Standards
|
||||
|
||||
Every project needs a README at the top level.
|
||||
|
||||
**Required sections:**
|
||||
1. **What it does** - One sentence summary
|
||||
2. **Installation** - Setup commands
|
||||
3. **Usage** - Basic example
|
||||
4. **Configuration** - Environment variables and settings
|
||||
5. **Contributing** - How to contribute
|
||||
|
||||
**Example structure:**
|
||||
|
||||
```markdown
|
||||
# Project Name
|
||||
|
||||
One-line description of what this project does.
|
||||
|
||||
## Installation
|
||||
```bash
|
||||
npm install
|
||||
```
|
||||
|
||||
## Usage
|
||||
```bash
|
||||
npm start
|
||||
```
|
||||
|
||||
## Configuration
|
||||
|
||||
Create `.env` file:
|
||||
```
|
||||
API_KEY=your_key_here
|
||||
```
|
||||
|
||||
## Contributing
|
||||
|
||||
See [CONTRIBUTING.md](./CONTRIBUTING.md).
|
||||
```
|
||||
|
||||
**Keep READMEs focused**. Link to separate docs for complex topics. Don't make the README a tutorial.
|
||||
118
rules/concerns/git-workflow.md
Normal file
118
rules/concerns/git-workflow.md
Normal file
@@ -0,0 +1,118 @@
|
||||
# Git Workflow Rules
|
||||
|
||||
## Conventional Commits
|
||||
|
||||
Format: `<type>(<scope>): <subject>`
|
||||
|
||||
### Commit Types
|
||||
|
||||
- **feat**: New feature
|
||||
- `feat(auth): add OAuth2 login flow`
|
||||
- `feat(api): expose user endpoints`
|
||||
|
||||
- **fix**: Bug fix
|
||||
- `fix(payment): resolve timeout on Stripe calls`
|
||||
- `fix(ui): button not clickable on mobile`
|
||||
|
||||
- **refactor**: Code refactoring (no behavior change)
|
||||
- `refactor(utils): extract date helpers`
|
||||
- `refactor(api): simplify error handling`
|
||||
|
||||
- **docs**: Documentation only
|
||||
- `docs(readme): update installation steps`
|
||||
- `docs(api): add endpoint examples`
|
||||
|
||||
- **chore**: Maintenance tasks
|
||||
- `chore(deps): update Node to 20`
|
||||
- `chore(ci): add GitHub actions workflow`
|
||||
|
||||
- **test**: Tests only
|
||||
- `test(auth): add unit tests for login`
|
||||
- `test(e2e): add checkout flow tests`
|
||||
|
||||
- **style**: Formatting, no logic change
|
||||
- `style: sort imports alphabetically`
|
||||
|
||||
### Commit Rules
|
||||
|
||||
- Subject max 72 chars
|
||||
- Imperative mood ("add", not "added")
|
||||
- No period at end
|
||||
- Reference issues: `Closes #123`
|
||||
|
||||
## Branch Naming
|
||||
|
||||
Pattern: `<type>/<short-description>`
|
||||
|
||||
### Branch Types
|
||||
|
||||
- `feature/add-user-dashboard`
|
||||
- `feature/enable-dark-mode`
|
||||
- `fix/login-redirect-loop`
|
||||
- `fix/payment-timeout-error`
|
||||
- `refactor/extract-user-service`
|
||||
- `refactor/simplify-auth-flow`
|
||||
- `hotfix/security-vulnerability`
|
||||
|
||||
### Branch Rules
|
||||
|
||||
- Lowercase and hyphens
|
||||
- Max 50 chars
|
||||
- Delete after merge
|
||||
|
||||
## Pull Requests
|
||||
|
||||
### PR Title
|
||||
|
||||
Follow Conventional Commit format:
|
||||
- `feat: add user dashboard`
|
||||
- `fix: resolve login redirect loop`
|
||||
|
||||
### PR Description
|
||||
|
||||
```markdown
|
||||
## What
|
||||
Brief description
|
||||
|
||||
## Why
|
||||
Reason for change
|
||||
|
||||
## How
|
||||
Implementation approach
|
||||
|
||||
## Testing
|
||||
Steps performed
|
||||
|
||||
## Checklist
|
||||
- [ ] Tests pass
|
||||
- [ ] Code reviewed
|
||||
- [ ] Docs updated
|
||||
```
|
||||
|
||||
## Merge Strategy
|
||||
|
||||
### Squash Merge
|
||||
|
||||
- Many small commits
|
||||
- One cohesive feature
|
||||
- Clean history
|
||||
|
||||
### Merge Commit
|
||||
|
||||
- Preserve commit history
|
||||
- Distinct milestones
|
||||
- Detailed history preferred
|
||||
|
||||
### When to Rebase
|
||||
|
||||
- Before opening PR
|
||||
- Resolving conflicts
|
||||
- Keeping current with main
|
||||
|
||||
## General Rules
|
||||
|
||||
- Pull latest from main before starting
|
||||
- Write atomic commits
|
||||
- Run tests before pushing
|
||||
- Request peer review before merge
|
||||
- Never force push to main/master
|
||||
105
rules/concerns/naming.md
Normal file
105
rules/concerns/naming.md
Normal file
@@ -0,0 +1,105 @@
|
||||
# Naming Conventions
|
||||
|
||||
Use consistent naming across all code. Follow language-specific conventions.
|
||||
|
||||
## Language Reference
|
||||
|
||||
| Type | Python | TypeScript | Nix | Shell |
|
||||
|------|--------|------------|-----|-------|
|
||||
| Variables | snake_case | camelCase | camelCase | UPPER_SNAKE |
|
||||
| Functions | snake_case | camelCase | camelCase | lower_case |
|
||||
| Classes | PascalCase | PascalCase | - | - |
|
||||
| Constants | UPPER_SNAKE | UPPER_SNAKE | camelCase | UPPER_SNAKE |
|
||||
| Files | snake_case | camelCase | hyphen-case | hyphen-case |
|
||||
| Modules | snake_case | camelCase | - | - |
|
||||
|
||||
## General Rules
|
||||
|
||||
**Files**: Use hyphen-case for documentation, snake_case for Python, camelCase for TypeScript. Names should describe content.
|
||||
|
||||
**Variables**: Use descriptive names. Avoid single letters except loop counters. No Hungarian notation.
|
||||
|
||||
**Functions**: Use verb-noun pattern. Name describes what it does, not how it does it.
|
||||
|
||||
**Classes**: Use PascalCase with descriptive nouns. Avoid abbreviations.
|
||||
|
||||
**Constants**: Use UPPER_SNAKE with descriptive names. Group related constants.
|
||||
|
||||
## Examples
|
||||
|
||||
Python:
|
||||
```python
|
||||
# Variables
|
||||
user_name = "alice"
|
||||
is_authenticated = True
|
||||
|
||||
# Functions
|
||||
def get_user_data(user_id):
|
||||
pass
|
||||
|
||||
# Classes
|
||||
class UserProfile:
|
||||
pass
|
||||
|
||||
# Constants
|
||||
MAX_RETRIES = 3
|
||||
API_ENDPOINT = "https://api.example.com"
|
||||
```
|
||||
|
||||
TypeScript:
|
||||
```typescript
|
||||
// Variables
|
||||
const userName = "alice";
|
||||
const isAuthenticated = true;
|
||||
|
||||
// Functions
|
||||
function getUserData(userId: string): User {
|
||||
return null;
|
||||
}
|
||||
|
||||
// Classes
|
||||
class UserProfile {
|
||||
private name: string;
|
||||
}
|
||||
|
||||
// Constants
|
||||
const MAX_RETRIES = 3;
|
||||
const API_ENDPOINT = "https://api.example.com";
|
||||
```
|
||||
|
||||
Nix:
|
||||
```nix
|
||||
# Variables
|
||||
let
|
||||
userName = "alice";
|
||||
isAuthenticated = true;
|
||||
in
|
||||
# ...
|
||||
```
|
||||
|
||||
Shell:
|
||||
```bash
|
||||
# Variables
|
||||
USER_NAME="alice"
|
||||
IS_AUTHENTICATED=true
|
||||
|
||||
# Functions
|
||||
get_user_data() {
|
||||
echo "Getting data"
|
||||
}
|
||||
|
||||
# Constants
|
||||
MAX_RETRIES=3
|
||||
API_ENDPOINT="https://api.example.com"
|
||||
```
|
||||
|
||||
## File Naming
|
||||
|
||||
Use these patterns consistently. No exceptions.
|
||||
|
||||
- Skills: `hyphen-case`
|
||||
- Python: `snake_case.py`
|
||||
- TypeScript: `camelCase.ts` or `hyphen-case.ts`
|
||||
- Nix: `hyphen-case.nix`
|
||||
- Shell: `hyphen-case.sh`
|
||||
- Markdown: `UPPERCASE.md` or `sentence-case.md`
|
||||
82
rules/concerns/project-structure.md
Normal file
82
rules/concerns/project-structure.md
Normal file
@@ -0,0 +1,82 @@
|
||||
# Project Structure
|
||||
|
||||
## Python
|
||||
|
||||
Use src layout for all projects. Place application code in `src/<project>/`, tests in `tests/`.
|
||||
|
||||
```
|
||||
project/
|
||||
├── src/myproject/
|
||||
│ ├── __init__.py
|
||||
│ ├── main.py # Entry point
|
||||
│ └── core/
|
||||
│ └── module.py
|
||||
├── tests/
|
||||
│ ├── __init__.py
|
||||
│ └── test_module.py
|
||||
├── pyproject.toml # Config
|
||||
├── README.md
|
||||
└── .gitignore
|
||||
```
|
||||
|
||||
**Rules:**
|
||||
- One module per directory file
|
||||
- `__init__.py` in every package
|
||||
- Entry point in `src/myproject/main.py`
|
||||
- Config in root: `pyproject.toml`, `requirements.txt`
|
||||
|
||||
## TypeScript
|
||||
|
||||
Use `src/` for source, `dist/` for build output.
|
||||
|
||||
```
|
||||
project/
|
||||
├── src/
|
||||
│ ├── index.ts # Entry point
|
||||
│ ├── core/
|
||||
│ │ └── module.ts
|
||||
│ └── types.ts
|
||||
├── tests/
|
||||
│ └── module.test.ts
|
||||
├── package.json # Config
|
||||
├── tsconfig.json
|
||||
└── README.md
|
||||
```
|
||||
|
||||
**Rules:**
|
||||
- One module per file
|
||||
- Index exports from `src/index.ts`
|
||||
- Entry point in `src/index.ts`
|
||||
- Config in root: `package.json`, `tsconfig.json`
|
||||
|
||||
## Nix
|
||||
|
||||
Use `modules/` for NixOS modules, `pkgs/` for packages.
|
||||
|
||||
```
|
||||
nix-config/
|
||||
├── modules/
|
||||
│ ├── default.nix # Module list
|
||||
│ └── my-service.nix
|
||||
├── pkgs/
|
||||
│ └── my-package/
|
||||
│ └── default.nix
|
||||
├── flake.nix # Entry point
|
||||
├── flake.lock
|
||||
└── README.md
|
||||
```
|
||||
|
||||
**Rules:**
|
||||
- One module per file in `modules/`
|
||||
- One package per directory in `pkgs/`
|
||||
- Entry point in `flake.nix`
|
||||
- Config in root: `flake.nix`, shell.nix
|
||||
|
||||
## General
|
||||
|
||||
- Use hyphen-case for directories
|
||||
- Use kebab-case for file names
|
||||
- Config files in project root
|
||||
- Tests separate from source
|
||||
- Docs in root: README.md, CHANGELOG.md
|
||||
- Hidden configs: .env, .gitignore
|
||||
134
rules/concerns/testing.md
Normal file
134
rules/concerns/testing.md
Normal file
@@ -0,0 +1,134 @@
|
||||
# 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.
|
||||
Reference in New Issue
Block a user