orchestrata_tools.test_generator.branch_tests

Reference › orchestrata_tools › test_generator › branch_tests

This module identifies branches in a method from its docstring and metadata and generates a test for each. Detection is heuristic: it reads the Raises and Returns sections and does not look at the method body. For body-level branch tests see testgen.branch. It is used by combined_generator and re-exported by the package. tests/test_orchestrata_tools_branch_tests_coverage.py and tests/test_orchestrata_tools.py cover it.

Branch is a dataclass with a name, a condition and a suggestion. BranchTestGenerator.branches returns the branches found for a MethodInfo, and generate_for_method returns test source covering all of them, or an empty string when none are found.

At a glance

Source src/orchestrata_tools/test_generator/branch_tests.py
Lines of code 115
Classes 2
Functions 0
Imports (internal) orchestrata_tools.models.ast_models
Imported by orchestrata_tools.test_generator, orchestrata_tools.test_generator.combined_generator
Tests that import it tests/test_orchestrata_tools.py, tests/test_orchestrata_tools_branch_tests_coverage.py

Class diagram

Class diagram orchestrata_tools.test_generator.branch_tests

Dependency diagram

Dependency diagram orchestrata_tools.test_generator.branch_tests

API

Classes

Branch (dataclass)

Decorators: dataclass · Line 17

A single branch in a method.

Attribute Type Default
name str
condition str
suggestion str

BranchTestGenerator

Line 25

Generate tests covering branches identified from method metadata.

Method Signature Summary
branches branches(method: MethodInfo) -> list[Branch] Identify branches from docstring and method metadata.
generate_for_method generate_for_method(method: MethodInfo) -> str Generate tests covering all identified branches.

Source

Permalink: https://gitlab.com/MSamy/orchestrata-ultimate-architecture/-/blob/a4cbc2bdf/src/orchestrata_tools/test_generator/branch_tests.py

"""Analyze method docstrings to identify branches and generate test cases.

Heuristic-based branch detection from docstring Raises/Returns sections.
"""

from __future__ import annotations

import re
from dataclasses import dataclass
from typing import TYPE_CHECKING

if TYPE_CHECKING:
    from orchestrata_tools.models.ast_models import MethodInfo


@dataclass
class Branch:
    """A single branch in a method."""

    name: str
    condition: str
    suggestion: str


class BranchTestGenerator:
    """Generate tests covering branches identified from method metadata."""

    def branches(self, method: MethodInfo) -> list[Branch]:
        """Identify branches from docstring and method metadata."""
        result = []
        doc = method.docstring or ""

        # Look for Raises: section
        raises = re.findall(
            r"Raises?:\s*\n?((?:\s*[-*]?\s*\w+.*\n?)+)", doc, re.IGNORECASE
        )
        if raises:
            for line in raises[0].split("\n"):
                match = re.match(
                    r"\s*[-*]?\s*(\w+Error|\w+Exception|\w+):?\s*(.*)", line
                )
                if match:
                    exc_type, desc = match.groups()
                    result.append(
                        Branch(
                            name=f"raises_{exc_type.lower()}",
                            condition=f"{exc_type} raised",
                            suggestion=desc
                            or f"Test that {exc_type} is raised on invalid input",
                        )
                    )

        # Look for Returns: section with multiple return types
        returns = re.findall(
            r"Returns?:\s*\n?((?:\s*.*?\n?)+?)(?=\n\w+:|$)", doc, re.IGNORECASE
        )
        if returns:
            ret_text = returns[0]
            if "None" in ret_text or "null" in ret_text.lower():
                result.append(
                    Branch(
                        name="returns_none",
                        condition="returns None",
                        suggestion="Test case where method returns None",
                    )
                )

        # Async methods have success/failure branches
        if method.is_async:
            result.append(
                Branch(
                    name="async_success",
                    condition="async operation succeeds",
                    suggestion="Test successful async execution",
                )
            )

        # Methods with optional parameters have default branch
        for param in method.parameters:
            if param.default:
                result.append(
                    Branch(
                        name=f"default_{param.name}",
                        condition=f"{param.name} uses default ({param.default})",
                        suggestion=f"Test with default value for {param.name}",
                    )
                )

        return result

    def generate_for_method(self, method: MethodInfo) -> str:
        """Generate tests covering all identified branches."""
        branches = self.branches(method)
        if not branches:
            return ""

        lines = [
            f"class Test{method.class_name or ''}{method.name.title()}Branches:",
            f'    """Branch coverage tests for {method.class_name}.{method.name}()."""',
            "",
        ]

        for branch in branches:
            test_name = f"test_{branch.name}"
            lines.extend(
                [
                    f"    def {test_name}(self):",
                    f'        """Branch: {branch.condition}."""',
                    f"        # {branch.suggestion}",
                    "        raise NotImplementedError('TODO: implement branch test')",
                    "",
                ]
            )

        return "\n".join(lines)