
Beyond Spec-Driven Hype: Test-Driven Development with AWS Kiro
A deep Dive on Test Driven Development
The companion piece to this one used Claude Code's subagent pattern to enforce TDD structurally — a test-writer that never sees implementation, an implementer that can't touch test files, permission hooks instead of prompt instructions. Kiro takes a different route to a similar destination, and it's worth treating as its own pattern rather than "Claude Code with extra steps," because the enforcement mechanism is genuinely different: Kiro doesn't rely on subagent role separation, it relies on making you commit to a written spec before code generation is even possible.
A quick grounding on what Kiro actually is, since it's moved fast enough that a lot of write-ups about it are already stale. Amazon launched Kiro internationally on May 7, 2026 as a ground-up replacement for Amazon Q Developer, built on Code OSS — the open-source base of VS Code — so existing extensions, themes, and keybindings carry over. It runs on Bedrock-hosted Claude models — Sonnet 4.6 and Opus 4.6 are GA, with Opus 4.7 available experimentally. Amazon Q Developer itself reaches end of support on April 30, 2027, so if you're on an AWS-standardized team, this isn't a speculative bet — it's the direction AWS has already committed to.
Why spec-driven development and TDD are pointed at the same problem
Amazon's stated rationale for Kiro is that agentic coding without formal structure produces code that works locally but drifts from architecture, misses edge cases, and falls apart under production load. That's a description of exactly the failure mode TDD exists to prevent, just aimed at the planning layer instead of the code layer. TDD forces you to specify behavior before implementation exists. Kiro's spec workflow forces you to specify requirements, design, and a task breakdown before implementation exists. They're the same discipline, applied one layer apart — and the interesting part of this article is where they compose and where they don't.
Every Kiro spec follows a three-phase workflow: requirements, design, then implementation, with each phase producing a versioned artifact — a folder containing requirements.md, design.md, and tasks.md, with the AI not jumping straight to code. Kiro then builds a dependency graph of the tasks in tasks.md and groups independent tasks into waves — Wave 1 runs everything with no dependencies concurrently, Wave 2 runs everything whose dependencies were satisfied by Wave 1, and so on, with waves executing sequentially but tasks within a wave executing concurrently. That wave structure matters for how you should sequence test-writing tasks against implementation tasks, which I'll get into below — it's not automatic, you have to design the task graph so tests land in an earlier wave than the code that should satisfy them.
TDD vs. spec-driven development: what's actually different
Worth being precise about this before going further, because "they're basically the same discipline" undersells what spec-driven development adds. TDD and SDD operate at different altitudes and answer different questions — TDD is a unit of code discipline, SDD is a unit of feature discipline, and Kiro's contribution is putting a durable, versioned artifact at the layer above where TDD normally starts.
| Test-Driven Development | Spec-Driven Development (Kiro) |
|---|---|---|
Unit of work | A function or class contract | A feature, end to end — requirements through deployment |
What gets written first | A failing test | A requirements document, in EARS notation |
Where ambiguity gets caught | At the test-assertion level, once you're already coding | At the requirements-analysis step, before any code or even any test exists |
Artifact that survives after merge | The test suite | requirements.md, design.md, and tasks.md — committed, versioned, reviewable by non-engineers |
Scope of "done" | Function behaves as specified | Feature matches architecture, acceptance criteria, and task plan all at once |
Who can review the spec | Engineers reading test code | Product owners, compliance, and architects reading a PRD-shaped document |
Failure mode it targets | Code that technically runs but doesn't do what's needed | Code that works locally but drifts from the agreed architecture or misses a requirement entirely |
The practical upshot: TDD is what happens inside a Kiro implementation task, not a replacement for it. Kiro's requirements.md is answering "what should this feature do, and have we thought it through," which is a question TDD never asks — TDD assumes you already know the contract and starts from there. Put them together and you get requirement-level rigor and function-level rigor in the same workflow, which is a stronger guarantee than either discipline produces alone. That's the real value of running TDD on top of a Kiro spec rather than picking one: the spec gets the requirement right before a single test is written against it, and Kiro is, as far as I've seen, the first mainstream IDE to make that layering a first-class, versioned part of the workflow instead of a wiki page nobody updates.
The same S3 upload client, through Kiro's spec workflow

