Unit testing standards following TDD methodology, test pyramid principles, and comprehensive coverage practices. Covers pytest, Jest, mocking, fixtures, and CI integration for reliable test suites.
Quick Navigation: Level 1: Quick Start (5 min) → Level 2: Implementation (30 min) → Level 3: Mastery (Extended)
test_<function>_<scenario>_<expected_result># pytest unit testing example
import pytest
from datetime import datetime
def calculate_discount(user_age: int, purchase_amount: float) -> float:
"""Calculate discount based on age and purchase amount."""
if user_age < 18:
return 0.0
elif user_age >= 65:
return purchase_amount * 0.15
elif purchase_amount >= 100:
return purchase_amount * 0.10
return 0.0
# Unit tests following TDD
def test_calculate_discount_no_discount_for_minors():
"""Test that users under 18 receive no discount."""
# Arrange
user_age = 16
purchase_amount = 100.0
# Act
discount = calculate_discount(user_age, purchase_amount)
# Assert
assert discount == 0.0
def test_calculate_discount_senior_discount():
"""Test that seniors (65+) receive 15% discount."""
assert calculate_discount(65, 100.0) == 15.0
assert calculate_discount(70, 200.0) == 30.0
def test_calculate_discount_large_purchase():
"""Test that purchases >= $100 receive 10% discount."""
assert calculate_discount(30, 100.0) == 10.0
assert calculate_discount(30, 150.0) == 15.0
@pytest.mark.parametrize("age,amount,expected", [
(16, 100, 0.0), # Minor
(30, 50, 0.0), # No discount
(30, 100, 10.0), # Large purchase
(65, 50, 7.5), # Senior
(70, 200, 30.0), # Senior large purchase
])
def test_calculate_discount_parametrized(age, amount, expected):
"""Test multiple discount scenarios."""
assert calculate_discount(age, amount) == expected
Red-Green-Refactor Cycle (see resources/tdd-workflow.md)
# Step 1: RED - Write failing test
def test_user_authentication_success():
"""Test successful user authentication."""
auth_service = AuthenticationService()
result = auth_service.authenticate('user@example.com', 'password123')
assert result.success is True
assert result.token is not None
# Step 2: GREEN - Write minimum code to pass
class AuthenticationService:
def authenticate(self, email: str, password: str):
# Minimal implementation
return AuthResult(success=True, token='dummy_token')
# Step 3: REFACTOR - Improve implementation
class AuthenticationService:
def __init__(self, user_repository, token_generator):
self.user_repo = user_repository
self.token_gen = token_generator
def authenticate(self, email: str, password: str):
user = self.user_repo.find_by_email(email)
if not user or not user.verify_password(password):
return AuthResult(success=False, error='Invalid credentials')
token = self.token_gen.generate(user.id)
return AuthResult(success=True, token=token)
Test Structure (see templates/test-template-pytest.py)
# tests/test_user_service.py
import pytest
from unittest.mock import Mock, MagicMock
from app.services.user_service import UserService
from app.models.user import User
@pytest.fixture
def mock_database():
"""Create mock database connection."""
db = Mock()
db.query = MagicMock(return_value=[])
return db
@pytest.fixture
def user_service(mock_database):
"""Create UserService with mocked dependencies."""
return UserService(database=mock_database)
class TestUserService:
"""Test suite for UserService."""
def test_create_user_success(self, user_service, mock_database):
"""Test successful user creation."""
# Arrange
user_data = {'email': 'test@example.com', 'name': 'Test User'}
mock_database.insert = MagicMock(return_value=1)
# Act
user_id = user_service.create_user(user_data)
# Assert
assert user_id == 1
mock_database.insert.assert_called_once()
def test_create_user_duplicate_email(self, user_service, mock_database):
"""Test user creation fails with duplicate email."""
# Arrange
user_data = {'email': 'existing@example.com', 'name': 'Test'}
mock_database.find_by_email = MagicMock(return_value=User(id=1))
# Act & Assert
with pytest.raises(DuplicateEmailError):
user_service.create_user(user_data)
Advanced Mocking (see templates/test-mocking-examples.py)
from unittest.mock import Mock, patch, MagicMock
import pytest
# Mock external API calls
@patch('requests.get')
def test_fetch_user_data(mock_get):
"""Test fetching user data from external API."""
# Arrange
mock_response = Mock()
mock_response.json.return_value = {'id': 1, 'name': 'John'}
mock_response.status_code = 200
mock_get.return_value = mock_response
# Act
service = ExternalAPIService()
user_data = service.fetch_user(1)
# Assert
assert user_data['name'] == 'John'
mock_get.assert_called_once_with('https://api.example.com/users/1')
# Mock database operations
@pytest.fixture
def mock_db_session():
"""Create mock database session."""
session = MagicMock()
session.query = MagicMock()
session.add = MagicMock()
session.commit = MagicMock()
return session
def test_save_user(mock_db_session):
"""Test saving user to database."""
repository = UserRepository(session=mock_db_session)
user = User(name='Test', email='test@example.com')
repository.save(user)
mock_db_session.add.assert_called_once_with(user)
mock_db_session.commit.assert_called_once()
Coverage Configuration (see resources/configs/pytest.ini)
[pytest]
testpaths = tests
python_files = test_*.py
python_classes = Test*
python_functions = test_*
addopts =
--verbose
--cov=src
--cov-report=term-missing
--cov-report=html
--cov-report=xml
--cov-fail-under=80
[coverage:run]
source = src
omit =
*/tests/*
*/venv/*
*/__pycache__/*
*/site-packages/*
[coverage:report]
exclude_lines =
pragma: no cover
def __repr__
raise AssertionError
raise NotImplementedError
if __name__ == .__main__.:
if TYPE_CHECKING:
Running Coverage
# Run tests with coverage
pytest --cov=src tests/
# Generate HTML report
pytest --cov=src --cov-report=html tests/
open htmlcov/index.html
# Coverage for specific module
pytest --cov=src.services.user_service tests/test_user_service.py
Test Naming and Organization
# ✅ Good: Descriptive test names
def test_calculate_total_with_discount_applies_10_percent_for_loyalty_members():
"""Test that loyalty members receive 10% discount on total."""
pass
# ❌ Bad: Vague test names
def test_calculate():
pass
# ✅ Good: Group related tests
class TestUserAuthentication:
def test_successful_login(self):
pass
def test_failed_login_invalid_password(self):
pass
def test_failed_login_nonexistent_user(self):
pass
# ✅ Good: Test one thing
def test_user_creation_generates_unique_id():
user = create_user('test@example.com')
assert isinstance(user.id, str)
assert len(user.id) == 36 # UUID length
# ❌ Bad: Testing multiple things
def test_user_creation():
user = create_user('test@example.com')
assert user.id is not None
assert user.email == 'test@example.com'
assert user.created_at is not None
assert user.is_active is True # Too many assertions
Parameterized Testing
@pytest.mark.parametrize("input_value,expected_output", [
(0, 0),
(1, 1),
(2, 4),
(3, 9),
(10, 100),
])
def test_square_function(input_value, expected_output):
"""Test square function with multiple inputs."""
assert square(input_value) == expected_output
@pytest.mark.parametrize("email", [
"invalid.email",
"@example.com",
"user@",
"user @example.com",
"",
])
def test_email_validation_rejects_invalid(email):
"""Test email validation rejects invalid formats."""
with pytest.raises(ValidationError):
validate_email(email)
Jest Configuration (see resources/configs/jest.config.js)
// jest.config.js
module.exports = {
testEnvironment: 'node',
coverageDirectory: 'coverage',
collectCoverageFrom: [
'src/**/*.{js,jsx}',
'!src/**/*.test.{js,jsx}',
'!src/index.js'
],
coverageThreshold: {
global: {
branches: 80,
functions: 80,
lines: 80,
statements: 80
}
},
testMatch: ['**/__tests__/**/*.js', '**/?(*.)+(spec|test).js']
};
Jest Testing Example (see templates/test-template-jest.js)
// src/calculator.test.js
const { add, subtract, divide } = require('./calculator');
describe('Calculator', () => {
describe('add', () => {
test('should add two positive numbers', () => {
expect(add(2, 3)).toBe(5);
});
test('should add negative numbers', () => {
expect(add(-2, -3)).toBe(-5);
});
});
describe('divide', () => {
test('should divide two numbers', () => {
expect(divide(10, 2)).toBe(5);
});
test('should throw error when dividing by zero', () => {
expect(() => divide(10, 0)).toThrow('Division by zero');
});
});
});
// Mock testing
jest.mock('./apiService');
const apiService = require('./apiService');
test('fetches user data successfully', async () => {
const mockUser = { id: 1, name: 'John' };
apiService.getUser.mockResolvedValue(mockUser);
const user = await fetchUser(1);
expect(user).toEqual(mockUser);
expect(apiService.getUser).toHaveBeenCalledWith(1);
});
Go Test Template (see templates/test-template-go.go)
// calculator_test.go
package calculator
import (
"testing"
)
func TestAdd(t *testing.T) {
tests := []struct {
name string
a, b int
expected int
}{
{"positive numbers", 2, 3, 5},
{"negative numbers", -2, -3, -5},
{"zero", 0, 0, 0},
}
for _, tt := range tests {
t.Run(tt.name, func(t *testing.T) {
result := Add(tt.a, tt.b)
if result != tt.expected {
t.Errorf("Add(%d, %d) = %d; want %d",
tt.a, tt.b, result, tt.expected)
}
})
}
}
func TestDivide(t *testing.T) {
result, err := Divide(10, 2)
if err != nil {
t.Fatalf("unexpected error: %v", err)
}
if result != 5 {
t.Errorf("got %d, want 5", result)
}
}
func TestDivideByZero(t *testing.T) {
_, err := Divide(10, 0)
if err == nil {
t.Error("expected error for division by zero")
}
}
# pytest
pytest # Run all tests
pytest tests/test_user.py # Run specific file
pytest -k "test_auth" # Run tests matching pattern
pytest --cov=src --cov-report=html # Coverage report
pytest -v -s # Verbose with stdout
pytest --tb=short # Short traceback
# Jest
npm test # Run all tests
npm test -- --coverage # With coverage
npm test -- --watch # Watch mode
npm test -- user.test.js # Specific file
# Go
go test ./... # All packages
go test -v # Verbose
go test -cover # Coverage
go test -bench=. # Benchmarks
// TODO: Add basic example for unit-testing
// This example demonstrates core functionality
// TODO: Add advanced example for unit-testing
// This example shows production-ready patterns
// TODO: Add integration example showing how unit-testing
// works with other systems and services
See examples/unit-testing/ for complete working examples.
This skill integrates with:
Problem: Not testing edge cases and error conditions leads to production bugs
Solution: Implement comprehensive test coverage including:
Prevention: Enforce minimum code coverage (80%+) in CI/CD pipeline
Problem: Hardcoding values makes applications inflexible and environment-dependent
Solution: Use environment variables and configuration management:
Prevention: Use tools like dotenv, config validators, and secret scanners
Problem: Security vulnerabilities from not following established security patterns
Solution: Follow security guidelines:
Prevention: Use security linters, SAST tools, and regular dependency updates
Best Practices:
npx skills add williamzujkowski/unit-testing下载完整 Skill 目录,包含 SKILL.md 及所有相关文件
Search for places (restaurants, cafes, etc.) via Google Places API proxy on localhost.
Interact with GitHub using the `gh` CLI. Use `gh issue`, `gh pr`, `gh run`, and `gh api` for issues, PRs, CI runs, and advanced queries.
Create or update AgentSkills. Use when designing, structuring, or packaging skills with scripts, references, and assets.
Start voice calls via the OpenClaw voice-call plugin.
Notion API for creating and managing pages, databases, and blocks.
Gemini CLI for one-shot Q&A, summaries, and generation.
Category:developer