From Acceptance Criteria to Test Cases: A Two-Stage AI Review Workflow

Table of contents

The test case workflow covers writing test cases into the permanent suite. There’s a related but distinct problem: some tickets need their Acceptance Criteria in Jira itself enriched with concrete test cases – so a tester or stakeholder reading the ticket sees exactly what “done” means, without leaving Jira to go find a separate test file.

The naive version of this is one AI command: read the ticket, write full test cases, append them to Jira. The actual workflow deliberately splits that into two stages, because the naive version has an expensive failure mode – a reviewer disagreeing with the scope only after reading through fully-written test case bodies, steps and all, forcing a full rewrite over what was really just a scoping disagreement.

Genericised throughout – ticket IDs and field names below are placeholders, not real identifiers.

Why scope gets confirmed before anything expensive gets written

The fix is to make disagreeing about scope cheap. Stage one produces a one-line-per-scenario list – just enough for a reviewer to say “yes, test that” or “no, that’s out of scope” – with none of the step-by-step detail that stage two eventually adds.

flowchart TD
    Ticket["Jira ticket +\nAcceptance Criteria"] --> Scenarios["Stage 1: Scenario draft\n(one line per scenario, scope only)"]
    Scenarios --> Confirm{"BA confirms\nscope"}
    Confirm -- "scope changes needed" --> Scenarios
    Confirm -- "confirmed" --> Proposal["Stage 2: Full test case proposal\n(steps, preconditions, expected results)"]
    Proposal --> Approve{"BA approves\nproposal"}
    Approve -- "feedback" --> Proposal
    Approve -- "approved" --> Jira["Appended to Jira AC field\n(append only, never replace)"]

Each new scenario in stage one becomes exactly one new test case in stage two – a 1:1 mapping that makes “how big is this proposal going to be” predictable from the scenario count alone, before any of the expensive writing happens.

Reusing coverage instead of re-deriving it

Both stages need to know what already exists – which test cases already cover part of this ticket’s behaviour, so nothing gets proposed twice. The scenario stage builds that picture once, and the full-proposal stage explicitly inherits it rather than re-reading everything from scratch:

  Stage 1 (Scenarios) Stage 2 (Full proposal)
Reads the ticket + spec in full Yes Yes (needed for step-level detail)
Re-reads all existing test files for coverage Yes – builds the table No – inherits Stage 1’s table
Produces One-line scenarios, tagged Full step-by-step test cases
Output location Scenario drafts folder Test-case proposal folder
Gate BA confirms scope BA approves full proposal

Skipping the re-read in stage two isn’t just an optimisation – it’s a correctness guarantee. If both stages independently re-derived coverage, they could disagree with each other on a fast-moving ticket where a test file changed in between. Inheriting the same table means both stages are provably looking at the same picture of what already exists.

A tag can mean two different things depending on the stage

This is the single most easily-missed subtlety in the whole design: the same tag means something different depending on which stage produced it.

flowchart LR
    subgraph S1["Stage 1 — Scenarios"]
        direction TB
        n1["[TICKET] = brand-new scenario,\nwill become one new test case"]
        n2["[REGRESSION] / [SECONDARY] / [EDGE CASE]\n= reference to an existing test case,\nlisted for coverage visibility only"]
    end
    subgraph S2["Stage 2 — Full proposal"]
        direction TB
        n3["[TICKET] = every new test case\nwritten for this ticket, no exceptions"]
        n4["[REGRESSION] / [SECONDARY] / [EDGE CASE]\n= still just a reference —\nnever assigned to a new test case here"]
    end

In other words: every genuinely new test case is [TICKET], in both stages, always. The other three tags only ever point at test cases that already exist elsewhere in the permanent suite – they show up here purely so a reviewer can see the full coverage picture in one place, not because a new test case was just created with that label. Classification into [REGRESSION] / [SECONDARY] / [EDGE CASE] only happens later, when a test case is promoted into the permanent suite through a separate, deliberate review – never automatically, and never at proposal time.

Getting this backwards – tagging a brand-new test case [REGRESSION] because it feels important, for instance – would misrepresent it as something that already exists and is already part of the standing suite. The rule exists specifically to keep “new” and “existing” impossible to confuse at a glance.

Append-only, never replace

The last safeguard is about what happens at the very end, when an approved proposal actually touches Jira: it is appended to the ticket’s Acceptance Criteria field, never used to replace whatever’s already there.

This matters because the AC field is often where the original requirement lives, sometimes written by someone who is no longer on the project. Overwriting it in the name of “cleaning it up” would destroy the original intent behind the ticket. Appending preserves both: the human-authored original criteria, and the AI-drafted test cases that operationalise them – clearly marked as generated, with a date, so anyone reading the ticket later can tell the two apart at a glance.

Put together, the shape of the whole thing is: cheap scope agreement first, full detail only after that’s settled, coverage computed once and trusted twice, tags that never lie about whether something is new, and a Jira update that only ever adds. None of those individually are exotic ideas – the value is in applying all of them together to something that would otherwise tempt a much simpler, much riskier one-shot design.

Images Required

None for this article — it’s diagram- and table-based throughout.

Until next time, peace and love!


2024 Shafik Walakaka. Please submit feedback and comments here!