Phase 1 — requirements.md, written in EARS notation
Kiro's requirements.md uses EARS (Easy Approach to Requirements Syntax) notation to produce structured, testable requirements, with each requirement following the pattern WHEN [condition/event] THE SYSTEM SHALL [expected behavior]. This is worth pausing on, because it's structurally close to a Gherkin/BDD given-when-then, and it's a genuinely good fit for TDD's red phase — an EARS requirement is close to executable as-is.
For the S3 upload client:
# requirements.md
## Requirement 1: Transient failure retryWHEN uploadPart returns a 500, 503, or RequestTimeout error,THE SYSTEM SHALL retry with exponential backoff and full jitter, base 200ms, capped at 5s per attempt.
## Requirement 2: Retry exhaustionWHEN 8 total attempts have been made OR 60 seconds have elapsed, whichever occurs first,THE SYSTEM SHALL abandon the upload and reject the operation.
## Requirement 3: Cleanup on failureWHEN the upload is abandoned for any reason,THE SYSTEM SHALL call AbortMultipartUpload exactly once, covering all parts uploaded so far, before rejecting.
## Requirement 4: Non-retryable failuresWHEN uploadPart returns a 403 or 400 error,THE SYSTEM SHALL reject immediately without retrying.
## Requirement 5: Partial success handlingWHEN one part fails permanently after other parts have already succeeded,THE SYSTEM SHALL abort the entire multipart upload as a single operation, not per-part.
Kiro also lets you ask it to analyze requirements for logical inconsistencies, ambiguities, conflicting constraints, and gaps before moving to design — run this step deliberately. On the actual requirements above, the analysis pass is where you'd expect Kiro to flag that Requirement 2 and Requirement 3 interact ambiguously: does the 60-second cap apply per-part or to the whole multipart upload? Getting an LLM to interrogate its own requirements draft before design starts is cheap, and it's catching the exact class of ambiguity that would otherwise surface three weeks later as a test someone can't agree on.
Phase 2 — design.md
The design phase documents technical architecture, sequence diagrams, and implementation considerations. For this to actually support TDD rather than just describe the happy path, push the design doc to state the test strategy explicitly, not just the architecture:
# design.md (excerpt)
## Test strategy- Unit tests mock the S3 client; no real AWS calls in the suite.- Retry/backoff timing tested with fake timers (jest.useFakeTimers), not real sleeps — suite must complete in under 2s.- The "one abort, not per-part" requirement (Req 5) needs a test that simulates two parts succeeding before a third fails permanently — do not test this as a single-part failure, which would pass trivially without exercising the aggregation logic.- Mutation testing (Stryker) gates the PR at 75% mutation score, scoped to changed files only.
That last bullet is the connective tissue to the earlier article's CI gate — Kiro's spec workflow front-loads structure into planning, but it still ships to the same CI pipeline, and the mutation-score gate is exactly as necessary here as it was in the pure Claude Code workflow. Spec-driven development reduces the odds of missing a requirement; it does nothing on its own to guarantee the eventual tests actually exercise the logic rather than just asserting it ran.
Phase 3 — tasks.md, sequenced so tests land before implementation
tasks.md provides step-by-step implementation instructions including tests and deployment tasks, and it's a dependency-ordered implementation plan with discrete, executable steps. This is the piece you have to actively steer to get TDD rather than test-after: left to its own defaults, Kiro will happily generate a tasks.md where "implement uploadPartWithRetry" and "write tests for uploadPartWithRetry" are adjacent, undifferentiated line items with no ordering constraint between them. Ask explicitly for test-first sequencing when you review the generated task list, or better, say so in the original prompt:
# tasks.md (excerpt, after requesting test-first ordering)
- [ ] 1. Write failing unit tests for retry/backoff logic (Req 1, 2, 4)- [ ] 2. Write failing unit tests for abort-on-failure aggregation (Req 3, 5)- [ ] 3. Implement retry/backoff logic to satisfy tasks 1 (depends on: 1)- [ ] 4. Implement abort aggregation to satisfy task 2 (depends on: 2)- [ ] 5. Run mutation testing, address any surviving mutants (depends on: 3, 4)
Because Kiro executes tasks in dependency-ordered waves, running independent tasks concurrently within a wave, tasks 1 and 2 land in Wave 1 and run together, tasks 3 and 4 land in Wave 2 gated on their respective test tasks completing, and task 5 waits on both. That's a genuine structural analog to the red-green split from the Claude Code pattern — the difference is it's declared once in a plan file instead of enforced per-write by a permission hook. It's weaker than the hook — nothing stops you from writing "depends on: 1" and then having Kiro implement first anyway if you don't review the execution order — but it's also more legible to a reviewer who isn't a Kiro power user: a teammate can open tasks.md and see the test-first intent without knowing anything about tool-permission configuration.
Where Kiro's structure helps TDD and where it doesn't replace it

