Testing Guide#
This guide covers testing practices and guidelines for PhyloZoo.
Setup#
Testing Dependencies#
PhyloZoo uses a small set of testing tools:
pytest >= 7.0.0
pytest-cov >= 4.0.0
You can install them directly:
pip install pytest pytest-cov
or as part of the development extra when working on PhyloZoo itself:
pip install -e ".[dev]"
Test Organization#
Test Framework#
PhyloZoo uses pytest as the testing framework. Tests are located in the tests/ directory
and mirror the source code structure.
Test Structure#
Tests are organized to mirror the source code structure:
tests/core/ - Tests for core modules (networks, quartets, splits, sequences, distance)
tests/viz/ - Tests for visualization
tests/utils/ - Tests for utility modules
tests/conftest.py - Shared fixtures and pytest configuration
Running Tests#
Run all tests:
pytest
Run tests with coverage:
pytest --cov=phylozoo --cov-report=html
Run specific test files:
pytest tests/core/network/test_dnetwork.py
Run tests matching a pattern:
pytest -k "test_split"
Run tests and show print statements:
pytest -s
Run tests in verbose mode:
pytest -v
Docstring Examples#
The >>> examples in the docstrings are tests as well. pytest is configured
(--doctest-modules and src/phylozoo in testpaths in pyproject.toml) to
execute every example and compare its printed output with the line below it, so a
plain pytest run covers both tests/ and the examples in src/. To run only
the examples:
pytest src/phylozoo
pytest src/phylozoo/core/split/base.py # one module
This keeps the examples in the API reference honest: an example that stops matching the code fails the suite. When writing one, keep in mind that
output is compared literally, so print sets and dicts in a deterministic form (
sorted(net.taxa)rather thannet.taxa) and avoid NumPy scalar reprs (float(arr[0, 1]));a blank line in the expected output is written as
<BLANKLINE>, and a section heading such asNotesmust be separated from the last example by a blank line;the network in an example must be valid, since constructors validate – an invalid one fails at construction and takes every later line of the example down with it;
examples that need external files or that only illustrate an API (pseudo-code) can be excluded with
# doctest: +SKIPon the>>>line, and examples that write files should write into a temporary directory;the same example should also exist as a regular test if it demonstrates behaviour worth guarding, since doctests only check what is printed.
Writing Tests#
Guidelines#
When adding new tests:
Create a test file named test_<module_name>.py in the appropriate subdirectory
Import the module you’re testing
Use descriptive test function names starting with test_
Use pytest fixtures from conftest.py when appropriate
Add docstrings to test classes and functions explaining what they test
Use type hints for test functions
Example:
def test_my_function() -> None:
"""Test that my_function works correctly."""
result = my_function(input)
assert result == expected_output
Test Classes#
Group related tests in test classes:
class TestMyClass:
"""Test cases for MyClass."""
def test_basic_functionality(self) -> None:
"""Test basic functionality."""
# Test code here
pass
def test_edge_case(self) -> None:
"""Test edge case handling."""
# Test code here
pass
Fixtures#
Shared fixtures are defined in tests/conftest.py. Use fixtures for common test data:
def test_with_fixture(sample_network) -> None:
"""Test using a fixture."""
assert sample_network.num_nodes > 0
Best Practices#
Test both success and failure cases: Test that functions work correctly and handle errors appropriately
Use descriptive assertions: Include clear error messages in assertions when possible
Test edge cases: Test with empty inputs, single elements, boundary conditions
Keep tests independent: Each test should be able to run independently
Use fixtures for common setup: Avoid duplicating test setup code
Continuous Integration#
Every push to master and every pull request against master triggers the
CI GitHub Actions workflow defined in .github/workflows/ci.yml. The workflow
runs three jobs in parallel:
Tests (required) — runs the full
pytestsuite (with coverage) on Python 3.10 and Python 3.11, installing the package with thedevextras.Lint (required) — runs
ruff check src testsandblack --check src tests.
Note
mypy type-checking is not currently part of the CI workflow because not all
type annotations pass cleanly yet. Full mypy compliance is planned for a future
release. You can still run mypy src locally to inspect the current state.
The configuration for each tool is taken from pyproject.toml, so running them
locally before pushing produces the same result the workflow does:
pytest --cov=phylozoo --cov-report=term
ruff check src tests
black --check src tests
Concurrent runs on the same branch are cancelled automatically, so only the most recent commit in a pull request is checked.
The CI badge in the project README links to the most recent workflow runs on
master.
In addition to ci.yml (which gates merges to master), the
.github/workflows/ directory also contains:
docs.yml— builds and deploys the Sphinx documentation to GitHub Pages on every version tag.release.yml— builds the source distribution and wheel, and publishes them to PyPI on every version tag.