To establish a comprehensive Pytest suite for your data_processor service module, follow these steps and file structures.
1. Framework Setup
First, install the necessary libraries:
``bash pip install pytest pytest-cov ``
Configure pytest.ini at your project root to define test discovery, coverage reporting, and basic options:
``ini # pytest.ini [pytest] minversion = 6.0 addopts = --strict-markers --cov=data_processor --cov-report=term-missing --cov-report=xml testpaths = tests python_files = test_*.py ``
2. Test Files Structure
Organize your project and test files as follows:
`` project_root/ ├── data_processor/ │ ├── __init__.py │ ├── service.py # Your service module │ └── repository_interface.py # Your repository interface definition ├── tests/ │ ├── __init__.py │ ├── conftest.py # Pytest fixtures │ └── test_data_processor.py # Unit tests for data_processor └── pytest.ini ``
3. Fixtures/Mocks (conftest.py)
Create tests/conftest.py to house your mock repository and service instance fixtures. This isolates the service logic from external dependencies.
```python # tests/conftest.py import pytest from unittest.mock import MagicMock
# Assume these imports are valid for your project structure from data_processor.service import DataProcessorService from data_processor.repository_interface import RepositoryInterface
@pytest.fixture def mock_repository() -> MagicMock: """Provides a mock implementation of the repository interface.""" mock = MagicMock(spec=RepositoryInterface) mock.fetch_by_id.side_effect = lambda item_id: {"id": item_id, "status": "new", "value": 100} if item_id == "item-123" else None mock.store_result.return_value = True return mock
@pytest.fixture def data_processor_service(mock_repository: MagicMock) -> DataProcessorService: """Provides a DataProcessorService instance with the mocked repository injected.""" return DataProcessorService(repository=mock_repository) ```
4. Parameterized Test Cases (test_data_processor.py)
In tests/test_data_processor.py, implement parameterized tests for the process_item method of your service. This demonstrates testing various input scenarios.
```python # tests/test_data_processor.py import pytest from unittest.mock import MagicMock
# Assume DataProcessorService.process_item exists
@pytest.mark.parametrize("item_id, expected_status, should_call_store", [ ("item-123", "processed", True), ("item-404", "not_found", False), ("item-invalid", "error", False), ]) def test_process_item_scenarios( data_processor_service: DataProcessorService, mock_repository: MagicMock, item_id: str, expected_status: str, should_call_store: bool ): """Tests the DataProcessorService.process_item method across different conditions.""" result = data_processor_service.process_item(item_id) assert result == expected_status
if should_call_store: mock_repository.store_result.assert_called_once_with(item_id, expected_status) else: mock_repository.store_result.assert_not_called()
if item_id == "item-123": # Specific assertion for a known good path mock_repository.fetch_by_id.assert_called_once_with("item-123") ```
5. Coverage Notes
The pytest.ini configuration automatically integrates coverage.py. Run your tests from the project root:
``bash pytest ``
This command will execute tests and print a coverage report to the terminal. To generate a detailed HTML report, run:
``bash coverage html ``
Open htmlcov/index.html in your browser to view the interactive report.
6. CI Hook Suggestion
Integrate these tests into your CI pipeline (e.g., GitHub Actions, GitLab CI) with a step similar to this:
```yaml # Example CI workflow step
- name: Run Pytest and Coverage Checks
run: | pip install pytest pytest-cov pytest coverage report --fail-under=90 # Fails the build if coverage is below 90% ```