Testing Guide¶
This page is under construction.
Guide for writing and running tests in KNL.
Running Tests¶
Quick Start¶
# Run all tests
make test
# Run with coverage
make test-cov
# Run with verbose output
pytest tests/ -v
# Run specific test file
pytest tests/test_config.py
# Run specific test
pytest tests/test_config.py::TestConfigManager::test_load_global_config
Coverage Reports¶
# Generate coverage report
make test-cov
# View HTML coverage report
open htmlcov/index.html # On macOS
xdg-open htmlcov/index.html # On Linux
Test Structure¶
Directory Organization¶
tests/
├── conftest.py # Pytest configuration and fixtures
├── test_config.py # Configuration tests
├── test_task.py # Task management tests
├── test_paths.py # Path utilities tests
├── test_patterns.py # Pattern matching tests
├── test_cli.py # CLI tests
└── fixtures/ # Test fixtures and data
├── sample_config.toml
└── sample_task/
Naming Conventions¶
- Test files:
test_<module>.py - Test classes:
Test<Feature> - Test methods:
test_<behavior>
Example:
# tests/test_config.py
class TestConfigManager:
def test_load_global_config(self):
"""Test loading global configuration."""
pass
def test_load_local_config(self):
"""Test loading local configuration."""
pass
def test_merge_configs(self):
"""Test merging global and local configs."""
pass
Writing Tests¶
Basic Test Structure¶
import pytest
from knl.core.config import ConfigManager
class TestConfigManager:
def test_load_config(self, tmp_path):
"""Test loading configuration from file."""
# Arrange
config_file = tmp_path / "config.toml"
config_file.write_text('[task]\nid_format = "jira"')
# Act
manager = ConfigManager(config_file)
config = manager.load()
# Assert
assert config.task.id_format == "jira"
Using Fixtures¶
# tests/conftest.py
import pytest
from pathlib import Path
@pytest.fixture
def temp_repo(tmp_path):
"""Create a temporary repository structure."""
repo = tmp_path / "test-repo"
repo.mkdir()
knowledge = repo / ".knowledge"
knowledge.mkdir()
return repo
# tests/test_task.py
def test_create_task(temp_repo):
"""Test task creation."""
# Use temp_repo fixture
task_dir = temp_repo / ".knowledge" / "tasks" / "PROJ-123"
# ... test logic
Testing CLI Commands¶
from typer.testing import CliRunner
from knl.cli import app
runner = CliRunner()
def test_cli_version():
"""Test CLI version command."""
result = runner.invoke(app, ["--version"])
assert result.exit_code == 0
assert "1.0.0" in result.output
def test_cli_create_task():
"""Test task creation via CLI."""
result = runner.invoke(app, ["create", "PROJ-123"])
assert result.exit_code == 0
assert "Created task PROJ-123" in result.output
Testing Error Handling¶
import pytest
def test_invalid_task_id():
"""Test handling of invalid task ID."""
with pytest.raises(ValueError, match="Invalid task ID"):
create_task("invalid-id")
def test_file_not_found():
"""Test handling of missing file."""
with pytest.raises(FileNotFoundError):
load_config(Path("/nonexistent/config.toml"))
Parametrized Tests¶
import pytest
@pytest.mark.parametrize("task_id,expected", [
("PROJ-123", "PROJ-123"),
("#456", "gh-456"),
("ABC-789", "ABC-789"),
])
def test_normalize_task_id(task_id, expected):
"""Test task ID normalization."""
result = normalize_task_id(task_id)
assert result == expected
Test Categories¶
Unit Tests¶
Test individual components in isolation:
def test_parse_jira_id():
"""Test JIRA ID parsing."""
assert parse_jira_id("PROJ-123") == ("PROJ", "123")
def test_parse_github_id():
"""Test GitHub ID parsing."""
assert parse_github_id("#456") == "456"
Integration Tests¶
Test multiple components together:
def test_task_lifecycle(temp_repo):
"""Test complete task lifecycle."""
# Create task
task = create_task("PROJ-123", temp_repo)
assert task.exists()
# Update task
update_task_status(task, "in_progress")
assert task.status == "in_progress"
# Delete task
delete_task(task)
assert not task.exists()
End-to-End Tests¶
Test complete user workflows:
def test_full_workflow(temp_repo):
"""Test full task workflow via CLI."""
runner = CliRunner()
# Initialize repo
result = runner.invoke(app, ["init"], cwd=temp_repo)
assert result.exit_code == 0
# Create task
result = runner.invoke(app, ["create", "PROJ-123"], cwd=temp_repo)
assert result.exit_code == 0
# List tasks
result = runner.invoke(app, ["list"], cwd=temp_repo)
assert "PROJ-123" in result.output
Best Practices¶
Test Independence¶
Each test should be independent:
# Good - uses fixture for isolation
def test_create_task(temp_repo):
create_task("PROJ-123", temp_repo)
assert task_exists("PROJ-123", temp_repo)
# Bad - depends on previous test
def test_update_task():
# Assumes PROJ-123 exists from previous test
update_task_status("PROJ-123", "done")
Descriptive Names¶
Use clear, descriptive test names:
# Good
def test_create_task_with_github_id_normalizes_to_filesystem_safe_name():
pass
# Acceptable
def test_github_id_normalization():
pass
# Bad
def test_task1():
pass
Test Coverage Goals¶
- Aim for >80% code coverage
- Focus on critical paths
- Test edge cases and error handling
- Don't test framework code
Mocking External Dependencies¶
from unittest.mock import Mock, patch
@patch('knl.utils.git.subprocess.run')
def test_git_status(mock_run):
"""Test git status command."""
mock_run.return_value = Mock(
stdout="On branch main\n",
returncode=0
)
result = get_git_status()
assert result.branch == "main"
mock_run.assert_called_once()
Continuous Integration¶
Tests run automatically on:
- Every push to GitHub
- Every pull request
- Before deployment
GitHub Actions workflow:
# .github/workflows/test.yml
name: Tests
on: [push, pull_request]
jobs:
test:
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v4
- uses: actions/setup-python@v5
with:
python-version: '3.14'
- run: make test-cov
- uses: codecov/codecov-action@v3
Troubleshooting¶
Tests Failing Locally¶
# Clear pytest cache
rm -rf .pytest_cache
# Clear Python cache
find . -type d -name __pycache__ -exec rm -rf {} +
find . -type f -name "*.pyc" -delete
# Reinstall in development mode
uv pip install -e ".[dev]"
# Run tests with verbose output
pytest tests/ -vv
Coverage Issues¶
Slow Tests¶
Next Steps¶
- Development Setup - Set up your environment
- Contributing Guide - Contribution guidelines
- Architecture Principles - Understand the design