Testing Guide
Comprehensive guide for testing CareLog.
Testing Framework
CareLog uses pytest for testing with the following structure:
- Unit tests for services
- Integration tests for workflows
- Fixture-based test data
- Temporary databases for isolation
Running Tests
Run All Tests
bash
pytest tests/ -vRun Specific Test File
bash
pytest tests/test_user_service.py -vRun Specific Test
bash
pytest tests/test_user_service.py::test_create_user -vRun with Coverage
bash
pytest tests/ --cov=core --cov-report=htmlView coverage report: open htmlcov/index.html
Run Tests in Parallel
bash
pytest tests/ -n autoTest Structure
Test File Organization
tests/
├── __init__.py
├── conftest.py # Shared fixtures
├── test_auth.py # Authentication tests
├── test_services.py # Core service tests
├── test_user_service.py # User management tests
├── test_health_log_service.py
├── test_appointment_service.py
├── test_diagnosis_prescription_service.py
├── test_emergency_call_advanced.py
├── test_visit_notification_service.py
├── test_audit_log_service.py
└── test_database_service.pyNaming Conventions
- Test files:
test_*.py - Test functions:
test_* - Fixture functions: Descriptive names without
test_prefix
Writing Tests
Basic Test Structure
python
import pytest
from core.services.your_service import YourService
@pytest.fixture
def temp_db():
"""Create temporary database for testing"""
import tempfile
import os
from core.services.database_service import DatabaseService
# Create temp file
fd, path = tempfile.mkstemp(suffix='.json')
os.close(fd)
# Initialize database
db = DatabaseService(db_path=path)
db.update("collection", {})
yield db, path
# Cleanup
try:
os.unlink(path)
except:
pass
def test_your_feature(temp_db):
"""Test your feature"""
db, path = temp_db
service = YourService(db_service=db)
# Arrange
expected_value = "test"
# Act
result = service.your_method(expected_value)
# Assert
assert result is not None
assert result.field == expected_valueTest Fixtures
Shared Fixtures
Common fixtures are defined in conftest.py:
python
@pytest.fixture
def temp_db():
"""Temporary database for testing"""
# Setup
yield db, path
# TeardownCustom Fixtures
Create fixtures for test-specific setup:
python
@pytest.fixture
def sample_user(temp_db):
"""Create a sample user for testing"""
db, path = temp_db
service = UserService(db_service=db)
user = service.create(
name="Test User",
firstName="Test",
lastName="User",
email="test@example.com",
password="password",
role="patient"
)
return userAssertion Patterns
Basic Assertions
python
# Equality
assert result == expected
# Identity
assert result is not None
# Membership
assert item in collection
# Boolean
assert condition is TrueNone-Safe Assertions
Always check for None before accessing attributes:
python
# WRONG - May cause AttributeError
result = service.get("id")
assert result.field == "value"
# CORRECT - Safe pattern
result = service.get("id")
assert result is not None
assert result.field == "value"Collection Assertions
python
# Length
assert len(results) == 3
# All items match condition
assert all(isinstance(item, Type) for item in results)
# Any item matches condition
assert any(item.field == "value" for item in results)Test Categories
Unit Tests
Test individual components in isolation.
Example: Testing UserService
python
def test_create_user(temp_db):
"""Test user creation"""
db, path = temp_db
service = UserService(db_service=db)
user = service.create(
name="John Doe",
firstName="John",
lastName="Doe",
email="john@example.com",
password="password123",
role="patient"
)
assert user is not None
assert user.name == "John Doe"
assert user.email == "john@example.com"
assert user.role == "patient"Integration Tests
Test interactions between components.
Example: Testing Appointment Workflow
python
def test_appointment_workflow(temp_db):
"""Test complete appointment workflow"""
db, path = temp_db
user_service = UserService(db_service=db)
appointment_service = AppointmentService(db_service=db)
# Create patient and doctor
patient = user_service.create(..., role="patient")
doctor = user_service.create(..., role="doctor")
# Patient requests appointment
appointment = appointment_service.create(
patientID=patient.id,
reason="Checkup",
requestedDate="2025-10-26T10:00:00"
)
assert appointment.status == "Pending"
# Doctor assigns appointment
appointment_service.assign_doctor(appointment.appointmentID, doctor.id)
updated = appointment_service.get(appointment.appointmentID)
assert updated.doctorID == doctor.id
# Complete appointment
appointment_service.update_status(appointment.appointmentID, "Completed")
final = appointment_service.get(appointment.appointmentID)
assert final.status == "Completed"Edge Case Tests
Test boundary conditions and error cases.
python
def test_get_nonexistent_user(temp_db):
"""Test getting user that doesn't exist"""
db, path = temp_db
service = UserService(db_service=db)
result = service.get_by_id("nonexistent")
assert result is None
def test_create_user_duplicate_email(temp_db):
"""Test creating user with duplicate email"""
db, path = temp_db
service = UserService(db_service=db)
# Create first user
service.create(..., email="test@example.com", ...)
# Attempt to create duplicate
with pytest.raises(Exception): # Or specific exception
service.create(..., email="test@example.com", ...)Testing Best Practices
1. Arrange-Act-Assert Pattern
Structure tests clearly:
python
def test_feature():
# Arrange - Set up test data
service = YourService(db_service=db)
test_data = "value"
# Act - Execute the feature
result = service.method(test_data)
# Assert - Verify the result
assert result == expected2. One Concept Per Test
Each test should verify one specific behavior:
python
# GOOD - Single concept
def test_user_creation():
user = service.create(...)
assert user is not None
def test_user_email_validation():
with pytest.raises(ValueError):
service.create(..., email="invalid")
# BAD - Multiple concepts
def test_user():
user = service.create(...)
assert user is not None
assert service.authenticate(...) is not None
assert service.delete(...) is True3. Descriptive Test Names
Use clear, descriptive names:
python
# GOOD
def test_create_user_with_valid_data():
...
def test_authenticate_returns_none_for_invalid_password():
...
# BAD
def test_user():
...
def test_auth():
...4. Independent Tests
Tests should not depend on each other:
python
# GOOD - Each test is independent
def test_create():
service.create(...)
def test_update():
service.create(...) # Create test data here
service.update(...)
# BAD - Tests depend on execution order
created_user = None
def test_create():
global created_user
created_user = service.create(...)
def test_update():
service.update(created_user.id, ...) # Depends on previous test5. Use Fixtures for Common Setup
Extract common setup to fixtures:
python
@pytest.fixture
def sample_patient(temp_db):
db, path = temp_db
service = UserService(db_service=db)
return service.create(..., role="patient")
def test_with_patient(sample_patient):
assert sample_patient.role == "patient"6. Test Data Should Be Realistic
Use realistic test data:
python
# GOOD
def test_create_appointment():
appointment = service.create(
patientID="patient123",
reason="Annual physical examination",
requestedDate="2025-11-01T10:00:00"
)
# AVOID
def test_create_appointment():
appointment = service.create(
patientID="1",
reason="test",
requestedDate="2025-01-01"
)Testing Specific Components
Testing Services with Database
python
def test_service_with_database(temp_db):
db, path = temp_db
service = YourService(db_service=db)
# Create
item = service.create(...)
assert item is not None
# Read
retrieved = service.get(item.id)
assert retrieved is not None
assert retrieved.id == item.id
# Update
updated = service.update(item.id, field="new value")
assert updated.field == "new value"
# Delete
success = service.delete(item.id)
assert success is True
assert service.get(item.id) is NoneTesting Authentication
python
def test_password_hashing():
hasher = PasswordHash()
password = "secure_password_123"
# Hash password
hashed = hasher.hash(password)
assert hashed != password
assert len(hashed) > 0
# Verify correct password
assert hasher.verify(password, hashed) is True
# Verify incorrect password
assert hasher.verify("wrong", hashed) is False
def test_user_authentication(temp_db):
db, path = temp_db
service = UserService(db_service=db)
# Create user
service.create(..., email="test@example.com", password="pass123", ...)
# Successful authentication
user = service.authenticate("test@example.com", "pass123")
assert user is not None
# Failed authentication
user = service.authenticate("test@example.com", "wrong")
assert user is NoneTesting with Mock Data
python
from unittest.mock import Mock, patch
def test_with_mock():
# Mock external dependency
mock_service = Mock()
mock_service.get.return_value = {"data": "value"}
# Test with mock
result = your_function(mock_service)
assert result == "expected"
mock_service.get.assert_called_once()Test Coverage
Measuring Coverage
bash
pytest tests/ --cov=core --cov-report=term-missingCoverage Goals
- Services: Aim for 90%+ coverage
- Models: 80%+ coverage
- Utils: 95%+ coverage
- UI: Best effort (Streamlit is harder to test)
Coverage Report
bash
pytest tests/ --cov=core --cov-report=html
open htmlcov/index.htmlContinuous Integration
GitHub Actions Example
yaml
name: Tests
on: [push, pull_request]
jobs:
test:
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v2
- name: Set up Python
uses: actions/setup-python@v2
with:
python-version: '3.8'
- name: Install dependencies
run: |
pip install -r requirements.txt
pip install pytest pytest-cov
- name: Run tests
run: pytest tests/ --cov=core --cov-report=xml
- name: Upload coverage
uses: codecov/codecov-action@v2Debugging Tests
Running Tests in Debug Mode
bash
pytest tests/ -v -s # -s shows print statementsUsing pdb
python
def test_with_debugging():
import pdb; pdb.set_trace() # Breakpoint
result = service.method()
assert result is not NoneVerbose Output
bash
pytest tests/ -vv # Extra verboseCommon Testing Pitfalls
1. Not Checking for None
python
# WRONG
result = service.get("id")
assert result.field == "value" # May raise AttributeError
# CORRECT
result = service.get("id")
assert result is not None
assert result.field == "value"2. Shared Mutable State
python
# WRONG - Shared list between tests
test_data = []
def test_one():
test_data.append("item")
def test_two():
# test_data may have items from test_one!
assert len(test_data) == 0 # May fail
# CORRECT - Fresh state each test
def test_one():
test_data = []
test_data.append("item")
def test_two():
test_data = []
assert len(test_data) == 03. Testing Implementation Instead of Behavior
python
# WRONG - Testing implementation
def test_internal_method():
service._internal_method() # Private method
# CORRECT - Testing behavior
def test_public_interface():
result = service.public_method()
assert result == expected