yuga-planner / tests /README_TESTS.md
blackopsrepl's picture
feat: refactor tests and logging
f3473c1

Yuga Planner Test Framework Instructions

Overview

This document provides instructions for writing, running, and maintaining tests in the Yuga Planner project using our standardized test framework.

Quick Start

Running Tests

Standard Testing (recommended for CI/CD)

pytest tests/test_*.py -v

Debug Mode (detailed output for troubleshooting)

YUGA_DEBUG=true pytest tests/test_*.py -v -s

Direct Execution (individual test files)

python tests/test_specific_file.py
YUGA_DEBUG=true python tests/test_specific_file.py  # with debug output

Writing Tests

1. Basic Test Structure

Every test file should follow this pattern:

import sys
from tests.test_utils import get_test_logger, create_test_results

# Initialize logging
logger = get_test_logger(__name__)

def test_your_feature():
    """Test function that works with both pytest and direct execution."""
    logger.start_test("Description of what you're testing")

    try:
        # Your test logic here
        result = your_function_to_test()

        # Use assertions for validation
        assert result is not None, "Result should not be None"
        assert result.status == "success", f"Expected success, got {result.status}"

        logger.pass_test("Feature works correctly")

    except Exception as e:
        logger.fail_test(f"Test failed: {str(e)}")
        raise

# Direct execution support
if __name__ == "__main__":
    results = create_test_results(logger)
    results.run_test('test_your_feature', test_your_feature)
    all_passed = results.summary()
    sys.exit(0 if all_passed else 1)

2. Test Utilities Reference

TestLogger Methods

from tests.test_utils import get_test_logger
logger = get_test_logger(__name__)

# Test lifecycle
logger.start_test("Test description")      # Mark test beginning
logger.pass_test("Success message")        # Log successful completion
logger.fail_test("Error message")          # Log test failure

# Organization
logger.section("Section Title")            # Create visual separators

# Standard logging levels
logger.debug("Detailed debug information")
logger.info("General information")
logger.warning("Warning message")
logger.error("Error message")

TestResults Methods

from tests.test_utils import create_test_results

results = create_test_results(logger)

# Run tests with automatic error handling
results.run_test('test_name', test_function)

# Generate summary and get overall result
all_passed = results.summary()  # Returns True if all tests passed

# Use for exit codes
sys.exit(0 if all_passed else 1)

3. Async Test Pattern

For async tests, use this pattern:

import asyncio
import pytest

@pytest.mark.asyncio
async def test_async_feature():
    """Async test that works with pytest."""
    logger.start_test("Testing async functionality")

    try:
        result = await your_async_function()
        assert result.is_valid(), "Async result should be valid"
        logger.pass_test("Async functionality works")
    except Exception as e:
        logger.fail_test(f"Async test failed: {str(e)}")
        raise

# For direct execution of async tests
async def run_async_tests():
    """Helper for running async tests directly."""
    logger.section("Async Tests")
    await test_async_feature()

if __name__ == "__main__":
    results = create_test_results(logger)
    # Use asyncio.run for async test execution
    results.run_test('async_tests', lambda: asyncio.run(run_async_tests()))
    all_passed = results.summary()
    sys.exit(0 if all_passed else 1)

4. Complex Test Files

For files with multiple test functions:

def test_feature_one():
    logger.start_test("Testing feature one")
    # ... test logic ...
    logger.pass_test("Feature one works")

def test_feature_two():
    logger.start_test("Testing feature two")
    # ... test logic ...
    logger.pass_test("Feature two works")

def test_integration():
    logger.start_test("Testing integration")
    # ... test logic ...
    logger.pass_test("Integration works")

if __name__ == "__main__":
    results = create_test_results(logger)

    # Run all tests
    results.run_test('feature_one', test_feature_one)
    results.run_test('feature_two', test_feature_two)
    results.run_test('integration', test_integration)

    # Generate summary
    all_passed = results.summary()
    sys.exit(0 if all_passed else 1)

Environment Control

Debug Output Control

The framework respects the YUGA_DEBUG environment variable:

  • YUGA_DEBUG=false or unset: Minimal output suitable for CI/CD
  • YUGA_DEBUG=true: Detailed debug output for troubleshooting

Usage Examples

# Quiet mode (default)
pytest tests/test_factory.py -v

# Debug mode
YUGA_DEBUG=true pytest tests/test_factory.py -v -s

# Direct execution with debug
YUGA_DEBUG=true python tests/test_constraints.py

Best Practices

1. Test Organization

  • Use descriptive test function names: test_calendar_event_creation_with_constraints
  • Group related tests in the same file
  • Use logger.section() to separate different test groups within a file

2. Error Messages

  • Always provide clear assertion messages:
    assert result.count == 5, f"Expected 5 items, got {result.count}"
    

3. Test Lifecycle

  • Always use logger.start_test() at the beginning of each test
  • Use logger.pass_test() or logger.fail_test() to mark completion
  • Let exceptions propagate for pytest compatibility

4. Output Structure

  • Use sections to organize output:
    logger.section("Calendar Operations Tests")
    # ... run calendar tests ...
    
    logger.section("Task Management Tests")
    # ... run task tests ...
    

Integration with Existing Code

Pytest Compatibility

The framework is fully compatible with existing pytest features:

  • Test discovery works without changes
  • Fixtures continue to work normally
  • Async tests work with @pytest.mark.asyncio
  • All pytest command-line options are supported

Logging Integration

  • Integrates with project's utils.logging_config
  • Respects existing logging configuration
  • No interference with application logging

Troubleshooting

Common Issues

  1. Tests run but no output: Ensure you're using -s flag with pytest in debug mode
  2. Import errors: Make sure tests/test_utils.py is accessible
  3. Async tests failing: Use @pytest.mark.asyncio for pytest, asyncio.run() for direct execution

Debug Mode Benefits

When YUGA_DEBUG=true:

  • Detailed function entry/exit logging
  • Variable state information
  • Extended error messages
  • Test timing information

Example Test Files

Refer to these existing test files for patterns:

  • tests/test_calendar_operations.py - Basic synchronous tests
  • tests/test_task_composer_agent.py - Async test patterns
  • tests/test_constraints.py - Large pytest-based test suite
  • tests/test_factory.py - Complex test file with multiple test types

Summary

This test framework provides:

  • Consistency across all test files
  • Flexibility for different execution modes
  • Professional output suitable for development and CI/CD
  • Maintainability through centralized utilities
  • Compatibility with existing pytest workflows

Follow these patterns for all new tests to maintain consistency and leverage the full power of the test framework.