CtrlK
BlogDocsLog inGet started
Tessl Logo

tessl/pypi-cleo

Cleo allows you to create beautiful and testable command-line interfaces.

Pending

Quality

Pending

Does it follow best practices?

Impact

Pending

No eval scenarios have been run

Overview
Eval results
Files

testing.mddocs/

Testing Utilities

The testing utilities provide comprehensive tools for testing CLI commands and applications with simulated input/output, assertion helpers, and isolated test environments. These utilities enable thorough testing of command behavior without requiring real console interaction.

Capabilities

Command Testing

Test individual commands in isolation with controlled input and output.

class CommandTester:
    def __init__(self, command: Command) -> None:
        """
        Create a tester for a specific command.
        
        Args:
            command (Command): Command instance to test
        """
    
    def execute(self, args: str = "", inputs: str | None = None, 
                interactive: bool | None = None, verbosity: Verbosity | None = None,
                decorated: bool | None = None) -> int:
        """
        Execute the command with simulated input.
        
        Args:
            args (str): Command arguments and options as string
            inputs (str | None): Simulated user input responses
            interactive (bool | None): Override interactive mode detection
            verbosity (Verbosity | None): Set specific verbosity level
            decorated (bool | None): Override decoration detection
            
        Returns:
            int: Command exit code
        """
    
    @property
    def command(self) -> Command:
        """Get the command being tested."""
    
    @property
    def io(self) -> IO:
        """Get the IO interface used for testing."""
    
    def get_display(self, normalize: bool = False) -> str:
        """
        Get the output display content.
        
        Args:
            normalize (bool): Whether to normalize line endings
            
        Returns:
            str: Output content from the command execution
        """
    
    def get_error_output(self, normalize: bool = False) -> str:
        """
        Get the error output content.
        
        Args:
            normalize (bool): Whether to normalize line endings
            
        Returns:
            str: Error output content from the command execution
        """
    
    def get_status_code(self) -> int:
        """
        Get the last execution status code.
        
        Returns:
            int: Exit code from last execution
        """

Application Testing

Test complete applications with multiple commands and routing.

class ApplicationTester:  
    def __init__(self, application: Application) -> None:
        """
        Create a tester for an application.
        
        Args:
            application (Application): Application instance to test
        """
    
    def execute(self, args: str = "", inputs: str | None = None,
                interactive: bool | None = None, verbosity: Verbosity | None = None,
                decorated: bool | None = None) -> int:
        """
        Execute application commands with simulated input.
        
        Args:
            args (str): Full command line including command name and arguments
            inputs (str | None): Simulated user input responses
            interactive (bool | None): Override interactive mode detection
            verbosity (Verbosity | None): Set specific verbosity level
            decorated (bool | None): Override decoration detection
            
        Returns:
            int: Application exit code
        """
    
    @property
    def application(self) -> Application:
        """Get the application being tested."""
    
    @property
    def io(self) -> IO:
        """Get the IO interface used for testing."""
    
    def get_display(self, normalize: bool = False) -> str:
        """
        Get the output display content.
        
        Args:
            normalize (bool): Whether to normalize line endings
            
        Returns:
            str: Output content from the application execution
        """
    
    def get_error_output(self, normalize: bool = False) -> str:
        """
        Get the error output content.
        
        Args:
            normalize (bool): Whether to normalize line endings
            
        Returns:
            str: Error output content from the application execution
        """
    
    def get_status_code(self) -> int:
        """
        Get the last execution status code.
        
        Returns:
            int: Exit code from last execution
        """

Usage Examples

Basic Command Testing

import pytest
from cleo.testers.command_tester import CommandTester
from your_app.commands import GreetCommand

def test_greet_command_basic():
    command = GreetCommand()
    tester = CommandTester(command)
    
    # Test with no arguments
    exit_code = tester.execute()
    assert exit_code == 0
    assert "Hello" in tester.get_display()

def test_greet_command_with_name():
    command = GreetCommand()
    tester = CommandTester(command)
    
    # Test with name argument
    exit_code = tester.execute("John")
    assert exit_code == 0
    assert "Hello John" in tester.get_display()

def test_greet_command_with_yell_option():
    command = GreetCommand()
    tester = CommandTester(command)
    
    # Test with --yell flag
    exit_code = tester.execute("John --yell")
    assert exit_code == 0
    assert "HELLO JOHN" in tester.get_display()

