From unity-coding-skills
Guides test case design for Unity projects: selects techniques, derives cases from requirements, and formats outputs. Activate when designing tests from specs.
How this skill is triggered — by the user, by Claude, or both
Slash command
/unity-coding-skills:test-designing-guideThe summary Claude sees in its skill listing — used to decide when to auto-load this skill
Guide for designing test cases for Unity projects.
Guide for designing test cases for Unity projects.
This skill requires the following inputs in its prompt:
| Input | Required | Description |
|---|---|---|
| Requirements | Required | The feature requirements to test against |
| Implementation design | Required | Class names, public method signatures, dependency interfaces, and design rationale |
| Existing code context | Optional | File paths and class summaries of relevant existing code |
Silently ignore the following if present in the prompt:
Read the requirements and identify testable specifications. If the specifications are unclear, use the AskUserQuestion tool to request clarification before proceeding. If the test target has low testability, flag it in the Testability Assessment (Section 7).
For each test target, determine which layer it belongs to based on its nature and integration level:
/Editor/), asset file validation, and cross-asset consistency checks.AddComponent<T>(), a prefab, or a scene. Unit tests cover targets whose execution is initiated by a direct method call; integration tests cover behavior that only emerges from Unity's component wiring.
Note: Never use Edit Mode tests for runtime code logic. Edit Mode and Play Mode test runners cannot execute simultaneously — splitting coverage for a single SUT between the two modes prevents running all tests at once. Play Mode tests can run on actual devices (player builds), which Editor tests cannot.
Prefer specification-based tests over structural (implementation-coupled) tests. Structural tests break under refactoring and lose value fast. It's fine to write a structural test temporarily when you're unsure about an implementation, but plan to delete it once specification tests cover the same behavior.
For each test target, select appropriate techniques:
AskUserQuestion tool to confirm the expected behavior before deriving test cases. Do not guess or invent the behavior.The most common derivation error is merging partitions with different expected outcomes into one (parameterized) method, encoding the difference as a condition in the name/Verification (e.g. ..._InteractableMatchesNonEmpty, "interactable is true only when non-empty"). One rule prevents it:
One test method = one equivalence partition = one definite expected outcome. Never parameterize the expected outcome. If the expected value changes with the input, the inputs belong to different partitions → separate test methods, each named after its own outcome.
For each input variable:
..._<Partition>_<ThatPartitionsOutcome>. The <Expected> segment is a concrete state/value (IsInteractable, IsNotInteractable), never a condition word (Matches…, …WhenNonEmpty, DependingOn…).Worked example — drawPileButton.interactable driven by the draw pile count:
| Partition | Representative values | Expected outcome |
|---|---|---|
Empty (count == 0) | 0 | interactable is false |
Non-empty (count ≥ 1) | 1 (boundary), 5 | interactable is true |
Two partitions, two outcomes → two methods (NOT one parameterized method over {0, 1, 5}):
| Test Method | Verification |
|---|---|
Sync_DrawPileIsEmpty_DrawPileButtonIsNotInteractable | the draw pile button is not interactable |
Sync_DrawPileIsExist_DrawPileButtonIsInteractable (count: {1, 5}) | the draw pile button is interactable |
The non-empty method keeps both 1 and 5 because they share the outcome true; 1 is the partition's boundary value, 5 an interior representative.
Boundary value analysis locates where partitions meet. For a field valid in 1–99: partitions <1 / 1–99 / >99; boundaries 0, 1 and 99, 100. Fold each partition's boundary representatives into that partition's parameterized method (e.g., valid partition over {1, 99}) — never one method per boundary. When the spec does not differentiate behavior near an edge, one representative per partition suffices and boundaries can be skipped entirely (per the Boundary value analysis technique above).
When an equivalence partition includes multiple test cases — such as argument variations within the same partition or boundary values at the partition's edges — consolidate them into a single parameterized test. All cases must belong to the same equivalence partition and share the same expected outcome.
Specifying parameter name and values in the Test Method column — write values after the method name:
(flag: (bool))(direction: (Direction))(param1: {0, 1, 2}, param2: {3, 4, 5}) — when a param is bool or enum covering all its values, use the shorthand in place of the braces: (flag: (bool), count: {0, 1, 5})({param1: 0, param2: 3}, {param1: 1, param2: 4})(use pairwise) — the test-writing phase applies the pairwise (all-pairs) method to select a covering combination setDo NOT over-consolidate: keep separate rows for tests that belong to different equivalence partitions or produce different expected outcomes.
AskUserQuestion to confirm with the user before designing test cases.When the SUT consumes a pseudo-random number generator (UnityEngine.Random, System.Random, etc.), choose one of these strategies based on what the spec actually pins down:
And/Range constraints, or Within/custom comparer for tolerances. Combine with Repeat attribute so flakiness isn't masked by a single lucky run.test-helper package (com.nowsprinting.test-helper) provides lightweight sampling helpers; reach for MathNet.Numerics only when you need rigorous statistics.Verify from the user's perspective — assert on-screen display and UI interactions as much as possible. Avoid relying on internal state or property checks when user-visible behavior can be asserted instead.
When the test target is a prefab, scene, or a GameObject composed of multiple components, consider the following test perspectives:
When the task type is bug-fix, additionally apply the following during technique selection:
For refactoring work, apply cover and modify: design regression coverage before changing the implementation. Treat every bug as an opportunity to grow the regression suite.
For each technique, derive coverage-aware test cases:
<MethodName>_<Condition>_<Expected> — the test target is a method, so include the method name.<Condition>_<Expected> — the test target is NOT a single method (it is a multi-component interaction or an on-screen rendering), so do NOT include a method name. Do NOT add a feature-area or category prefix before <Condition> — the name starts directly with the condition (e.g., OnVictoryForced_AllCardViewsAreWithinScreen, not Reward_OnVictoryForced_AllCardViewsAreWithinScreen).<Condition> segment is the equivalence partition name, not an enumeration of individual argument values.<Expected> segment names the concrete resulting state or value of that one partition (e.g. IsNotInteractable, ReturnsFizz), never a condition or comparison. Words like Matches…, OnlyWhen…, DependingOn…, BasedOn… in the name — or "only when / only if / depending on" in the Verification — signal that the outcome varies with input, meaning two partitions were merged into one method — split into one method per outcome (see Deriving test methods from equivalence partitions in Section 3).Expected segment of the test method name. Write from the information already available in the prompt (requirements and design inputs); abstract descriptions are acceptable — reading source to obtain more concrete identifiers is not required. Describe observable behavior only; do NOT include any of the following anywhere in the test case output — this prohibition applies to the Verification column, class header descriptions (text after #### ClassName), and any other field:
[Test], [UnityTest], [LoadScene], [Category(...)], [TakeScreenshot], etc.)PointerEventData, how to instantiate fixtures)LayoutAssert, GameObjectFinder)drawPileButton.interactable) whose owner is not present in the implementation design inputs — name the element descriptively instead (e.g., "the draw pile button is not interactable"). Exact identifiers are not required at design time; resolving the precise field name is a test-writing concern. You MAY name exactly: public framework API members (e.g., CanvasGroup.blocksRaycasts) and identifiers that appear in the design inputs.TestCase, Values, etc.) in the Test Method column — that's a test-writing decision.
FizzBuzz_MultipleOfThree_ReturnsFizz (n: 3, 6, 9) | Returns "Fizz"(uses spy: <TargetDependency>). Do NOT note stubs or fakes — those are arrange/action concerns. xUTP definitions for reference:
(saves screenshot for image analysis: element positions within screen, no overlap between elements, correct visibility state, text/background contrast).
<Condition> segment of the target method name — e.g., At960x540_RendersVersionLabelAtBottomRight (visual verification tests use <Condition>_<Expected>, with no method name). This makes each resolution a distinct, independently runnable test case.(saves screenshot for image analysis: ...) list when applicable:
| Style | Test Method | Verification |
|---|---|---|
| Bad (mechanism) | OnBeginDrag_WhenDragStarts_BlocksRaycastsIsDisabled | Call OnBeginDrag synchronously with [Test] and Assert that CanvasGroup.blocksRaycasts == false |
| Good | OnBeginDrag_WhenDragStarts_BlocksRaycastsIsDisabled | CanvasGroup.blocksRaycasts is disabled when drag starts |
| Bad (rationale) | StartRun_SameSeed_ProducesSameMap | The map structure is reproduced when a run is started twice with the same seed. Fails in the current implementation where RNG is not stored in the holder (BUG 1 reproduction test) |
| Good | StartRun_SameSeed_ProducesSameMap (reproduction test) | The map structure matches when a run is started twice with the same seed |
| Bad (parameterized) | Sync_GivenPhase_PanelVisible | The panel is visible only during the Defeat phase (verifies multiple argument patterns) |
| Good | Sync_GivenPhase_PanelVisible (phase: {Defeat, Victory}) | The panel is visible |
The Bad (mechanism) row leaks the test-writing mechanism (attribute choice, sync invocation, exact assertion form).
The Bad (rationale) row leaks why the test exists and what the current behavior is — none of that belongs in Verification; (reproduction test) goes in the Test Method column instead.
The Bad (parameterized) row hides the concrete argument values in a vague phrase in Verification; they belong in the Test Method column as named parameters, e.g. (phase: {Defeat, Victory}).
The Good rows state the observable behavior only; all other concerns go in the Test Method column or the test-writing phase.
The merged-partitions error — encoding two different expected outcomes into one method — deserves a dedicated example (see Deriving test methods from equivalence partitions in Section 3 for the full worked example with partition tables):
| Style | Test Method | Verification |
|---|---|---|
| Bad (merged partitions) | Sync_GivenDrawPileCount_DrawPileButtonInteractableMatchesNonEmpty (count: {0, 1, 5}) | the draw pile button is interactable only when the draw pile is non-empty |
| Good | Sync_DrawPileIsEmpty_DrawPileButtonIsNotInteractable | the draw pile button is not interactable |
| Good | Sync_DrawPileIsExist_DrawPileButtonIsInteractable (count: {1, 5}) | the draw pile button is interactable |
The Bad (merged partitions) row collapses two partitions — empty (→ not interactable) and non-empty (→ interactable) — into one method, betrayed by Matches… in the name and "only when" in the Verification. Split into one method per partition with a single definite outcome; parameterize only the same-outcome representatives (1, 5).
After completing Section 4, perform a traceability pass (acceptance tests coverage check) before writing the final output:
Output a coverage summary table only when gaps were found or a requirement was explicitly waived. Omit the table when all requirements are covered without exception.
| Requirement (from prompt) | Covering test(s) | Gap / Waiver reason |
|---|---|---|
| XXX should do Y | MethodName_ConditionA_DoesY | — |
| ZZZ must not allow W | (none) | Waived: prevented at a lower layer, not this class |
Finally, run a partition-split self-check on every test row that has parameter values in the Test Method column: for each listed parameter value, ask "does the expected outcome change when I substitute this value?" If any substitution produces a different expected outcome, those values belong to different partitions — split into one method per outcome, each with a single definite expected value, before producing final output. Reliable symptoms of a merged partition: Matches…, OnlyWhen…, DependingOn… in the <Expected> segment; "only when", "only if", or "depending on" in the Verification cell. (A plain temporal phrase such as "when drag starts" in Verification describes the test condition, not a varying outcome — it is not a symptom.)
Output must contain the following blocks in this order:
### Editor tests### Unit tests### Integration tests### Visual verification tests### Manual testsNote: All layers may contain
(none)when no test cases apply to that layer. Do NOT write "Edit Mode" or "Play Mode" in test case output — that is a test-writing concern, not a design concern.
Structure by layer:
#### <ClassName> → ##### <MethodName> → table#### <ClassName> → tableOutput markers — annotations that flag test case categories; append to the specified field only, not the Verification column:
(reproduction test) — append to the Test Method column when the test reproduces a reported bug (see Section 3, Reproduction tests)(acceptance test) — append to the Test Method column when the test is the same-layer witness (see Section 5) for a requirement stated in the prompt: it must directly exercise the behavior the requirement describes — not a component that contributes to satisfying it. A requirement about on-screen display or user interaction requires an integration or visual verification test; a requirement about method-level behavior may be witnessed by a unit test.(spec change) — append to the Test Method column ONLY when updating an existing test whose Verification (observable expected outcome) changes. A change to the SUT signature/type that requires only arrange/action construction updates (e.g., wrapping Foo→FooRef) while the expected observable behavior stays identical is NOT a spec change — leave such tests unmarked (construction details are a test-writing concern per Section 4, not a design concern). Litmus test: if the Verification column wording is unchanged, do NOT append (spec change).### Editor tests
#### <ClassName>
##### <MethodName>
| Test Method | Verification |
|--------------------------------------------------|---------------------------------------------------------------------|
| `Method_Condition_Expected` | `<property>` is `<expected value or state>` |
| `Method_Condition_Expected` (reproduction test) | `<property>` is `<expected value or state>` |
| `Method_Condition_Expected` (n: 3, 6, 9) | `<property>` is `<expected value or state>` |
### Unit tests
#### <ClassName>
##### <MethodName>
| Test Method | Verification |
|--------------------------------------------------|---------------------------------------------------------------------|
| `Method_Condition_Expected` | `<property>` is `<expected value or state>` |
| `Method_Condition_Expected` | `<property>` is `<expected value or state>` (uses spy: IDependency) |
### Integration tests
#### <ClassName>
| Test Method | Verification |
|--------------------------------------------------|---------------------------------------------------------------------|
| `Condition_Expected` | `<property>` is `<expected value or state>` |
### Visual verification tests
#### <ClassName>
| Test Method | Image analysis by saved screenshot |
|-----------------------|------------------------------------------------------------------------------------------------|
| `Condition_Expected` | <element positions, no overlap, text/background contrast> |
### Manual tests
| Test Case | Test perspectives / Verification method |
|-----------------------------|---------------------------------------------------|
| Brief description of item | <behavioral aspects to verify and how to confirm> |
After designing all test cases, evaluate and output a Testability Assessment at the end of your response.
Use one of the following labels:
| Label | Meaning |
|---|---|
TESTABILITY: PASS | All public methods are independently testable; test case count is realistic |
TESTABILITY: WARN | Localized concerns (e.g., too many test doubles, large integration tests, high FSM state combinations) |
TESTABILITY: FAIL | Fundamental testability issues that require design revision |
FAIL criteria — flag FAIL if any of the following apply:
new inside constructor, etc.)Output format for the assessment section:
### Testability Assessment
TESTABILITY: PASS
or, for WARN/FAIL, include Testability Issues with specific problem locations and proposed remedies:
### Testability Assessment
TESTABILITY: FAIL
#### Testability Issues
| Issue | Location | Proposed Remedy |
|--------------------------------------------------------|----------------------|----------------------------------------|
| Hidden state: `_score` modified by private method only | `GameManager._score` | Expose via read-only property or event |
| Static coupling: `Random.Range` called directly | `CardSelector.Pick` | Inject `IRandomSource` interface |
npx claudepluginhub nowsprinting/unity-coding-skills --plugin unity-coding-skillsProvides Unity test conventions: GameObjectFinder over Find, Operators over direct events, internal/Integration/Acceptance categories, and UNITY_INCLUDE_TESTS guards.
Guides choosing between plain C#, edit mode, play mode, and smoke testing for Unity features. Useful when adding tests or fixing slow/brittle suites.
Provides test design patterns, coverage strategies (80-100% targets), types (unit/integration/E2E), organization, and best practices for comprehensive test suites. Use for new suites, coverage improvement, or test design.