Helps: the EARS-notation requirements are close enough to test assertions that translating requirement → test is nearly mechanical, which reduces the gap between "what we agreed to build" and "what the test suite checks." An EARS requirement like "when the API returns a 429, the system shall retry with exponential backoff starting at 1 second, with a maximum of 3 retries" forces the kind of precision that "handle errors gracefully" doesn't — and imprecise requirements are a bigger source of untested edge cases than imprecise code.
Doesn't replace: nothing in the spec workflow prevents the same green-doesn't-mean-correct failure mode from the S3 abort-call bug in the companion article. If task 3 above ("implement retry/backoff logic") gets executed by an agent that also has the test file open in context — which is Kiro's default, there's no built-in equivalent to the Claude Code permission hook blocking edits to
*.test.* — the same shortcut is available: satisfy the specific mock sequence rather than the general requirement. You have to impose that boundary yourself, either by structuring tasks so test-writing and implementation are reviewed as separate diffs even if the same agent executed both, or by using Kiro's sub-agents primitive to approximate the role separation from the Claude Code pattern.Genuinely new: Agent Hooks that execute automatically on file events — created, saved, deleted — or on spec task pre/post events, which gives you something the Claude Code pattern doesn't have natively: a hook that runs the mutation-testing gate automatically the moment an implementation task completes, rather than waiting for a CI job to catch it on PR. A hook is defined by describing what you want automated in natural language, for example "when I save a React component file, automatically create or update its corresponding test file". Adapted for the TDD loop:
Hook: post-task-mutation-checkTrigger: spec task post-execution, when task type is "implementation"Instructions: Run the mutation testing suite scoped to files changed in this task. If mutation score is below 75%, do not mark the task as Done — surface the surviving mutants and generate a test that kills each one before allowing the task to complete.
Hooks fire on spec task pre/post execution events specifically, which is the piece that has no clean equivalent in a pure chat-driven Claude Code session — there, "run mutation testing after implementation" is a step you remember to ask for or a separate CI job that runs after the fact. In Kiro it can be wired into the task graph itself, so the task literally cannot complete with a failing mutation gate, which is a stronger guarantee than a CI check that runs on the eventual PR.
Steering files as the TDD-discipline equivalent of a shared test-writer prompt
Kiro's steering files — product.md, tech.md, and structure.md, generated into .kiro/steering/ and read on every prompt — give the agent persistent project context. This is the natural place to encode the same TDD ground rules that lived in the Claude Code
implementer.md subagent prompt from the companion article — except here it applies globally to every spec in the repo, not just one role:# .kiro/steering/testing.md
- Every implementation task must have a corresponding test task that precedes it in tasks.md, with an explicit "depends on" relationship.- Tests are written against the requirement in requirements.md, not against implementation details. If no implementation exists yet for a task, do not infer behavior from a similarly-named function elsewhere in the codebase.- A task that makes tests pass by special-casing a specific input value present in the test file, rather than implementing the general requirement, is not complete — flag it instead of marking it Done.- Mutation score below 75% on changed files blocks task completion.
Steering is read on every prompt, so an inaccuracy or gap here propagates into every future session — which cuts both ways. Get this file right once and every spec in the repo inherits the discipline; get it wrong and you've silently baked test-after habits into every future feature without noticing, because nothing surfaces the gap until a mutation-testing gate you never configured would have caught it.
Kiro vs. Claude Code subagents for this specific pattern
Worth being precise about the actual trade-off rather than treating this as "same thing, different UI" — each is a genuinely strong fit for a different unit of review.
| Claude Code subagent pattern | Kiro spec-driven pattern |
|---|---|---|
Enforcement mechanism | Tool-permission hooks — structurally blocks implementer from editing test files | Task dependency ordering in tasks.md — a plan, not a hard block |
Where the "red" phase lives | A dedicated test-writer subagent, isolated context | requirements.md in EARS notation, translated to tests as a task |
Auditability for non-technical stakeholders | Low — subagent config is a .claude/agents/*.md file, not typically reviewed outside engineering | Higher — requirements.md and design.md are closer to a PRD, reviewable by product/compliance without reading permission configs |
Automatic post-task verification | Requires a separate CI job or explicit prompt | Agent Hooks can gate task completion directly on task lifecycle events |
Best fit | Teams that already have strong Claude Code / CI discipline and want maximal structural enforcement | Teams that want the planning artifact itself to be the audit trail — regulated industries, AWS-native shops already moving off Q Developer |
They're not mutually exclusive, either — nothing stops a Kiro spec's implementation task from being executed by a Claude Code session with the same permission-hook subagent pattern underneath, since Kiro's own agent is Bedrock-hosted Claude. The meaningful choice is which artifact you want to be the source of truth for "did we actually specify behavior before writing code": a permission-enforced conversation, or a versioned spec folder.
Getting the most out of it: setup choices that make this workflow strong
None of this happens automatically the moment you install Kiro — the value is real but it's realized through a few deliberate setup choices, the same way TDD itself only pays off if you actually write the test first instead of skipping to green. Worth calling these out explicitly so the pattern lands well the first time rather than after a few false starts:
- Push back during requirements analysis. EARS notation already does the hard work of making vague requirements visibly vague — "handle errors gracefully" simply doesn't parse into WHEN/SHALL, which forces the precision TDD needs at the very first step, before a line of code or even a test exists. Run the requirements-analysis pass Kiro offers and actually engage with what it surfaces; this is the cheapest place in the entire lifecycle to catch a gap.
- State the test-first sequencing you want in tasks.md once, and it sticks for the whole feature. A single instruction — "test tasks precede and gate their corresponding implementation tasks" — gets you the same red-green separation the Claude Code subagent pattern enforces with permission hooks, just declared once at the plan level instead of per-file.
- Wire mutation-score gating into an Agent Hook and it becomes structural, not a habit you have to maintain. Because hooks fire on spec task lifecycle events, this is a strictly stronger guarantee than a CI check that only runs once a PR is open — the implementation task literally can't complete until the gate passes.
- AWS took the February 2026 incident seriously and used it to harden the platform — the response added stronger safety guardrails for AI-generated code touching production infrastructure, which is the right outcome from an early, real-world stress test of an agentic IDE at this scale. It's a useful reminder to keep the same green-diff review habit from the Claude Code pattern, and Kiro's spec artifacts make that review easier, not harder, since a reviewer can check the diff against a written requirement instead of inferring intent from a conversation.
- Confirm current pricing and model access at kiro.dev before you budget — Kiro's roadmap has been moving fast (new Opus models, credit-based pricing, CLI and Windows support all landed within a few months of GA), which is a sign of a platform actively being invested in rather than one that's settled and stagnant.
Bottom line
Kiro turns spec-driven development into the missing layer above TDD, not a competitor to it. TDD alone starts from a contract you're assumed to already understand; Kiro's requirements-and-design phase is what gets that contract right in the first place, in a form product owners and compliance reviewers can actually read. Sequence tasks.md so tests gate implementation, put the same ground rules in a steering file that used to live in a subagent prompt, and wire mutation-score checks into an Agent Hook, and you get a workflow where the discipline is baked into the plan itself — versioned, reviewable, and durable in a way a one-off conversation with an agent isn't. For teams already standardizing on AWS and moving off Q Developer, this is the strongest version of TDD-plus-planning currently available in a mainstream IDE.
About the Author
Ashish Kasaudhan is a DevSecOps Architect with 17 years of experience across cloud infrastructure, containerization, CI/CD, and enterprise automation. He is an AWS Community Builders member and Ambassador. He writes regularly about cloud, DevOps, AIOps, and MLOps.
If this post was useful, connect on LinkedIn — he writes about this stuff regularly, and it’s worth having in your feed:
👉 linkedin.com/in/ashish-kasaudhan-713a4225
Enjoyed reading this content? Let the author know!
Your likes, comments, shares, and saves help creators reach more builders.
Loading recommendations
Loading article