Testing Guide#

This guide covers testing practices and guidelines for PhyloZoo.

Setup#

Testing Dependencies#

PhyloZoo uses a small set of testing tools:

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

Writing Tests#

Guidelines#

When adding new tests:

  1. Create a test file named test_<module_name>.py in the appropriate subdirectory

  2. Import the module you’re testing

  3. Use descriptive test function names starting with test_

  4. Use pytest fixtures from conftest.py when appropriate

  5. Add docstrings to test classes and functions explaining what they test

  6. 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 pytest suite (with coverage) on Python 3.10 and Python 3.11, installing the package with the dev extras.

  • Lint (required) — runs ruff check src tests and black --check src tests.

  • Type check (advisory) — runs mypy src. The job reports the current mypy error count on every PR so progress can be tracked, but is marked continue-on-error: true so it does not block merging while the codebase is being cleaned up. Once the error count reaches zero the continue-on-error flag should be removed and the job promoted to “required”.

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
mypy src

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.