一键导入
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.