This guide provides instructions for setting up the testing environment, running tests, and debugging issues.
- Prerequisites
- Setting Up the Virtual Environment
- Running Tests
- Coverage Reports
- Debugging Tests
- Writing New Tests
- Common Issues and Solutions
- Python 3.13
- pip (Python package installer)
- Git (for cloning the repository)
# Navigate to the project root directory
cd ha-llmvision
# Create a virtual environment
python -m venv .venvOn macOS/Linux:
source .venv/bin/activateOn Windows:
.venv\Scripts\activate# Upgrade pip
pip install --upgrade pip
# Install all test dependencies from requirements file
pip install -r requirements-test.txtNote: The requirements-test.txt file includes all necessary dependencies for running tests, including:
- Testing frameworks (pytest and plugins)
- Home Assistant testing utilities
- Mocking libraries
- Code coverage tools
- Component dependencies (aiohttp, Pillow, etc.)
The project includes a pytest.ini file that automatically configures the Python path, so tests should work out of the box. If you encounter import errors, you can manually set the PYTHONPATH:
# Add project root to PYTHONPATH (if needed)
export PYTHONPATH="${PYTHONPATH}:$(pwd)"The pytest.ini file also configures:
- Test discovery patterns
- Default pytest options (verbose, no cache files)
- Coverage settings
- Test markers for categorization
Note: The project is configured to not create .pytest_cache directories to keep the repository clean. All test artifacts are listed in .gitignore.
# Check pytest is installed
pytest --version
# Should output something like: pytest 8.4.2
# Verify Python path is configured correctly
python -c "import sys; print('custom_components' in str(sys.path))"
# Run a simple test to verify everything works
pytest tests/test_const.py::TestConstants::test_domain -v# Run all unit tests (default - excludes integration tests)
pytest tests/ -v
# Run only unit tests (explicitly exclude integration tests)
pytest tests/ -m "not integration" -v
# Run ALL tests including integration tests (requires setup)
pytest tests/ -v --run-integrationNote: Integration tests in tests/test_api.py are automatically skipped unless you have the required configuration files (.instance and .token).
# Run tests for a specific module
pytest tests/test_memory.py -v
# Run tests for multiple modules
pytest tests/test_memory.py tests/test_calendar.py -v# Run a specific test class
pytest tests/test_memory.py::TestMemory -v
# Run a specific test function
pytest tests/test_memory.py::TestMemory::test_init_without_entry -v# Quiet mode (less verbose)
pytest tests/ -q
# Show print statements
pytest tests/ -v -s
# Stop on first failure
pytest tests/ -x
# Show local variables on failure
pytest tests/ -l# Run tests with coverage
pytest tests/ --ignore=tests/test_api.py --cov=custom_components/llmvision --cov-report=term
# Generate HTML coverage report
pytest tests/ --ignore=tests/test_api.py --cov=custom_components/llmvision --cov-report=html
# View HTML report (opens in browser)
open htmlcov/index.html # macOS
xdg-open htmlcov/index.html # Linux
start htmlcov/index.html # Windows# Show missing lines in terminal
pytest tests/ --cov=custom_components/llmvision --cov-report=term-missing
# Generate multiple report formats
pytest tests/ --cov=custom_components/llmvision --cov-report=term --cov-report=html --cov-report=xml
# Set minimum coverage threshold (fails if below)
pytest tests/ --cov=custom_components/llmvision --cov-fail-under=50# Drop into debugger on failure
pytest tests/ --pdb
# Drop into debugger at start of each test
pytest tests/ --trace
# Show captured output even for passing tests
pytest tests/ -v -sAdd breakpoints in your test code:
def test_something():
import pdb; pdb.set_trace() # Breakpoint
# Your test code hereCreate .vscode/launch.json:
{
"version": "0.2.0",
"configurations": [
{
"name": "Python: Pytest Current File",
"type": "python",
"request": "launch",
"module": "pytest",
"args": [
"${file}",
"-v",
"-s"
],
"console": "integratedTerminal",
"justMyCode": false
}
]
}# Show all log output
pytest tests/ -v --log-cli-level=DEBUG
# Show warnings
pytest tests/ -v -W all"""Unit tests for module_name.py module."""
import pytest
from unittest.mock import Mock, patch, AsyncMock
class TestClassName:
"""Test ClassName class."""
@pytest.fixture
def mock_dependency(self):
"""Create a mock dependency."""
return Mock()
def test_method_name(self, mock_dependency):
"""Test method_name does something."""
# Arrange
instance = ClassName(mock_dependency)
# Act
result = instance.method_name()
# Assert
assert result == expected_value
@pytest.mark.asyncio
async def test_async_method(self):
"""Test async method."""
result = await async_function()
assert result is not None- Use descriptive test names:
test_method_name_when_condition_then_expected_result - Follow AAA pattern: Arrange, Act, Assert
- One assertion per test (when possible)
- Use fixtures for common setup
- Mock external dependencies (API calls, database, file system)
- Test edge cases (empty inputs, None values, errors)
- Keep tests independent (no shared state between tests)
Available in tests/conftest.py:
@pytest.fixture
def mock_hass():
"""Mock Home Assistant instance."""
@pytest.fixture
def mock_config_entry():
"""Mock config entry."""Problem: ModuleNotFoundError: No module named 'custom_components' or missing test dependencies
Solution:
# Ensure you're in the project root directory
cd ha-llmvision
# Ensure virtual environment is activated
source .venv/bin/activate
# Reinstall all test dependencies
pip install -r requirements-test.txt
# If still having issues, try upgrading pip first
pip install --upgrade pip
pip install -r requirements-test.txtProblem: RuntimeWarning: coroutine was never awaited
Solution:
# Add @pytest.mark.asyncio decorator
@pytest.mark.asyncio
async def test_async_function():
result = await async_function()
assert result is not NoneProblem: Mock is not being used, real code is executing
Solution:
# Ensure you're patching the right location
# Patch where it's used, not where it's defined
with patch('module_using_function.function_name') as mock_func:
# Your test codeProblem: Tests have shared state or side effects
Solution:
# Use fixtures with proper scope
@pytest.fixture(scope="function") # Creates new instance per test
def my_fixture():
return SomeObject()
# Or use autouse fixtures to clean up
@pytest.fixture(autouse=True)
def cleanup():
yield
# Cleanup code hereProblem: Some modules not included in coverage report
Solution:
# Specify the package explicitly
pytest tests/ --cov=custom_components/llmvision --cov-report=term-missing
# Or create .coveragerc file
[run]
source = custom_components/llmvision
omit =
*/tests/*
*/__pycache__/*Problem: Tests take too long to execute
Solution:
# Run tests in parallel (pytest-xdist is included in requirements-test.txt)
pytest tests/ -n auto # Uses all CPU cores
# Run only failed tests from last run
pytest tests/ --lf
# Run tests that failed first, then others
pytest tests/ --ff
# Skip slow tests (if marked with @pytest.mark.slow)
pytest tests/ -m "not slow"The project includes a GitHub Actions workflow (.github/workflows/tests.yaml) that automatically runs tests on:
- Push to main/master/dev branches
- Pull requests
- Manual workflow dispatch
- Matrix Testing: Tests run on Python
- Unit Tests: Runs all unit tests (excludes integration tests)
- Coverage Report: Generates coverage report and uploads to Codecov
- Test Summary: Provides a summary of test results
- Check the "Actions" tab in your GitHub repository
- Each commit/PR will show test status with a ✅ or ❌
- Click on a workflow run to see detailed test output
- Coverage reports are available on Codecov (if configured)
Always run tests locally before pushing:
# Quick test
pytest tests/ -m "not integration" -q
# Full test with coverage (like CI)
pytest tests/ -m "not integration" --cov=custom_components/llmvision --cov-report=termThe project includes several configuration files for testing:
-
pytest.ini- Main pytest configuration- Configures Python path automatically
- Sets default test options
- Defines test markers (slow, integration, unit)
- Configures coverage settings
-
requirements-test.txt- All testing dependencies- Testing frameworks and plugins
- Home Assistant testing utilities
- Mocking and coverage tools
-
.coveragerc(optional) - Additional coverage configuration- Can be used to override pytest.ini coverage settings
- pytest Documentation
- pytest-cov Documentation
- pytest-asyncio Documentation
- unittest.mock Documentation
- Home Assistant Testing
Located in tests/test_*.py files (except test_api.py). These test individual components in isolation using mocks and don't require external dependencies.
Run unit tests:
pytest tests/ -m "not integration" -vLocated in tests/test_api.py. These test the actual API endpoints against a running Home Assistant instance.
Setup for integration tests:
- Start a Home Assistant instance
- Create
tests/.instancewith your HA URL:http://localhost:8123 - Create
tests/.tokenwith a long-lived access token - Run integration tests:
pytest tests/test_api.py -v
Note: Integration tests are automatically skipped if configuration files are missing.
- Total Unit Tests: 143
- Overall Coverage: 25%
- Modules with High Coverage:
const.py: 100%calendar.py: 96%memory.py: 77%
When adding new features:
- Write tests first (TDD approach)
- Ensure all tests pass:
pytest tests/ -v - Check coverage:
pytest tests/ --cov=custom_components/llmvision - Aim for at least 80% coverage on new code
- Update this README if adding new test patterns
If you encounter issues:
- Check the Common Issues section
- Run tests with verbose output:
pytest tests/ -vv - Check the HTML coverage report for uncovered lines
- Review test logs:
pytest tests/ --log-cli-level=DEBUG
For those who want to get started quickly:
# 1. Clone the repository (if not already done)
git clone <repository-url>
cd ha-llmvision
# 2. Create and activate virtual environment
python -m venv .venv
source .venv/bin/activate # On Windows: .venv\Scripts\activate
# 3. Install all test dependencies
pip install -r requirements-test.txt
# 4. Run tests using the convenience script
./run_tests.sh # Run all unit tests
./run_tests.sh -c # Run with coverage report
./run_tests.sh -q # Quick run (quiet mode)
# Or use pytest directly
pytest tests/ -v # Run all tests (integration tests auto-skipped)
pytest tests/ -m "not integration" --cov=custom_components/llmvision --cov-report=htmlThat's it! You're ready to run tests and contribute to the project.
The run_tests.sh script provides convenient shortcuts:
./run_tests.sh # Run all unit tests
./run_tests.sh -c # Run with coverage report
./run_tests.sh -v # Verbose output
./run_tests.sh -q # Quick/quiet mode
./run_tests.sh -i # Include integration tests
./run_tests.sh -c -v # Coverage with verbose output
./run_tests.sh --help # Show all optionspython -m venv .venv # Create virtual environment
source .venv/bin/activate # Activate (macOS/Linux)
.venv\Scripts\activate # Activate (Windows)
pip install -r requirements-test.txt # Install dependencies./run_tests.sh # Run all unit tests
./run_tests.sh -c # Run with coverage
./run_tests.sh -v # Verbose output
./run_tests.sh -q # Quick/quiet mode
./run_tests.sh -i # Include integration tests
./run_tests.sh --help # Show all optionspytest tests/ -v # Run all tests (verbose)
pytest tests/ -q # Run all tests (quiet)
pytest tests/test_memory.py # Run specific file
pytest tests/ -k "test_init" # Run tests matching pattern
pytest tests/ -x # Stop on first failure
pytest tests/ --lf # Run last failed tests
pytest tests/ -n auto # Run tests in parallel
pytest tests/ -m "not integration" # Exclude integration testspytest tests/ --cov=custom_components/llmvision # Basic coverage
pytest tests/ --cov=custom_components/llmvision --cov-report=html # HTML report
pytest tests/ --cov=custom_components/llmvision --cov-report=term-missing # Show missing lines
open htmlcov/index.html # View HTML reportpytest tests/ --pdb # Drop into debugger on failure
pytest tests/ -vv -s # Very verbose with print output
pytest tests/ --log-cli-level=DEBUG # Show debug logs
pytest tests/ -l # Show local variables on failure# Run specific test with coverage and verbose output
pytest tests/test_memory.py::TestMemory::test_init_without_entry -v --cov=custom_components/llmvision
# Run all tests except integration tests with coverage
pytest tests/ --ignore=tests/test_api.py --cov=custom_components/llmvision --cov-report=html
# Run failed tests first, stop on first failure, show output
pytest tests/ --ff -x -s# Update all dependencies to latest versions
pip install --upgrade -r requirements-test.txt
# Update specific package
pip install --upgrade pytest
# Check for outdated packages
pip list --outdated
# Freeze current versions (for reproducibility)
pip freeze > requirements-test-frozen.txt# Remove virtual environment
deactivate
rm -rf .venv
# Remove coverage files (if generated with --cov-report=html)
rm -rf .coverage htmlcov/
# Remove Python cache files
find . -type d -name "__pycache__" -exec rm -rf {} +
find . -type f -name "*.pyc" -deleteNote: The project is configured to not create .pytest_cache files automatically. Coverage HTML reports are only generated when explicitly requested with --cov-report=html.