بنقرة واحدة
harness-migration
Migrating deprecated ops.testing.Harness tests to state-transition (Scenario) tests
التثبيت باستخدام Codex أو Claude انسخ هذا Prompt والصقه في Codex أو Claude أو مساعد آخر ليراجع صفحة Skill ويثبّتها لك.
القائمة
Migrating deprecated ops.testing.Harness tests to state-transition (Scenario) tests
التثبيت باستخدام Codex أو Claude انسخ هذا Prompt والصقه في Codex أو Claude أو مساعد آخر ليراجع صفحة Skill ويثبّتها لك.
استنادا إلى تصنيف SOC المهني
Known-good Juju bundle shapes (COS Lite, 12-Factor + COS, Identity Platform, Charmed Kubeflow) — canonical app lists and relation edges
Implementing Juju actions for operational tasks in charms. WHEN: add Juju actions to a charm, implement backup / rotate-credentials / restore actions, declare actions in charmcraft.yaml, write action handlers, test actions with Scenario, run actions with juju run.
Adding and validating charm configuration options. WHEN: add charm configuration options, declare config in charmcraft.yaml, validate config values in config-changed, apply config to a Pebble layer, apply config to a machine charm, write Scenario tests for config.
End-to-end workflow for building ops-framework charms for custom applications (K8s and machine). WHEN: build a custom Juju charm, write src/charm.py with Pebble, write a machine charm with systemd, decide K8s vs machine substrate, scaffold a non-paas-charm with charmcraft init, customise the ops framework charm lifecycle.
Workflow for charming infrastructure software (databases, caches, message brokers, proxies, monitoring). WHEN: charm infrastructure software, charm a database / cache / message broker / proxy / monitoring system, implement peer relations, leader election, primary/replica failover, backup and restore actions, clustering.
Expert guidance for developing, building, testing, and publishing Juju charms using charmcraft. WHEN: edit charmcraft.yaml, run charmcraft pack, run charmcraft init, manage charm libraries, publish a charm to Charmhub, set up multi-base builds, configure relations / config options, set up storage or containers.
| name | harness-migration |
| description | Migrating deprecated ops.testing.Harness tests to state-transition (Scenario) tests |
| globs | ["tests/unit/**","tests/test_*.py"] |
Use this skill when an existing charm's unit tests still use ops.testing.Harness
and need to be rewritten as state-transition tests with ops.testing.Context /
State. Harness is deprecated; new charms scaffolded by cantrip already use
Scenario (see the scenario-tests skill).
charm_audit reports Harness usage, or the user asks to modernise tests.testing.Harness,
ops.testing.Harness, or calls Harness(...) directly.For writing a fresh Scenario suite from scratch, load scenario-tests instead.
Run the harness_inventory tool to enumerate remaining Harness usages. It
walks tests/, applies the upstream copilot-collections detector regex,
and returns a per-file checklist with harness / scenario counts and a
mixed flag for files that import both. Re-run after each migrated file
to watch the count drop.
pyproject.toml — ops[testing] must be in every dependency group
that runs unit tests. Remove any lingering ops-scenario package (it has
been folded into ops[testing]).uv sync --group <group>) so the venv has the
Scenario Context.Harness mutates state imperatively across many calls. Scenario builds the entire input state up front, fires one event, and asserts on the output.
| Harness | Scenario replacement |
|---|---|
Harness(MyCharm) + harness.begin() | ctx = testing.Context(MyCharm) |
harness.set_leader(True) | testing.State(leader=True) |
harness.update_config({...}) before emit | testing.State(config={...}) |
harness.add_relation(...) | testing.Relation(endpoint=..., remote_app_data=...) in State.relations |
harness.container_pebble_ready(...) | testing.Container(..., can_connect=True) in State.containers |
harness.charm.on.<event>.emit(...) | ctx.run(ctx.on.<event>(...), state_in) |
harness.run_action("name", params) | ctx.run(ctx.on.action("name", params=...), state) — read ctx.action_results |
harness.evaluate_status() | Fires automatically after every ctx.run(...) |
Each migrated test should exercise one Juju event. If a Harness test chained
several events, split it into multiple tests or feed state_out of one call
into the next ctx.run(...).
from ops import testing
def test_backup_action():
ctx = testing.Context(MyCharm)
state_in = testing.State(containers={testing.Container("workload", can_connect=True)})
state_out = ctx.run(
ctx.on.action("backup", params={"path": "/data"}),
state_in,
)
assert ctx.action_results == {"status": "success"}
assert state_out.unit_status == testing.ActiveStatus()
For failure paths:
import pytest
with pytest.raises(testing.ActionFailed) as exc:
ctx.run(ctx.on.action("backup", params={"path": "/bad"}), state_in)
assert "permission denied" in exc.value.message
rel = testing.Relation(
endpoint="database",
remote_app_data={"endpoints": "db.local:5432"},
)
state_in = testing.State(relations={rel})
state_out = ctx.run(ctx.on.relation_changed(rel), state_in)
Put the post-change data directly into remote_app_data; Scenario never
replays incremental updates.
container = testing.Container("workload", can_connect=True)
state_in = testing.State(containers={container})
state_out = ctx.run(ctx.on.pebble_ready(container), state_in)
assert "workload" in state_out.get_container(container.name).plan.services
Inspect the container on state_out.get_container(name) — the object in
state_in is immutable.
_on_collect_status runs implicitly after every ctx.run. Provide the state
its handlers inspect (containers, layers, service statuses), and add negative
variants:
layer = pebble.Layer({"services": {"workload": {"command": "run", "startup": "enabled"}}})
active = testing.Container(
"workload",
layers={"base": layer},
service_statuses={"workload": pebble.ServiceStatus.ACTIVE},
can_connect=True,
)
down = testing.Container("workload", can_connect=False)
def test_status_active():
state_out = ctx.run(ctx.on.update_status(), testing.State(containers={active}))
assert state_out.unit_status == testing.ActiveStatus()
def test_status_waiting_when_disconnected():
state_out = ctx.run(ctx.on.update_status(), testing.State(containers={down}))
assert isinstance(state_out.unit_status, testing.WaitingStatus)
ctx.on.*.state_in with everything the handler (and _on_collect_status)
needs.ctx.run(...). Assert on state_out, emitted
statuses, ctx.action_results, and monkeypatched workload helpers.run_charm_tests with test_type: "unit" and
test_path: "tests/unit/<file>.py".After every file is clean, run charm_validate to confirm unit tests pass,
coverage holds above the 80% floor, and packing still succeeds.
tests/ returns no hits.ops[testing] is in all unit-test dependency groups; ops-scenario is gone._on_collect_status covered with at least one
success and one failure path when it has branches.charm_validate passes.scenario-tests skill — patterns for writing new Scenario suites.