| name | testing |
| description | How to write and run fast, deterministic, behavior-focused pytest tests that never touch real external systems. Use whenever adding or changing behavior, or fixing a bug. |
| generated | true |
| source | .ai/skills/testing.md |
Skill: Testing
Purpose
Define how to write and run tests for this Python project so that every
meaningful behavior is covered by fast, deterministic, behavior-focused tests
that never touch real external systems.
When to Use This Skill
- Adding or changing any behavior (tests accompany behavior — almost always).
- Fixing a bug (add a regression test first).
- Working on the test suite or quality gate (task
0012).
- Reviewing whether a change is adequately tested.
Files to Read First
AGENTS.md
docs/DEVELOPMENT_WORKFLOW.md
pyproject.toml ([tool.pytest.ini_options])
- The skill for the area under test (runtime, prompts, plugins, tools, etc.).
Core Principles
- Use
pytest. Tests live under tests/, mirroring src/agentplatform/.
- Test behavior through public interfaces, not private implementation
details (test internals only when there is no public seam).
- No real external systems in unit tests: never call real LLMs, real MCP
servers, real databases, or third-party APIs. Mock/fake them.
- Integration tests only when explicitly requested, and still without real
secrets or live third-party calls — use fakes/local stand-ins.
- Deterministic: control time, randomness, and ordering.
- Regression tests for bugs: every fixed bug gets a test that fails before
the fix.
Process
- Pick the category (see below) for what you are testing.
- Arrange with fixtures for repeated setup (specs, compiled graphs, fake
resolver/access plugins, fake tools). Use
@pytest.mark.parametrize for input variations.
- Mock external boundaries (LLM/MCP/DB/HTTP) at the adapter seam, not deep
inside.
- Write behavior assertions: given input, assert observable output/effects.
- Cover negatives and security: invalid YAML/validation errors, missing
required prompt variables, denied protected-node access, plugin errors.
- Run
make test (and make check for the full gate). Fix failures.
Testing categories (use the right one)
- Unit tests — a single function/class via its public interface.
- Integration tests — multiple layers together (e.g. validate → compile →
run) with fakes; only when requested.
- Contract tests — verify plugin contracts and the YAML schema match
docs/; catch drift early.
- Golden example tests — a known YAML config produces a known compiled
graph / rendered prompt; update goldens deliberately.
- Negative tests — invalid specs, missing variables, denied access must
fail clearly with the right error.
- Security tests — protected access fail-closed, secrets redacted, no
request-state leakage.
Example pytest structure (illustrative, not product tests)
import pytest
@pytest.fixture
def valid_spec_dict() -> dict:
return {"version": "1.0", "app": {"name": "demo"}, "...": "..."}
@pytest.mark.parametrize("missing_key", ["version", "app", "runtime"])
def test_validation_reports_missing_top_level_key(valid_spec_dict, missing_key):
data = dict(valid_spec_dict)
del data[missing_key]
result = validate_spec(data)
assert not result.ok
assert any(missing_key in e.message for e in result.errors)
Do not implement real product tests in this task. Only trivial repository
smoke tests (e.g. import + version) belong here until task 0001+ create code.
Checklist Before Finishing
Common Mistakes to Avoid
- Calling real LLMs/MCP/DBs/APIs in unit tests.
- Asserting on private attributes/log strings instead of behavior.
- Flaky tests depending on real time, randomness, or ordering.
- Over-mocking until the test asserts nothing meaningful.
- Skipping negative/security tests because the happy path passes.
- Committing secrets or production endpoints in fixtures.
Expected Final Report
State: which test files/categories were added or changed; what behaviors and
edge/negative/security cases are now covered; mocks/fakes used for external
boundaries; the result of make test / make check; and any coverage gaps
intentionally left for a later task.