Testing Command Output and Formatting

def test_status_command_output():
    command = StatusCommand()
    tester = CommandTester(command)
    
    exit_code = tester.execute("--verbose")
    output = tester.get_display()
    
    # Test formatted output
    assert exit_code == 0
    assert "System Status:" in output
    assert "Service 1: Running" in output
    assert "Service 2: Stopped" in output
    
    # Test verbose output only appears with flag
    tester.execute("")
    normal_output = tester.get_display()
    assert len(output) > len(normal_output)

def test_error_output():
    command = ProcessCommand()
    tester = CommandTester(command)
    
    # Test error conditions
    exit_code = tester.execute("nonexistent-file.txt")
    assert exit_code == 1
    
    error_output = tester.get_error_output()
    assert "File not found" in error_output

Testing Interactive Commands

def test_interactive_confirmation():
    command = DeleteCommand()
    tester = CommandTester(command)
    
    # Test with "yes" input
    exit_code = tester.execute("important-file.txt", inputs="yes\n")
    assert exit_code == 0
    assert "File deleted" in tester.get_display()
    
    # Test with "no" input
    exit_code = tester.execute("important-file.txt", inputs="no\n")
    assert exit_code == 1
    assert "Operation cancelled" in tester.get_display()

def test_interactive_questions():
    command = ConfigCommand()
    tester = CommandTester(command)
    
    # Simulate multiple user inputs
    inputs = "myproject\n8080\nyes\nproduction\n"
    exit_code = tester.execute("", inputs=inputs)
    
    assert exit_code == 0
    output = tester.get_display()
    assert "Project: myproject" in output
    assert "Port: 8080" in output
    assert "Environment: production" in output

def test_choice_questions():
    command = SetupCommand()
    tester = CommandTester(command)
    
    # Test single choice
    exit_code = tester.execute("", inputs="2\n")  # Select second option
    assert exit_code == 0
    
    # Test invalid choice handling
    exit_code = tester.execute("", inputs="invalid\n1\n")  # Invalid then valid
    assert exit_code == 0
    assert "Invalid choice" in tester.get_display()

Testing Command Arguments and Options

def test_argument_validation():
    command = ProcessCommand()
    tester = CommandTester(command)
    
    # Test missing required argument
    exit_code = tester.execute("")
    assert exit_code == 1
    assert "Not enough arguments" in tester.get_error_output()
    
    # Test valid arguments
    exit_code = tester.execute("input.txt output.txt")
    assert exit_code == 0

def test_option_parsing():
    command = BuildCommand()
    tester = CommandTester(command)
    
    # Test short options
    exit_code = tester.execute("-v -o dist/")
    assert exit_code == 0
    
    # Test long options
    exit_code = tester.execute("--verbose --output-dir=dist/")
    assert exit_code == 0
    
    # Test option values
    tester.execute("--format json")
    assert command.option("format") == "json"

Application-Level Testing

def test_application_command_routing():
    from your_app.application import create_application
    
    app = create_application()
    tester = ApplicationTester(app)
    
    # Test help command
    exit_code = tester.execute("help")
    assert exit_code == 0
    assert "Available commands:" in tester.get_display()
    
    # Test list command
    exit_code = tester.execute("list")
    assert exit_code == 0
    output = tester.get_display()
    assert "process" in output  # Our custom command should be listed
    assert "config" in output

def test_application_command_execution():
    app = create_application()
    tester = ApplicationTester(app)
    
    # Test executing specific commands
    exit_code = tester.execute("process input.txt --format json")
    assert exit_code == 0
    
    exit_code = tester.execute("config --interactive", inputs="yes\nproduction\n")
    assert exit_code == 0

def test_command_not_found():
    app = create_application()
    tester = ApplicationTester(app)
    
    exit_code = tester.execute("nonexistent-command")
    assert exit_code == 1
    assert "Command not found" in tester.get_error_output()

Testing Verbosity Levels

