220 lines
4.7 KiB
Markdown
220 lines
4.7 KiB
Markdown
# PIF Compiler - Test Suite
|
|
|
|
## Overview
|
|
|
|
Comprehensive test suite for the PIF Compiler project using `pytest`.
|
|
|
|
## Structure
|
|
|
|
```
|
|
tests/
|
|
├── __init__.py # Test package marker
|
|
├── conftest.py # Shared fixtures and configuration
|
|
├── test_cosing_service.py # COSING service tests
|
|
├── test_models.py # (TODO) Pydantic model tests
|
|
├── test_echa_service.py # (TODO) ECHA service tests
|
|
└── README.md # This file
|
|
```
|
|
|
|
## Installation
|
|
|
|
```bash
|
|
# Install test dependencies
|
|
uv add --dev pytest pytest-cov pytest-mock
|
|
|
|
# Or manually install
|
|
uv pip install pytest pytest-cov pytest-mock
|
|
```
|
|
|
|
## Running Tests
|
|
|
|
### Run All Tests (Unit only)
|
|
```bash
|
|
uv run pytest
|
|
```
|
|
|
|
### Run Specific Test File
|
|
```bash
|
|
uv run pytest tests/test_cosing_service.py
|
|
```
|
|
|
|
### Run Specific Test Class
|
|
```bash
|
|
uv run pytest tests/test_cosing_service.py::TestParseCasNumbers
|
|
```
|
|
|
|
### Run Specific Test
|
|
```bash
|
|
uv run pytest tests/test_cosing_service.py::TestParseCasNumbers::test_single_cas_number
|
|
```
|
|
|
|
### Run with Verbose Output
|
|
```bash
|
|
uv run pytest -v
|
|
```
|
|
|
|
### Run with Coverage Report
|
|
```bash
|
|
uv run pytest --cov=src/pif_compiler --cov-report=html
|
|
# Open htmlcov/index.html in browser
|
|
```
|
|
|
|
## Test Categories
|
|
|
|
### Unit Tests (Default)
|
|
Fast tests with no external dependencies. Run by default.
|
|
|
|
```bash
|
|
uv run pytest -m unit
|
|
```
|
|
|
|
### Integration Tests
|
|
Tests that hit real APIs or databases. Skipped by default.
|
|
|
|
```bash
|
|
uv run pytest -m integration
|
|
```
|
|
|
|
### Slow Tests
|
|
Tests that take longer to run. Skipped by default.
|
|
|
|
```bash
|
|
uv run pytest -m slow
|
|
```
|
|
|
|
### Database Tests
|
|
Tests requiring MongoDB. Ensure Docker is running.
|
|
|
|
```bash
|
|
cd utils
|
|
docker-compose up -d
|
|
uv run pytest -m database
|
|
```
|
|
|
|
## Test Organization
|
|
|
|
### `test_cosing_service.py`
|
|
|
|
**Coverage:**
|
|
- ✅ `parse_cas_numbers()` - CAS parsing logic
|
|
- Single/multiple CAS
|
|
- Different separators (/, ;, ,, --)
|
|
- Parentheses removal
|
|
- Whitespace handling
|
|
- Invalid dash removal
|
|
|
|
- ✅ `cosing_search()` - API search
|
|
- Search by name
|
|
- Search by CAS
|
|
- Search by EC number
|
|
- Search by ID
|
|
- No results handling
|
|
- Invalid mode error
|
|
|
|
- ✅ `clean_cosing()` - JSON cleaning
|
|
- Basic field cleaning
|
|
- Empty tag removal
|
|
- CAS parsing
|
|
- URL creation
|
|
- Field renaming
|
|
|
|
- ✅ Integration tests (marked as `@pytest.mark.integration`)
|
|
- Real API calls (requires internet)
|
|
|
|
## Writing New Tests
|
|
|
|
### Example Unit Test
|
|
|
|
```python
|
|
class TestMyFunction:
|
|
"""Test my_function."""
|
|
|
|
def test_basic_case(self):
|
|
"""Test basic functionality."""
|
|
result = my_function("input")
|
|
assert result == "expected"
|
|
|
|
def test_edge_case(self):
|
|
"""Test edge case handling."""
|
|
with pytest.raises(ValueError):
|
|
my_function("invalid")
|
|
```
|
|
|
|
### Example Mock Test
|
|
|
|
```python
|
|
from unittest.mock import Mock, patch
|
|
|
|
@patch('module.external_api_call')
|
|
def test_with_mock(mock_api):
|
|
"""Test with mocked external call."""
|
|
mock_api.return_value = {"data": "mocked"}
|
|
result = my_function()
|
|
assert result == "expected"
|
|
mock_api.assert_called_once()
|
|
```
|
|
|
|
### Example Fixture Usage
|
|
|
|
```python
|
|
def test_with_fixture(sample_cosing_response):
|
|
"""Test using a fixture from conftest.py."""
|
|
result = clean_cosing(sample_cosing_response)
|
|
assert "cosingUrl" in result
|
|
```
|
|
|
|
## Best Practices
|
|
|
|
1. **Naming**: Test files/classes/functions start with `test_`
|
|
2. **Arrange-Act-Assert**: Structure tests clearly
|
|
3. **One assertion focus**: Each test should test one thing
|
|
4. **Use fixtures**: Reuse test data via `conftest.py`
|
|
5. **Mock external calls**: Don't hit real APIs in unit tests
|
|
6. **Mark appropriately**: Use `@pytest.mark.integration` for slow tests
|
|
7. **Descriptive names**: Test names should describe what they test
|
|
|
|
## Common Commands
|
|
|
|
```bash
|
|
# Run fast tests only (skip integration/slow)
|
|
uv run pytest -m "not integration and not slow"
|
|
|
|
# Run only integration tests
|
|
uv run pytest -m integration
|
|
|
|
# Run with detailed output
|
|
uv run pytest -vv
|
|
|
|
# Stop at first failure
|
|
uv run pytest -x
|
|
|
|
# Run last failed tests
|
|
uv run pytest --lf
|
|
|
|
# Run tests matching pattern
|
|
uv run pytest -k "test_parse"
|
|
|
|
# Generate coverage report
|
|
uv run pytest --cov=src/pif_compiler --cov-report=term-missing
|
|
```
|
|
|
|
## CI/CD Integration
|
|
|
|
For GitHub Actions (example):
|
|
|
|
```yaml
|
|
- name: Run tests
|
|
run: |
|
|
uv run pytest -m "not integration" --cov --cov-report=xml
|
|
```
|
|
|
|
## TODO
|
|
|
|
- [ ] Add tests for `models.py` (Pydantic validation)
|
|
- [ ] Add tests for `echa_service.py`
|
|
- [ ] Add tests for `echa_parser.py`
|
|
- [ ] Add tests for `echa_extractor.py`
|
|
- [ ] Add tests for `database_service.py`
|
|
- [ ] Add tests for `pubchem_service.py`
|
|
- [ ] Add integration tests with test database
|
|
- [ ] Set up GitHub Actions CI
|