Awesome-omni-skill testing-services
Writes unit tests for Python service classes using Arrange-Act-Assert pattern with proper mocking at boundaries. Tests behavior, not implementation. Mocks external systems only (API calls, file I/O, databases). Use when writing tests for services or fixing test coverage.
git clone https://github.com/diegosouzapw/awesome-omni-skill
T=$(mktemp -d) && git clone --depth=1 https://github.com/diegosouzapw/awesome-omni-skill "$T" && mkdir -p ~/.claude/skills && cp -r "$T/skills/testing-security/testing-services" ~/.claude/skills/diegosouzapw-awesome-omni-skill-testing-services && rm -rf "$T"
skills/testing-security/testing-services/SKILL.mdTesting Services
When to Use This Skill
Activate this skill when:
- Writing unit tests for service classes
- Need to improve test coverage for services
- Unclear what to mock vs what to test directly
- Writing integration tests that test service interactions
Quick Start
Running Tests Locally
# Run all unit tests pytest tests/unit/ -v # Run all integration tests pytest tests/integration/ -v # Run both unit and integration tests pytest tests/unit/ tests/integration/ -v # Run with coverage report pytest tests/unit/ tests/integration/ --cov=src --cov-report=term-missing --cov-report=html # Run tests for a specific module pytest tests/unit/services/test_entity_service.py -v # Run a specific test pytest tests/unit/services/test_entity_service.py::TestEntityService::test_create_entity_success -v
Understanding Test Results
# View coverage in terminal pytest tests/ --cov=src --cov-report=term-missing # View detailed HTML coverage report (opens in browser) pytest tests/ --cov=src --cov-report=html open htmlcov/index.html # macOS xdg-open htmlcov/index.html # Linux
Quick Reference
AAA Pattern
def test_service_operation(): # Arrange: Set up dependencies and test data mock_dependency = Mock(spec=Dependency) mock_dependency.method.return_value = "expected" service = ServiceClass(mock_dependency) # Act: Execute the operation under test result = service.operation(param="value") # Assert: Verify behavior and outcomes assert result == "expected_outcome" mock_dependency.method.assert_called_once_with("value")
Running Tests
pytest tests/ -v # Run all tests pytest tests/unit/test_service.py -v # Run specific file pytest tests/ --cov=src --cov-report=term-missing # With coverage pytest tests/ -k "test_create" -v # Pattern matching
Coverage Guidelines
- Aim for 85%+ overall coverage
- Focus on testing behavior, not hitting targets
- Missing coverage acceptable for edge cases or trivial code
Test Isolation and Independence
Every test should be completely independent. Tests must not:
- Rely on execution order
- Share mutable state
- Depend on previous test results
- Affect other tests when run in parallel
Example
# ✅ GOOD - Each test is self-contained class TestTaskManagement: def test_find_first_task(self, tmp_path): """Should find the first unchecked task""" # Arrange - Create fresh test data for this test only spec_file = tmp_path / "spec.md" spec_file.write_text(""" - [ ] Task 1 - [ ] Task 2 """) # Act result = find_next_available_task(str(spec_file)) # Assert assert result == (1, "Task 1") # ❌ BAD - Tests depend on shared state shared_state = {"count": 0} def test_increment(): shared_state["count"] += 1 assert shared_state["count"] == 1 # Breaks if tests run out of order
Mock at Boundaries Rule
The Golden Rule: Mock external systems only, never internal logic.
What to Mock ✅
External systems and infrastructure:
- HTTP API clients, database connections, repositories
- File system operations, subprocess calls
- External service clients (email, payment, etc.)
Service dependencies:
- Other services injected via constructor (enables isolation testing)
What NOT to Mock ❌
Internal logic:
- Domain models and dataclasses (see domain-modeling skill for understanding what domain models are)
- Pure functions, helper methods, static methods
- Business logic calculations
- Exceptions and error classes
Standard library basics:
- datetime (use fixed times), string/list/dict operations
Decision Guide
Ask: "Is this an external dependency or internal logic?"
- External dependency → Mock it
- Internal logic → Test it directly
Service Test Templates
Testing a Core Service
from unittest.mock import Mock import pytest from src.services.entity_service import EntityService from src.domain.entity import Entity from src.domain.exceptions import ValidationError class TestEntityService: """Tests for EntityService.""" def test_create_entity_success(self): # Arrange: Mock external dependencies mock_repository = Mock() mock_validator = Mock() mock_validator.is_valid.return_value = True service = EntityService(mock_repository, mock_validator) # Act result = service.create_entity(name="Test Entity", value=42) # Assert: Verify behavior assert result.name == "Test Entity" assert result.value == 42 mock_validator.is_valid.assert_called_once() mock_repository.save.assert_called_once() def test_create_entity_validation_failure(self): # Arrange mock_repository = Mock() mock_validator = Mock() mock_validator.is_valid.return_value = False service = EntityService(mock_repository, mock_validator) # Act & Assert with pytest.raises(ValidationError, match="Invalid entity data"): service.create_entity(name="Bad", value=-1) mock_repository.save.assert_not_called() def test_find_by_criteria_filters_correctly(self): # Arrange: Use real Entity objects (don't mock domain models!) mock_repository = Mock() entities = [ Entity(name="Low", value=10), Entity(name="High", value=100), ] mock_repository.list_all.return_value = entities service = EntityService(mock_repository, Mock()) # Act result = service.find_by_criteria(min_value=50) # Assert assert len(result) == 1 assert result[0].name == "High"
Testing a Composite Service
from unittest.mock import Mock import pytest from src.services.workflow_service import WorkflowService from src.domain.exceptions import WorkflowError, ValidationError class TestWorkflowService: """Tests for WorkflowService.""" def test_execute_workflow_success(self): # Arrange: Mock all Core service dependencies mock_entity_service = Mock() mock_notification_service = Mock() mock_audit_service = Mock() mock_entity = Mock(id="entity-123") mock_entity_service.create_entity.return_value = mock_entity service = WorkflowService( mock_entity_service, mock_notification_service, mock_audit_service ) # Act result = service.execute_workflow(name="Test", value=100, user_id="user-456") # Assert: Verify orchestration assert result.success is True assert result.entity_id == "entity-123" mock_entity_service.create_entity.assert_called_once_with("Test", 100) mock_notification_service.notify_entity_created.assert_called_once() mock_audit_service.log_success.assert_called_once() def test_execute_workflow_handles_failure(self): # Arrange mock_entity_service = Mock() mock_entity_service.create_entity.side_effect = ValidationError("Invalid") mock_notification_service = Mock() mock_audit_service = Mock() service = WorkflowService( mock_entity_service, mock_notification_service, mock_audit_service ) # Act & Assert with pytest.raises(WorkflowError, match="Entity creation failed"): service.execute_workflow(name="Bad", value=-1, user_id="user-456") mock_audit_service.log_failure.assert_called_once() mock_notification_service.notify_entity_created.assert_not_called() def test_calculate_priority_static_method(self): # Static methods are pure - test directly, no mocking needed result = WorkflowService.calculate_priority(value=200, user_tier="premium") assert result == 4 result = WorkflowService.calculate_priority(value=150, user_tier="standard") assert result == 1
Layer-Based Testing Strategy
Python applications often follow a layered architecture, and we test each layer differently:
┌─────────────────────────────────────────┐ │ CLI Layer │ ← Test command orchestration │ (commands: prepare, finalize, etc.) │ Mock everything below ├─────────────────────────────────────────┤ │ Service Layer │ ← Test business logic │ (services: task mgmt, reviewer mgmt) │ Mock infrastructure ├─────────────────────────────────────────┤ │ Infrastructure Layer │ ← Test external integrations │ (git, github, filesystem) │ Mock external systems ├─────────────────────────────────────────┤ │ Domain Layer │ ← Test directly │ (models, config, exceptions) │ Minimal/no mocking └─────────────────────────────────────────┘
Key insight: The lower the layer, the less you mock. Domain models are pure logic and need minimal mocking. CLI commands orchestrate everything and need maximum mocking.
Domain Layer (99% coverage target)
Testing approach:
- ✅ Direct testing - Minimal mocking
- ✅ Test business rules - Validation logic, data transformations
- ✅ Test edge cases - Invalid inputs, boundary conditions
What to mock: Almost nothing. Domain models are pure logic.
Example:
class TestMarkdownFormatter: """Test suite for MarkdownFormatter functionality""" def test_bold_formatting_for_github(self): """Should format text as bold using GitHub markdown syntax""" # Arrange - No mocking needed formatter = MarkdownFormatter(for_slack=False) # Act result = formatter.bold("important") # Assert assert result == "**important**"
Infrastructure Layer (97% coverage target)
Testing approach:
- ✅ Mock external systems - subprocess, HTTP, file I/O
- ✅ Test success and error paths - What happens on success? On failure?
- ✅ Verify system calls - Check correct commands are executed
What to mock: Everything external to Python (subprocess, network, filesystem)
Service Layer (95% coverage target)
Testing approach:
- ✅ Mock infrastructure - Mock git, GitHub API, filesystem
- ✅ Test business logic - Algorithms, data processing, validation
- ✅ Test service instantiation - Verify services are created with correct dependencies
- ✅ Test edge cases - No reviewers available, all tasks complete, etc.
What to mock: Infrastructure layer (subprocess, GitHub API, file I/O) and service dependencies
CLI Integration Tests (98% coverage target)
Testing approach:
- ✅ Mock service layer - Mock service classes and their methods
- ✅ Mock infrastructure - Mock git, GitHub API, filesystem operations
- ✅ Test command orchestration - Verify correct sequence of service calls
- ✅ Test service instantiation - Verify services are created with correct dependencies
- ✅ Test output - Verify CLI outputs are formatted correctly
What to mock: Service Layer classes and Infrastructure dependencies
Key Testing Patterns
Pattern: Use Real Domain Models
def test_process_order(): mock_repository = Mock() service = OrderService(mock_repository) # Use real Order domain model, not a mock result = service.process_order(customer_id="c123", items=["item1"], total=99.99) assert isinstance(result, Order) assert result.customer_id == "c123" assert result.total == 99.99
Pattern: Test Exception Handling
def test_find_user_not_found(): mock_repository = Mock() mock_repository.get_by_id.return_value = None service = UserService(mock_repository) with pytest.raises(UserNotFoundError, match="User u999 not found"): service.find_user("u999")
Pattern: Verify Side Effects
def test_send_notification(): mock_email_client = Mock() service = NotificationService(mock_email_client) service.send_notification(user_email="user@example.com", message="Hello!") mock_email_client.send.assert_called_once_with( to="user@example.com", subject="Notification", body="Hello!" )
Pattern: Parametrized Tests
import pytest @pytest.mark.parametrize("value,tier,expected", [ (100, "premium", 2), (100, "standard", 1), (200, "premium", 4), ]) def test_calculate_priority(value, tier, expected): result = WorkflowService.calculate_priority(value, tier) assert result == expected
Mock vs MagicMock vs Real Objects
Use Mock(spec=ClassName)
Mock(spec=ClassName)For most scenarios - provides autocomplete and catches attribute errors.
mock_repository = Mock(spec=UserRepository) mock_repository.find_by_id.return_value = User(id="123")
Use MagicMock
MagicMockWhen you need magic methods (
__len__, __iter__, __getitem__).
from unittest.mock import MagicMock mock_collection = MagicMock() mock_collection.__len__.return_value = 5
Use Real Objects
For domain models, value objects, exceptions.
user = User(id="123", name="Alice") # Real domain model order = Order(items=[Item("book")]) # Real value objects mock_service.process.side_effect = ValidationError("Bad data") # Real exception
Fixtures for Reusable Setup
import pytest from unittest.mock import Mock @pytest.fixture def mock_repository(): return Mock(spec=UserRepository) @pytest.fixture def user_service(mock_repository): return UserService(mock_repository) def test_create_user(user_service, mock_repository): mock_repository.save.return_value = User(id="123") result = user_service.create_user(name="Alice") assert result.id == "123"
Integration Tests
Integration tests verify services work together - only mock infrastructure.
def test_workflow_integration(): # Mock infrastructure only mock_database = Mock() mock_email_client = Mock() # Use real services entity_service = EntityService(mock_database) notification_service = NotificationService(mock_email_client) workflow_service = WorkflowService(entity_service, notification_service) # Test real interaction result = workflow_service.execute_workflow(name="Test", value=100, user_id="u123") assert result.success is True mock_database.save.assert_called() mock_email_client.send.assert_called()
One Concept Per Test
Each test should verify ONE specific behavior. If a test has multiple unrelated assertions, split it into separate tests.
Why? When a test fails, you should immediately know what broke. Multi-concept tests make debugging harder.
Exception for E2E Tests: End-to-end tests are expensive and slow to run (often taking minutes). For E2E tests, it's acceptable and encouraged to verify multiple related aspects of a workflow in a single test to minimize execution time. For example, an E2E test that triggers a workflow can verify:
- The workflow completes successfully
- A PR was created
- The PR has correct labels
- The PR has an AI summary
- The PR has cost information
This is a pragmatic trade-off: E2E tests prioritize execution speed over granular isolation, while unit/integration tests maintain strict one-concept-per-test discipline.
Example
# ❌ WRONG: Tests multiple unrelated things def test_user_service(): service = UserService(mock_repo) user = service.create_user("Alice") assert user.name == "Alice" found = service.find_user(user.id) service.delete_user(user.id) # Multiple unrelated behaviors # ✅ RIGHT: Separate focused tests def test_create_user_sets_name(): service = UserService(mock_repo) user = service.create_user("Alice") assert user.name == "Alice" def test_find_user_returns_existing(): service = UserService(mock_repo) mock_repo.get_by_id.return_value = User(id="123") found = service.find_user("123") assert found.id == "123"
Coverage Guidelines
Current Coverage Status
Target: 85%+ overall coverage with meaningful tests that provide confidence in refactoring and catch real regressions.
What to Test
High Priority (90%+ coverage):
- Business logic (task management, data processing algorithms)
- Command orchestration (CLI commands, workflow coordination)
- Configuration validation
- Error handling paths
Medium Priority (70%+ coverage):
- Utilities and formatters
- API integrations (GitHub, external services)
- File operations and artifact handling
Low Priority (may skip):
- Simple getters/setters
- Third-party library wrappers
- CLI argument parsing (tested via E2E)
- Entry points (
- tested via E2E)__main__.py
Viewing Coverage Reports
# Generate HTML coverage report pytest tests/ --cov=src --cov-report=html # Open in browser open htmlcov/index.html # macOS xdg-open htmlcov/index.html # Linux # View in terminal with missing lines pytest tests/ --cov=src --cov-report=term-missing
Coverage reports show which lines are tested and which are not, helping identify gaps in test coverage.
Common Anti-Patterns
❌ Mocking Internal Logic
# WRONG def test_bad(): service = UserService(mock_repo) with patch.object(UserService, 'validate_email', return_value=True): service.create_user(email="test@example.com") # ❌ Not testing real behavior # RIGHT def test_good(): service = UserService(mock_repo) service.create_user(email="test@example.com") # ✓ Tests real validation
❌ Mocking Domain Models
# WRONG mock_user = Mock(spec=User) # ❌ # RIGHT user = User(id="123", name="Alice") # ✓
❌ Testing Implementation Details
# WRONG def test_bad(): service.create_user("Alice") assert service._validate_name.called # ❌ Internal implementation # RIGHT def test_good(): user = service.create_user("Alice") assert user.name == "Alice" # ✓ Observable behavior
❌ Not Using spec= Parameter
# WRONG mock_repo = Mock() mock_repo.typo_method.return_value = "data" # No error caught # RIGHT mock_repo = Mock(spec=UserRepository) # mock_repo.typo_method raises AttributeError ✓
Coverage Guidance
Skip testing:
- Trivial getters/setters
- Simple
or__repr____str__ - Framework boilerplate
- One-line defensive code
Focus coverage on:
- Business logic in services
- Complex conditionals and loops
- Error handling for important cases
- Integration points between services
Test Organization
Tests should follow consistent organization conventions for maintainability. See the python-code-style skill for code organization conventions that also apply to test files.
Directory Structure
Tests mirror the
src/ directory structure:
tests/ ├── conftest.py # Shared fixtures ├── unit/ │ ├── domain/ # Domain layer tests │ │ ├── test_config.py │ │ ├── test_models.py │ │ └── test_exceptions.py │ ├── infrastructure/ # Infrastructure layer tests │ │ ├── git/ │ │ │ └── test_operations.py │ │ ├── github/ │ │ │ ├── test_operations.py │ │ │ └── test_actions.py │ │ └── filesystem/ │ │ └── test_operations.py │ └── services/ # Service layer tests │ ├── formatters/ │ │ └── test_table_formatter.py │ ├── test_entity_service.py │ ├── test_workflow_service.py │ └── test_statistics_service.py ├── integration/ # Integration tests │ └── cli/ # CLI command integration tests │ └── commands/ │ ├── test_prepare.py │ ├── test_finalize.py │ └── test_statistics.py ├── e2e/ # End-to-end tests └── builders/ # Test helpers/factories
Naming Conventions
Class Names:
TestModuleName or TestFunctionName
- ✅
TestEntityService - ✅
TestCheckUserCapacity - ❌
(wrong suffix)ServiceTests - ❌
(too vague)TestServices
Test Method Names:
test_<what>_<when>_<condition>
- ✅
test_find_user_returns_none_when_all_at_capacity - ✅
test_create_branch_raises_error_when_branch_exists - ✅
test_parse_data_extracts_id_and_name - ❌
(not descriptive)test_user - ❌
(vague)test_find_user_works
Fixture Names:
<resource>_<state> or mock_<service>
- ✅
,user_config
,sample_spec_filemock_github_api - ❌
,data
,setupfixture1
File Names:
test_<module_name>.py
- ✅
test_entity_service.py - ✅
test_operations.py - ❌
entity_tests.py
CI/CD Integration
Automated Testing
Tests run automatically on:
- Every push to main branch
- Every pull request
- Via GitHub Actions workflow
Test Workflow Example
# .github/workflows/test.yml name: Run Tests on: push: branches: [main] pull_request: branches: [main] jobs: test: runs-on: ubuntu-latest steps: - uses: actions/checkout@v3 - name: Set up Python uses: actions/setup-python@v4 with: python-version: '3.11' - name: Install dependencies run: | pip install -r requirements.txt pip install pytest pytest-cov - name: Run tests with coverage run: | pytest tests/unit/ tests/integration/ \ --cov=src \ --cov-report=term-missing \ --cov-report=html \ --cov-fail-under=70 - name: Upload coverage report uses: actions/upload-artifact@v3 with: name: coverage-report path: htmlcov/
PR Requirements
- All tests must pass
- Coverage must meet minimum threshold (typically 70%)
- New features require tests
- Bug fixes should include regression tests
Related Skills
- creating-services: For understanding service constructor patterns that you'll be testing
- domain-modeling: For understanding domain models and why not to mock them
- python-code-style: For code organization conventions that apply to test files
- identifying-layer-placement: For understanding which layer your code belongs to and how to test it