- name
- jsdlc-unit-test
- description
- Write unit tests for code that already exists, in a Japanese waterfall project — read the source, enumerate its real branches, produce a UT specification and the test code, and run them. Use when the user asks to add unit tests to existing or legacy code, 既存コードの単体テスト, backfill tests, raise coverage, or test a module that has no detail design.
# Unit Tests for Existing Code (既存コードの単体テスト)
Request / arguments: $ARGUMENTS
Mode is the first word: `create` (default), `review`, `modify`.
The rest names what to test: a file, a class, a package, or a design ID.
Read before acting:
- the source files under test — this skill's primary input
- `docs-jsdlc/templates/test-case.md`
- `docs-jsdlc/rules/review-checklist.md` §2 and §6
- the project profile `.kiro/steering/jsdlc-project-profile.md` §2–§4 (source
and test roots, framework, mocking approach, test command, trace tag). A
`<...>` placeholder in one of those stops this skill: report it, raise
`Q-NNN`, write no tests
---
## How this differs from `jsdlc-implement`
| | `jsdlc-implement` | this skill |
|---|---|---|
| Primary input | an approved detail design | the existing source code |
| Design required | yes — no parent, no code | no — optional evidence |
| Writes product code | yes | **never** |
| Produces | product code + its tests | UT specification + test code |
Use `jsdlc-implement` when the code is being written now from a design. Use this
skill when the code is already running and has no tests, or too few — which is
the ordinary case on a system under maintenance.
**This skill never edits product code.** Not to make it testable, not to fix a
bug the tests expose, not to add a seam. Changing the code under test in the
same breath as writing its tests destroys the only evidence the tests provide:
that they pass against the code as it stands today. When the code cannot be
tested as written, or a test exposes a defect, that is a finding and a `Q-NNN`,
and the change goes through `jsdlc-implement` afterwards.
---
## The trap in this work
Tests written from code alone cannot tell correct behaviour from current
behaviour. Read a branch, write a test asserting it does what it does, and the
test passes — while proving nothing except that the code has not changed. A bug
gets frozen into an assertion and the next person treats it as the contract.
So the whole skill turns on one distinction, kept visible in the output:
- **Confirmed** — the expected value comes from a detail design, a basic design
message list, an API spec, a client sheet named in profile §8, or a decision
recorded in the open-questions register. This is a real test.
- **Characterisation** — the expected value comes only from reading the code.
It locks in today's behaviour so a refactor cannot change it silently. It is
useful, and it is not a correctness proof.
Every case carries one of the two. Never silently promote a characterisation
case to confirmed because the behaviour looked reasonable.
---
## Mode: create
### Step 1 — Establish what evidence exists
Search for a detail design covering this code, by trace tag (`@design DD-...`
in the profile §4 form) and by ID. Record which of these you have:
- detail design → expected values are Confirmed; follow Step 2b as well
- basic design message list → error codes are Confirmed
- API spec → the boundary contract is Confirmed
- nothing → every case is Characterisation until a person decides otherwise
Say which, in one line, at the top of the report. A reader's first question is
how much of this is evidence and how much is a snapshot.
### Step 2 — Enumerate the code's real behaviour
Read the code completely before writing anything. Enumerate, mechanically —
this list is the coverage obligation, and it comes from the code, not from
imagination:
- every branch: each `if` / `else` / `switch` arm, each ternary, each
short-circuit that changes the outcome
- every loop: the zero-iteration, one-iteration and many-iteration paths
- every `throw` / `raise` / error return, with the code or type it carries
- every early return, and the condition that reaches it
- every caught exception, and what the handler actually does — a swallowed
exception is a finding, whether or not it is designed
- every nullable or optional parameter: null / empty / zero behaviour as the
code treats it today
- every numeric or temporal comparison, with its inclusivity as written
(`<` vs `<=`) — this is where characterisation tests earn their keep
- every external call to be mocked: what the code does with success, with each
failure, and with an unexpected value
Count them. The count of error exits in the code is the minimum number of
異常系 cases, exactly as it is in `jsdlc-implement` Step 3.
### Step 2b — Reconcile with the design, when one exists
Compare the enumeration against the design, both directions:
- in the design, absent from the code → the code is incomplete
- in the code, absent from the design → over-implementation (I-04)
- in both, but disagreeing on a value, a code, or an inclusivity → a mismatch
**Do not choose a side.** Each difference is a `Q-NNN` naming both readings and
the provisional one, exactly as `jsdlc-implement` Mode: modify step 3 requires.
Write the test against the code's behaviour, mark the case Characterisation, and
cite the question in the case's note. When the question is decided, the case is
revisited — that is what `modify` mode is for.
### Step 3 — Write the UT specification
Use `docs-jsdlc/templates/test-case.md`, phase UT. Build it from the Step 2
enumeration: viewpoints (テスト観点) grouped into 正常系 / 異常系 / 境界値, then
one row per case with precondition, operation, expected result.
The template's §4 note covers the two adjustments this phase needs: `Target`
holds the source location when there is no design item to cite, and each case
table carries an `Evidence` column — `Confirmed (DD-RSV-001 §7)` or
`Characterisation`. Fill both on every case.
Quality bars are the ordinary UT ones (`review-checklist.md` §6): expected
results are observable and concrete (T-03, T-04), every error exit appears in a
異常系 case (T-05), every limit gets below / at / above (T-06).
Trace: when a design exists, each case cites its `DD-<DOM>-<nnn>` item. When one
does not, the case cites the source location instead, and the coverage table
(T-02) is against the enumeration rather than against design items. Say so in
the document — a coverage table with no stated basis is worthless.
Save to `docs-jsdlc/output/test-case/UT-<DOM>-<nnn>-<slug>.md`. Codes come from
`docs-jsdlc/project/domain-codes.md` and are fixed once assigned (profile §6).
When this code covers an area with no domain code, that file is edited first —
raise `Q-NNN` naming the area and stop; never invent a code to get a filename.
### Step 4 — Write the test code
Test root and naming come from profile §2; framework, mocking approach and test
command from profile §3.
Follow the test style already in the repository over any style you prefer
(I-06). If no tests exist at all, follow the framework's ordinary convention and
say in the report that you set the precedent.
- Each test name carries its case ID, so the mapping to the specification is
mechanical.
- Characterisation cases are marked in the code too — a comment carrying the
case ID and the word `characterisation`, so a later reader knows the assertion
is a snapshot, not a contract.
- Mock only what profile §3 says is mocked. Reaching a real database or network
in a UT makes it a different test phase.
- No test depends on another's order or leftovers (T-08).
- Never weaken an assertion to make a test pass.
### Step 5 — Run them
Run the test command from profile §3. Report the actual output.
A failing test at this point means one of three things, and which one matters:
1. the test is wrong → fix the test
2. the code has a defect → **leave the test failing**, raise `Q-NNN`, report it
as a defect found. Do not adjust the assertion to green, and do not fix the
code here
3. the code contradicts the design → Step 2b, a mismatch, not a fix
Report coverage if profile §3 names a coverage command — run that command, do
not estimate. Where §3 states a coverage expectation, say whether the run
meets it. Report the number,
not an impression, and state what it was before if that is known.
### Step 6 — Report
- What evidence existed (Step 1), in the first line
- Enumerated items: branches, error exits, boundaries — the counts
- Cases written, split Confirmed / Characterisation
- Test results: passed, failed, and for each failure which of the three kinds
- Coverage before and after, if measured
- Every `Q-NNN` raised, and every defect found in the code under test
A run that produces mostly Characterisation cases is a normal outcome on legacy
code. Report it plainly. It is not a weaker result — it is an accurate one.
---
## Mode: review
Do not edit. Review existing unit tests against the code they cover, in both
directions:
**Code → tests:** every branch, error exit and boundary from a fresh Step 2
enumeration has at least one case. Gaps are the finding, listed by source
location.
**Tests → code:** every test still corresponds to code that exists. A test for a
deleted branch, or one passing because it asserts nothing meaningful, is a
finding. So is a test whose assertion was weakened to pass — check the history
when it is available.
Also check: assertions are concrete (T-04); Characterisation cases are marked as
such, and none has been quietly promoted to Confirmed; mocks match profile §3;
no test depends on execution order (T-08); tests actually run under the profile
§3 command.
Run the tests as part of the review and report the real result.
Write to `docs-jsdlc/output/review/UT-<DOM>-<nnn>-review-<YYYYMMDD>.md`.
---
## Mode: modify
1. Fix every NG from a review. Each change traces to a finding number.
2. When a `Q-NNN` from Step 2b has been decided, revisit the cases citing it:
the expected value becomes the decided one, the case becomes Confirmed, and
the question row is closed with the decider and date. If the decision means
the code is wrong, the test now fails — that is correct, and the code change
goes through `jsdlc-implement`, not here.
3. Never delete a failing test to close a finding. A test that fails because the
code is defective stays, with its `Q-NNN`.
4. Re-run the profile §3 test command and report the real result against the
previous one.
5. Report: findings fixed, questions closed, cases promoted from Characterisation
to Confirmed, and test results before and after.
GitHub에서 보기