def test_verbosity_levels():
    command = DiagnosticCommand()
    tester = CommandTester(command)
    
    # Test quiet mode
    exit_code = tester.execute("--quiet")
    quiet_output = tester.get_display()
    assert exit_code == 0
    assert len(quiet_output.strip()) == 0  # No output in quiet mode
    
    # Test normal verbosity
    exit_code = tester.execute("")
    normal_output = tester.get_display()
    assert "Basic info" in normal_output
    assert "Debug info" not in normal_output
    
    # Test verbose mode
    exit_code = tester.execute("--verbose")
    verbose_output = tester.get_display()
    assert "Basic info" in verbose_output
    assert "Detailed info" in verbose_output
    assert "Debug info" not in verbose_output
    
    # Test debug mode
    exit_code = tester.execute("-vvv")
    debug_output = tester.get_display()
    assert "Basic info" in debug_output
    assert "Detailed info" in debug_output
    assert "Debug info" in debug_output

Testing Table Output

def test_table_output():
    command = ReportCommand()
    tester = CommandTester(command)
    
    exit_code = tester.execute("")
    output = tester.get_display()
    
    # Check table structure
    assert "Name" in output  # Header
    assert "Status" in output  # Header
    assert "Service A" in output  # Data row
    assert "Running" in output  # Data row
    
    # Check table formatting (borders, alignment)
    lines = output.split('\n')
    header_line = next(line for line in lines if "Name" in line)
    assert "│" in header_line or "|" in header_line  # Table borders

def test_progress_output():
    command = ProcessCommand()
    tester = CommandTester(command)
    
    exit_code = tester.execute("--show-progress")
    output = tester.get_display()
    
    # Progress bars create temporary output that gets cleared
    # Test for completion message instead
    assert "Processing complete" in output
    assert exit_code == 0

Test Fixtures and Helpers

import pytest
import tempfile
import os

@pytest.fixture
def temp_dir():
    """Create a temporary directory for test files."""
    with tempfile.TemporaryDirectory() as tmpdir:
        yield tmpdir

@pytest.fixture  
def sample_config_file(temp_dir):
    """Create a sample configuration file."""
    config_path = os.path.join(temp_dir, "config.json")
    with open(config_path, 'w') as f:
        f.write('{"database": {"host": "localhost", "port": 5432}}')
    return config_path

def test_command_with_files(sample_config_file):
    command = LoadConfigCommand()
    tester = CommandTester(command)
    
    exit_code = tester.execute(f"--config {sample_config_file}")
    assert exit_code == 0
    assert "Configuration loaded" in tester.get_display()

class TestCommandHelper:
    """Helper class for common test patterns."""
    
    @staticmethod
    def execute_command(command_class, args="", inputs=None):
        """Execute a command and return tester for assertions."""
        command = command_class()
        tester = CommandTester(command)
        exit_code = tester.execute(args, inputs)
        return tester, exit_code
    
    @staticmethod
    def assert_success(tester, exit_code, expected_output=None):
        """Assert command executed successfully."""
        assert exit_code == 0
        if expected_output:
            assert expected_output in tester.get_display()
    
    @staticmethod
    def assert_error(tester, exit_code, expected_error=None):
        """Assert command failed with error."""
        assert exit_code != 0
        if expected_error:
            assert expected_error in tester.get_error_output()

# Usage of helper
def test_with_helper():
    tester, exit_code = TestCommandHelper.execute_command(
        GreetCommand, 
        "John --yell"
    )
    TestCommandHelper.assert_success(tester, exit_code, "HELLO JOHN")

Parameterized Testing

@pytest.mark.parametrize("name,expected", [
    ("John", "Hello John"),
    ("Alice", "Hello Alice"), 
    ("", "Hello"),
])
def test_greet_variations(name, expected):
    command = GreetCommand()
    tester = CommandTester(command)
    
    exit_code = tester.execute(name)
    assert exit_code == 0
    assert expected in tester.get_display()

@pytest.mark.parametrize("args,should_succeed", [
    ("valid-file.txt", True),
    ("", False),  # Missing required argument
    ("nonexistent.txt", False),  # File doesn't exist
])
def test_file_processing(args, should_succeed):
    command = ProcessFileCommand()
    tester = CommandTester(command)
    
    exit_code = tester.execute(args)
    
    if should_succeed:
        assert exit_code == 0
        assert "Success" in tester.get_display()
    else:
        assert exit_code != 0

Install with Tessl CLI

npx tessl i tessl/pypi-cleo

docs

application.md

commands.md

exceptions.md

index.md

io.md

styling.md

testing.md

ui.md

tile.json