Testing
FilterMate has a comprehensive test suite covering utilities, backends, UI components, and integration scenarios. The test suite targets 80%+ code coverage and uses mocked QGIS modules for fast, isolated testing.
Overviewβ
Test Categoriesβ
FilterMate's test suite is organized into several categories:
| Category | Files | Purpose |
|---|---|---|
| Backend Tests | test_backends.py | Multi-backend architecture, factory pattern |
| Utility Tests | test_appUtils.py, test_constants.py | Core utility functions |
| UI Tests | test_ui_*.py | UI configuration, themes, styles |
| Integration Tests | test_filter_history_integration.py | Feature interaction |
| Performance Tests | test_performance.py, benchmark_simple.py | Speed benchmarks |
| Refactoring Tests | test_refactored_helpers_*.py | Helper method coverage |
| Signal Tests | test_signal_utils.py | Signal/slot management |
| Configuration Tests | test_config_json_reactivity.py, test_choices_type_config.py | Dynamic config |
Test Statisticsβ
- Total Test Files: 35+
- Test Cases: 150+
- Target Coverage: 80%
- Current Coverage: ~75%
- Mock Framework: pytest-mock + pytest-qt
Setupβ
Prerequisitesβ
- Python 3.7+ (QGIS Python environment)
- pip package manager
- No QGIS installation required (tests use mocks)
Install Test Dependenciesβ
Method 1: Using requirements.txtβ
# From project root
cd /path/to/filter_mate
pip install -r tests/requirements-test.txt
Method 2: Manual Installationβ
pip install pytest pytest-cov pytest-mock pytest-qt
Dependencies Explainedβ
| Package | Purpose |
|---|---|
pytest | Test framework and runner |
pytest-cov | Code coverage reporting |
pytest-mock | Mocking QGIS and external dependencies |
pytest-qt | Qt signal/slot testing |
Running Testsβ
Basic Commandsβ
Run All Testsβ
# From project root
pytest tests/
# With verbose output
pytest tests/ -v
# Show print statements
pytest tests/ -v -s
Run Specific Test Fileβ
# Test backends
pytest tests/test_backends.py -v
# Test utilities
pytest tests/test_appUtils.py -v
# Test UI configuration
pytest tests/test_ui_config.py -v
Run Specific Test Classβ
# Test only backend factory
pytest tests/test_backends.py::TestBackendFactory -v
# Test only signal utilities
pytest tests/test_signal_utils.py::TestSignalUtils -v
Run Specific Test Functionβ
# Test specific function
pytest tests/test_backends.py::TestBackendFactory::test_get_backend_postgresql -v
# Test with keyword filter
pytest tests/ -k "backend" -v
Coverage Reportsβ
Generate HTML Coverage Reportβ
# Run tests with coverage
pytest tests/ --cov=modules --cov=filter_mate_app --cov-report=html
# Open report
# Windows:
start htmlcov/index.html
# Linux/Mac:
xdg-open htmlcov/index.html # Linux
open htmlcov/index.html # Mac
Generate Terminal Coverage Reportβ
# Brief coverage summary
pytest tests/ --cov=modules --cov=filter_mate_app
# Detailed line-by-line report
pytest tests/ --cov=modules --cov=filter_mate_app --cov-report=term-missing
Generate XML Coverage (CI/CD)β
# For CI/CD integration
pytest tests/ --cov=modules --cov=filter_mate_app --cov-report=xml
Test Structureβ
Directory Layoutβ
tests/
βββ conftest.py # Shared fixtures & QGIS mocks
βββ requirements-test.txt # Test dependencies
β
βββ Backend Tests
β βββ test_backends.py # Backend architecture
β
βββ Core Utility Tests
β βββ test_appUtils.py # Database utilities
β βββ test_constants.py # Module constants
β βββ test_signal_utils.py # Signal management
β
βββ UI Tests
β βββ test_ui_config.py # UI profile detection
β βββ test_ui_config_ratios.py # Dimension calculations
β βββ test_ui_styles.py # Theme application
β βββ test_qt_json_view_themes.py # JSON view themes
β βββ test_theme_detection.py # Theme synchronization
β
βββ Configuration Tests
β βββ test_config_json_reactivity.py # Dynamic config updates
β βββ test_choices_type_config.py # ChoicesType enum
β βββ test_color_contrast.py # Accessibility compliance
β
βββ Backend-Specific Tests
β βββ test_spatialite_expression_quotes.py # Field name quoting
β βββ test_spatialite_temp_table_fix.py # Temp table handling
β βββ test_sqlite_lock_handling.py # Database locking
β βββ test_geometry_repair.py # Auto-repair invalid geometries
β βββ test_buffer_error_handling.py # Buffer operation errors
β βββ test_ogr_type_handling.py # OGR provider detection
β βββ test_geopackage_detection.py # GeoPackage identification
β
βββ Feature Tests
β βββ test_filter_history.py # History tracking
β βββ test_filter_history_integration.py # UI integration
β βββ test_feedback_utils.py # User messaging
β βββ test_prepared_statements.py # SQL preparation
β
βββ Performance Tests
β βββ test_performance.py # Speed benchmarks
β βββ benchmark_simple.py # Quick profiling
β
βββ Refactoring Tests
β βββ test_refactored_helpers_appTasks.py # 58 helper methods
β βββ test_refactored_helpers_dockwidget.py # 14 UI helpers
β
βββ Migration Tests
βββ test_layer_provider_type_migration.py # Provider type handling
βββ test_source_table_name.py # Table name extraction
βββ test_buffer_type.py # Buffer type validation
Key Files Explainedβ
conftest.pyβ
Provides shared test fixtures and QGIS mocks:
@pytest.fixture
def mock_qgs_layer():
"""Mock QgsVectorLayer for testing"""
layer = Mock()
layer.providerType.return_value = 'postgres'
layer.featureCount.return_value = 1000
return layer
@pytest.fixture
def mock_iface():
"""Mock QGIS interface"""
iface = Mock()
iface.messageBar.return_value = Mock()
return iface
test_backends.pyβ
Tests the multi-backend architecture:
class TestBackendFactory:
def test_get_backend_postgresql(self):
"""Test PostgreSQL backend selection"""
# Arrange
layer = create_postgres_layer()
# Act
backend = BackendFactory.get_backend(layer)
# Assert
assert isinstance(backend, PostgresqlBackend)
assert backend.supports_predicates()
Testing Workflowβ
Test-Driven Development (TDD)β
Continuous Integrationβ
- Pre-commit: Run fast tests locally
- Push: Trigger CI pipeline
- CI: Run full test suite with coverage
- Review: Check coverage reports
- Merge: Only if tests pass + coverage maintained
Writing Testsβ
Test Templateβ
import pytest
from unittest.mock import Mock, patch
class TestMyFeature:
"""Test suite for MyFeature"""
@pytest.fixture
def setup_data(self):
"""Setup test data"""
return {
'param1': 'value1',
'param2': 123
}
def test_basic_functionality(self, setup_data):
"""Test basic feature operation"""
# Arrange
input_data = setup_data
expected_output = 'expected_value'
# Act
result = my_function(input_data)
# Assert
assert result == expected_output
def test_error_handling(self):
"""Test error scenarios"""
# Arrange
invalid_input = None
# Act & Assert
with pytest.raises(ValueError):
my_function(invalid_input)
@patch('modules.appUtils.psycopg2')
def test_with_mock(self, mock_psycopg2):
"""Test with external dependency mocked"""
# Arrange
mock_psycopg2.connect.return_value = Mock()
# Act
result = function_using_psycopg2()
# Assert
mock_psycopg2.connect.assert_called_once()
assert result is not None
Best Practicesβ
β Do'sβ
- β
Use descriptive names -
test_filter_applies_buffer_correctly - β Follow AAA pattern - Arrange, Act, Assert
- β One assertion per test (when possible)
- β Mock external dependencies - QGIS, databases, filesystem
- β Test edge cases - Empty inputs, None values, large datasets
- β Use fixtures - Share setup code across tests
- β Document test purpose - Clear docstrings
β Don'tsβ
- β Don't test QGIS internals - Only test FilterMate code
- β Don't use real databases - Mock all DB operations
- β Don't depend on test order - Tests should be independent
- β Don't skip cleanup - Use fixtures with yield
- β Don't write brittle tests - Avoid hardcoded values
- β Don't ignore failures - Fix or document intentional skips
Testing Checklistβ
When adding new code, ensure tests cover:
- Happy path - Normal expected usage
- Edge cases - Boundary conditions
- Error handling - Invalid inputs, exceptions
- Integration - Interaction with other components
- Performance - Not too slow (if critical path)
- Mocking - External dependencies isolated
Test Examplesβ
Example 1: Testing Utility Functionβ
# tests/test_appUtils.py
def test_get_primary_key_name_postgres(mock_postgres_layer):
"""Test primary key extraction from PostgreSQL layer"""
# Arrange
mock_postgres_layer.dataProvider().uri().table.return_value = 'my_table'
mock_postgres_layer.dataProvider().uri().keyColumn.return_value = 'id'
# Act
pk_name = get_primary_key_name(mock_postgres_layer)
# Assert
assert pk_name == 'id'
Example 2: Testing Backend Selectionβ
# tests/test_backends.py
class TestBackendFactory:
def test_recommend_backend_large_dataset(self):
"""Should recommend PostgreSQL for large datasets"""
# Arrange
layer = Mock()
layer.featureCount.return_value = 100000
layer.providerType.return_value = 'postgres'
# Act
recommendation = BackendFactory.recommend_backend(layer)
# Assert
assert recommendation == 'postgresql'
Example 3: Testing UI Configurationβ
# tests/test_ui_config.py
def test_detect_ui_profile_compact():
"""Should detect compact profile for small screens"""
# Arrange
screen_width = 1366
screen_height = 768
# Act
profile = detect_ui_profile(screen_width, screen_height)
# Assert
assert profile == 'compact'
Example 4: Testing Signal Handlingβ
# tests/test_signal_utils.py
def test_safe_disconnect_existing_signal(qtbot):
"""Should disconnect existing signal without error"""
# Arrange
widget = QPushButton()
callback = Mock()
widget.clicked.connect(callback)
# Act
safe_disconnect(widget.clicked, callback)
widget.click()
# Assert
callback.assert_not_called() # Signal disconnected
Troubleshootingβ
Common Issuesβ
Import Errorsβ
Problem: ModuleNotFoundError: No module named 'qgis'
Solution: Tests use mocked QGIS modules. Ensure conftest.py is in place:
# conftest.py must contain QGIS mocks
import sys
from unittest.mock import Mock
sys.modules['qgis'] = Mock()
sys.modules['qgis.core'] = Mock()
# ... more mocks
Test Discovery Issuesβ
Problem: pytest doesn't find tests
Solution: Ensure proper naming:
- Test files:
test_*.py - Test classes:
class Test* - Test functions:
def test_*()
Fixture Not Foundβ
Problem: fixture 'my_fixture' not found
Solution: Check fixture is in conftest.py or same file:
# conftest.py
@pytest.fixture
def my_fixture():
return "test_data"
Coverage Too Lowβ
Problem: Coverage report shows < 80%
Solution:
- Identify uncovered lines:
pytest --cov --cov-report=term-missing - Add tests for uncovered code
- Use
# pragma: no coverfor untestable code (sparingly)
Debugging Testsβ
Use pytest verbosityβ
# Show test names and results
pytest tests/test_backends.py -v
# Show print statements
pytest tests/test_backends.py -v -s
# Stop at first failure
pytest tests/test_backends.py -x
Use pytest debuggerβ
# Drop into debugger on failure
pytest tests/test_backends.py --pdb
# Set breakpoint in code
import pdb; pdb.set_trace()
Check mock callsβ
# Verify mock was called
mock_function.assert_called()
mock_function.assert_called_once()
mock_function.assert_called_with(expected_arg)
# Print mock calls for debugging
print(mock_function.call_args_list)
CI/CD Integrationβ
GitHub Actions Exampleβ
# .github/workflows/test.yml
name: Run 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.9'
- name: Install dependencies
run: |
pip install -r tests/requirements-test.txt
- name: Run tests with coverage
run: |
pytest tests/ --cov=modules --cov=filter_mate_app --cov-report=xml
- name: Upload coverage to Codecov
uses: codecov/codecov-action@v2
with:
file: ./coverage.xml
Performance Testingβ
Benchmark Testsβ
FilterMate includes performance benchmarks:
# Run performance tests
pytest tests/test_performance.py -v
# Run simple benchmark
python tests/benchmark_simple.py
Performance Goalsβ
| Operation | Target Time | Backend |
|---|---|---|
| Filter 10k features | < 1s | Any |
| Filter 100k features | < 5s | PostgreSQL |
| Export 10k features | < 2s | Any |
| UI update | < 100ms | N/A |
Contributing Testsβ
When contributing to FilterMate:
- Write tests first (TDD approach)
- Maintain coverage (β₯ 80%)
- Follow naming conventions
- Mock external dependencies
- Document test purpose
- Run full suite before PR
Test PR Checklistβ
- All tests pass locally
- New features have tests
- Coverage maintained or improved
- No skipped tests without reason
- Tests run in < 60 seconds
- Documentation updated
See Alsoβ
- Contributing Guide - Contribution workflow
- Code Style - Coding standards
- Architecture - System design
- Backend Development - Backend implementation
Resourcesβ
Last updated: December 8, 2025