Skip to content

Testing

hexa ships a zero-mock pytest suite that exercises the real sample pipelines. The same fixtures make great templates for testing your own pipelines against the container.


๐Ÿงช Running the Suite

Bash
python -m pytest tests -q
# expected: all pass (tests degrade gracefully when samples are absent)

The suite is fully deterministic: no network, no filesystem mocks, no dependency on external fixtures. A couple of tests guard on sample-file existence and skip cleanly otherwise, so the suite stays green in minimal checkouts.


๐Ÿ“ฆ What's Covered

Module What it proves
test_model.py PortNode, MethodSpec, nested attributes equality & helpers.
test_parse_yaml.py YAML โ†’ tree: minimal spec and the extraction-pipeline grammar (attributes / nested structs / ports / methods:).
test_parse_abc.py ABC module โ†’ tree: reflection from __annotations__ + @abstractmethod.
test_check_matches.py Two-representation agreement and ValueError on unknown extensions.
test_roundtrip.py Full circle fidelity: parse_yaml โ†’ generate_abc โ†’ parse_abc โ†’ generate_yaml โ†’ parse_yaml.
test_container.py build behavior: wiring, instances=/config= injection, defaults.

๐Ÿ” The Round-Trip Tests

The strongest guarantee lives in test_roundtrip.py โ€” the same tree must survive a full circuit and stay equal, not just lossless:

  • test_yaml_to_abc_to_yaml โ€” parse samples/minimal/sample.yaml, generate ABC source, parse it back, re-emit YAML, parse again; the roots and child counts match.
  • test_nested_struct_roundtrip โ€” a nested struct attribute (source) survives YAML โ†’ ABC โ†’ YAML unchanged:
    Python
    node1 = parse_yaml(yaml_path)
    assert isinstance(node1.attributes.get("source"), dict)
    ...
    node2 = parse_abc(tmp_abc)
    assert node1.attributes["source"] == node2.attributes["source"]
    assert node1.diff(node2) == []
    
  • test_abc_to_yaml_to_abc โ€” the mirror circuit starting from samples/minimal/abc.py.

These are the tests to keep green when you add a new grammar feature or a new converter.


๐Ÿ—๏ธ Container Tests

test_container.py pins the four injection contracts:

  1. Wiring with no overrides (test_build_without_overrides_is_unchanged) โ€” build(_Root) constructs every annotated slot from its type and keeps class defaults.
  2. Runtime injection (test_build_with_instances_and_config) โ€”
    Python
    root = build(
        _Root,
        instances={"repo": repo},
        config={"retries": 3, "label": "fast", "threshold": 2.5},
    )
    assert root.repo is repo            # injected verbatim
    assert root.mid.threshold == 2.5    # config propagated by name into nested nodes
    
  3. Typing-generic config fields (test_config_applies_to_typing_generic_annotations) โ€” str | None/list | None fields take config values and leave unset ones at their defaults.
  4. Optional defaults (test_config_optional_field_defaulted_to_none_when_unprovided) โ€” unprovided optional fields default to None.

The _Root/_Mid/_Leaf fixture classes live right in the test file โ€” a great minimal template for testing your own annotated pipelines.


๐Ÿ’ก Tips

  • Keep fixture classes small and place them in the test module (like _Root, _Repo, _OptionalConfig). No fixtures framework needed for the container tests.
  • Add hexa check <spec.yaml> <abc.py> to CI as a live example of the guard, then assert on the return with is True.
  • When extending the YAML grammar, add a case to both test_parse_yaml.py and a round-trip test โ€” if the round-trip holds, the converter pair is consistent by construction.