migrate-harness-tests-to-state-transition-test
Installation
SKILL.md
Migrate Harness Tests To State Transition Test
Overview
Guide charm maintainers through replacing deprecated Harness-based unit tests with deterministic state-transition tests that mirror how Juju actually fires events. Use this skill whenever a test still instantiates testing.Harness or chains multiple events inside one fixture.
When To Use
ops.testing.Harness(or Scenario Harness helpers) still appear in tests or fixtures.- Migration requires modeling relations, containers, secrets, or actions with
testing.State. - CI must stay green while Harness-only helpers (
add_relation,evaluate_status, etc.) are removed.
Preflight Checks
- Inspect
pyproject.toml(or requirements files) before touching code; ensureops[testing]is present in every dependency group that executes Scenario/state-transition tests. Remove legacyops-scenarioif it lingers. - Match the repo’s package manager: use
uv add --group unit ops[testing](and mirror across groups) when[tool.uv]oruv.lockexists; otherwise edit the appropriaterequirements.txt, Poetry group, etc. - After dependency edits, re-sync the exact groups you touched (
uv sync --group unit --group lint,poetry install, etc.) so the virtualenv contains Scenario Context. - Re-run the project’s bootstrap entrypoint (
uv sync,pip install -r requirements.txt,make setup, …) to align local + CI execution before rewriting tests.
Mandatory Plan & Ledger
Create a written task list before editing files and keep it updated:
- Record the migration goal (e.g., "Drop Harness from tests/unit/test_charm.py") plus all files involved.
- Add explicit tasks for every preflight requirement: dependency inspection, disposable venv creation, environment sync commands, bootstrap targets.
- Inventory Harness usage with the detector script, note each file’s assertions, and migrate files one by one in ledger order.
- Pair every code-change task with an immediate verification task (targeted pytest/tox command). Log the command, exit code, and timestamp when marking done.
- If a command fails, append remediation + re-run tasks; do not advance to the next file until the paired verification succeeds.
Repository Tooling Notes
- Prefer tox over ad-hoc venvs when
tox.tomlwires environments touvrunners; it keeps CI parity. - Keep dependency groups consistent (fmt, lint, unit, integration, …). When
ops[testing]is added to one group, mirror it across any environment that imports Scenario helpers. - Check helper targets in
Makefile/tox.tomlbefore running bespoke setup commands—they often wrap the correct bootstrap flow.
End-to-End Workflow
0. Pick the migration style
- Reuse existing fixtures/builders (for example
PenpotStateBuilder,create_state) and port their internals to Scenario primitives when available. - Inline tiny
Stateobjects only when Harness usage was minimal; large charms benefit from shared factories so relations/secrets stay consistent.
1. Inventory Harness usage
- Run
scripts/find_harness_tests.pyagainsttests/and categorize hits by surface area (actions, relations, Pebble, status). - Copy the current assertions/story so you can verify behavioral parity after rewriting.
2. Define the event boundary and states
- For each Harness test, map the single Juju event being exercised and document the expected input/output state using the migration checklist.
- Pre-populate
testing.Statewith relations, containers, config, leadership, secrets, and network data—Scenario never replays incremental Harness mutations. - Ensure collect-status side effects have the necessary mock containers/layers/service statuses.
3. Rewrite the test
- Follow state-transition-recipes.md to express one event per test.
- Invoke
ctx.run(ctx.on.<event>(...), state_in)and assert onstate_out,ctx.action_results, emitted statuses, and monkeypatched workload helpers. - Duplicate Harness failure coverage with
pytest.raises(testing.ActionFailed)or other exception checks; add separate tests for each branch instead of branching inside one function.
4. Validate and retire Harness
- Run the narrowest possible selection (e.g.,
tox -e unit -- tests/unit/test_charm.py) right after editing each file; log the result in the ledger. - Delete Harness fixtures/imports once a file is fully migrated, then rerun the detector to confirm it no longer reports hits.
- Finish with the repo’s canonical unit-test target (
tox -e unit,pytest tests/unit) and only mark the migration complete when it returns 0 with zero Harness matches.
Reusable Resources
scripts/find_harness_tests.py: Recursively scans the provided paths fortesting.Harness/Scenarioreferences. Usage:python3 .github/skills/migrate-harness-tests-to-state-transition-test/scripts/find_harness_tests.py tests/unit tests/integration
references/migration-checklist.md: Step-by-step plan that maps every Harness helper to its state-transition counterpart and reminds you to satisfy collect-status requirements.references/state-transition-recipes.md: Ready-to-paste snippets for actions, relation-changed, Pebble-ready, update-status, and failure cases.- Canonical guide: How to migrate unit tests from Harness (authoritative reference for nuanced behaviors).
Event Playbooks (quick cues)
- Actions:
ctx.on.action("name", params=...); assert onctx.action_resultsand expected failures (testing.ActionFailed). Provide container data when_on_collect_statusinspects Pebble. - Relation changes: Reuse the same
testing.Relationinstance in bothStateandctx.on.relation_changed(...); populate remote/local databags with the "new" data that triggered the event. - Pebble / containers: Supply
testing.Containerobjects with realisticlayers,service_statuses, andcan_connectflags. Inspect the output container viastate_out.get_container(name). - Status reporting: Prefer
ctx.on.update_status()to cover_on_collect_status; create success and failure variants (inactive service, disconnected container, missing plan).
Example Prompts
- "List the files that still use
testing.Harnessso I can migrate them to state-transition tests." - "Rewrite
tests/unit/test_charm.py::test_relation_changedusing the relation recipe and show the newtesting.State." - "Explain what additional state I need to provide so
_on_collect_statusstops failing inside the new tests."
Quality Bar
- Every migrated file passes
scripts/find_harness_tests.pywith zero hits. - Each test focuses on a single Juju event with clearly documented input and output state.
- Collect-status logic has both success and failure coverage.
- The final diff removes Harness imports and helpers in addition to adding state-transition tests.
- Repository ledger shows paired code/verification tasks with recorded command outputs and remediation notes for any failures.
- Final
tox -e unit(or repo equivalent) succeeds after dependency sync and Harness removal.
Weekly Installs
1
Repository
canonical/copil…lectionsGitHub Stars
19
First Seen
Mar 27, 2026
Security Audits