orchestrata_tools.test_generator.interface_tests
Reference › orchestrata_tools › test_generator › interface_tests
InterfaceTestGenerator writes tests for Protocol and abstract base classes. A protocol or ABC cannot be instantiated, so it first emits a concrete implementation. generate_for_protocol returns a concrete class implementing a ClassInfo protocol plus tests, and generate_for_abc returns a minimal concrete subclass plus tests. _signature renders a method signature and _param_default picks a default for a parameter from its annotation, checking container types such as list and dict before scalar types, because a type like list[str] contains the text str. It is used by combined_generator and covered by tests/test_orchestrata_tools_test_generator.py.
At a glance
| Source | src/orchestrata_tools/test_generator/interface_tests.py |
| Lines of code | 118 |
| Classes | 1 |
| 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_test_generator.py |
Class diagram
Dependency diagram
API
Classes
InterfaceTestGenerator
Line 15
Generate tests for Protocol and ABC classes.
| Method | Signature | Summary |
|---|---|---|
_param_default |
_param_default(param: ParamInfo) -> str |
|
_signature |
_signature(method: MethodInfo) -> str |
|
generate_for_protocol |
generate_for_protocol(protocol: ClassInfo) -> str |
Generate a concrete implementation + tests for a Protocol. |
generate_for_abc |
generate_for_abc(abstract_class: ClassInfo) -> str |
Generate a minimal concrete subclass + tests for an ABC. |
Source
"""Generate tests for Protocol and ABC classes.
Creates concrete implementations + pytest test stubs for protocols
and abstract base classes.
"""
from __future__ import annotations
from typing import TYPE_CHECKING
if TYPE_CHECKING:
from orchestrata_tools.models.ast_models import ClassInfo, MethodInfo, ParamInfo
class InterfaceTestGenerator:
"""Generate tests for Protocol and ABC classes."""
def _param_default(self, param: ParamInfo) -> str:
if param.type_annotation:
ann = param.type_annotation
# Container checks MUST run before the scalar substring checks:
# a generic like "list[str]" or "dict[str, int]" contains "str"/
# "int" as a substring of its element type, so checking scalar
# types first produced the wrong default (e.g. a `list[str]`
# param defaulted to '"test"' instead of "[]").
if "list" in ann or "List" in ann:
return "[]"
if "dict" in ann or "Dict" in ann:
return "{}"
if "str" in ann:
return '"test"'
if "int" in ann:
return "0"
if "float" in ann:
return "0.0"
if "bool" in ann:
return "True"
return "None"
def _signature(self, method: MethodInfo) -> str:
params = []
for p in method.parameters:
if p.name in ("self", "cls"):
continue
ann = f": {p.type_annotation}" if p.type_annotation else ""
default = f" = {p.default}" if p.default else ""
params.append(f"{p.name}{ann}{default}")
ret = f" -> {method.return_type}" if method.return_type else ""
return f"def {method.name}({', '.join(params)}){ret}:"
def generate_for_protocol(self, protocol: ClassInfo) -> str:
"""Generate a concrete implementation + tests for a Protocol."""
impl_name = f"_Concrete{protocol.name}"
lines = [
f"class {impl_name}:",
f' """Concrete implementation of {protocol.name} for testing."""',
"",
]
for method in protocol.methods:
if method.name.startswith("_"):
continue
sig = self._signature(method)
lines.append(f" {sig}")
lines.append(" ...")
lines.append("")
lines.extend(
[
"",
f"class Test{protocol.name}:",
f' """Tests for {protocol.name} protocol compliance."""',
"",
" def test_implements_all_methods(self):",
f" obj = {impl_name}()",
]
)
for method in protocol.methods:
if method.name.startswith("_"):
continue
params = ", ".join(
self._param_default(p)
for p in method.parameters
if p.name not in ("self", "cls")
)
lines.append(f" assert hasattr(obj, '{method.name}')")
lines.append(f" obj.{method.name}({params})")
lines.append("")
return "\n".join(lines)
def generate_for_abc(self, abstract_class: ClassInfo) -> str:
"""Generate a minimal concrete subclass + tests for an ABC."""
impl_name = f"_Concrete{abstract_class.name}"
lines = [
f"class {impl_name}({abstract_class.name}):",
f' """Minimal concrete implementation of {abstract_class.name}."""',
"",
]
for method in abstract_class.methods:
if method.is_abstract and not method.name.startswith("_"):
sig = self._signature(method)
lines.append(f" {sig}")
lines.append(" ...")
lines.append("")
lines.extend(
[
"",
f"class Test{abstract_class.name}:",
f' """Tests for {abstract_class.name} ABC compliance."""',
"",
" def test_can_instantiate(self):",
f" obj = {impl_name}()",
f" assert isinstance(obj, {abstract_class.name})",
"",
]
)
return "\n".join(lines)