- Home
- Skills
- Testing & Debugging
- Writing Tests — Spec-Driven, Adversarial, Coverage-Gated
Works with the AI Agents you already use
Writing Tests — Spec-Driven, Adversarial, Coverage-Gated
Writes spec-driven tests (AAA, should_X_when_Y) and runs them through an 8-step pre-commit check with a coverage gate.
Free
Writing Tests — Spec-Driven, Adversarial, Coverage-Gated
Example session with this skill installed
Write unit tests for applyCoupon. The project uses Node's built-in test runner (node:test), ES modules, and tests live in test/unit/ mirroring src/.
Spec
- No coupon → subtotal unchanged.
- Subtotal must be a non-negative integer number of cents, otherwise
RangeError. - Percent coupons take
value% off; the discount is rounded to the nearest cent (half up). - Fixed coupons subtract
valuecents. - The total never goes below zero.
- A coupon is valid through its
expiresAtinstant (inclusive); after that →COUPON_EXPIRED. - Subtotal below
minSubtotalCents→MIN_SUBTOTAL_NOT_MET. - Unknown coupon type →
UNKNOWN_COUPON_TYPE.
// src/pricing/apply-coupon.js
export function applyCoupon(subtotalCents, coupon, now = new Date()) {
if (!Number.isInteger(subtotalCents) || subtotalCents < 0) {
throw new RangeError('subtotalCents must be a non-negative integer');
}
if (!coupon) return subtotalCents;
if (coupon.expiresAt && now > coupon.expiresAt) {
throw new Error('COUPON_EXPIRED');
}
if (subtotalCents < (coupon.minSubtotalCents ?? 0)) {
throw new Error('MIN_SUBTOTAL_NOT_MET');
}
if (coupon.type === 'percent') {
return subtotalCents - Math.round((subtotalCents * coupon.value) / 100);
}
if (coupon.type === 'fixed') {
return Math.max(0, subtotalCents - coupon.value);
}
throw new Error('UNKNOWN_COUPON_TYPE');
}
- Read your context and instructions
- Compiled the writing tests
Summary. 16 tests written from the spec (6 success, 7 error, 3 edge). 15 pass. One fails, and the code is at fault, not the test: the spec says the total never goes below zero, but a percent coupon with value above 100 returns a negative total (-500 for 150% off 1000 cents). I left the assertion as the spec states it. The task isn't done until the code is fixed.
These results come from an actual run of the files below on Node.js v24 (Linux), in a scratch copy of the sample.
Test code: test/unit/pricing/apply-coupon.test.js
import { describe, it } from 'node:test';
import assert from 'node:assert/strict';
import { applyCoupon } from '../../../src/pricing/apply-coupon.js';
const NOW = new Date('2026-10-08T12:00:00Z');
const percentCoupon = (overrides = {}) => ({ type: 'percent', value: 10, ...overrides });
const fixedCoupon = (overrides = {}) => ({ type: 'fixed', value: 500, ...overrides });
describe('applyCoupon', () => {
describe('success', () => {
it('should_return_subtotal_unchanged_when_no_coupon_is_given', () => {
const result = applyCoupon(2599, null, NOW);
assert.equal(result, 2599);
});
it('should_take_percentage_off_when_coupon_is_percent', () => {
const result = applyCoupon(10000, percentCoupon({ value: 15 }), NOW);
assert.equal(result, 8500);
});
it('should_round_discount_to_nearest_cent_when_percent_yields_fraction', () => {
// 10% of 1005 = 100.5 cents -> discount rounds to 101
const result = applyCoupon(1005, percentCoupon({ value: 10 }), NOW);
assert.equal(result, 904);
});
it('should_subtract_fixed_amount_when_coupon_is_fixed', () => {
const result = applyCoupon(2000, fixedCoupon({ value: 500 }), NOW);
assert.equal(result, 1500);
});
it('should_accept_coupon_when_now_equals_expiry_instant', () => {
const coupon = percentCoupon({ expiresAt: NOW });
const result = applyCoupon(1000, coupon, NOW);
assert.equal(result, 900);
});
it('should_accept_coupon_when_subtotal_equals_minimum', () => {
const coupon = fixedCoupon({ value: 500, minSubtotalCents: 3000 });
const result = applyCoupon(3000, coupon, NOW);
assert.equal(result, 2500);
});
});
describe('error', () => {
it('should_throw_coupon_expired_when_now_is_after_expiry', () => {
const coupon = percentCoupon({ expiresAt: new Date('2026-10-08T11:59:59Z') });
assert.throws(() => applyCoupon(1000, coupon, NOW), { message: 'COUPON_EXPIRED' });
});
it('should_throw_min_subtotal_not_met_when_subtotal_is_one_cent_below_minimum', () => {
const coupon = fixedCoupon({ minSubtotalCents: 3000 });
assert.throws(() => applyCoupon(2999, coupon, NOW), { message: 'MIN_SUBTOTAL_NOT_MET' });
});
it('should_throw_unknown_coupon_type_when_type_is_not_supported', () => {
const coupon = { type: 'bogo', value: 1 };
assert.throws(() => applyCoupon(1000, coupon, NOW), { message: 'UNKNOWN_COUPON_TYPE' });
});
const invalidSubtotals = [
['negative', -1],
['fractional', 10.5],
['NaN', Number.NaN],
['a_numeric_string', '1000'],
];
for (const [label, bad] of invalidSubtotals) {
it(`should_throw_range_error_when_subtotal_is_${label}`, () => {
assert.throws(() => applyCoupon(bad, null, NOW), RangeError);
});
}
});
describe('edge', () => {
it('should_return_zero_when_fixed_discount_exceeds_subtotal', () => {
const result = applyCoupon(300, fixedCoupon({ value: 500 }), NOW);
assert.equal(result, 0);
});
it('should_return_zero_when_subtotal_is_zero', () => {
const result = applyCoupon(0, percentCoupon(), NOW);
assert.equal(result, 0);
});
it('should_never_return_negative_total_when_percent_value_exceeds_100', () => {
// Spec: "the total never goes below zero" — applies to every coupon type.
const result = applyCoupon(1000, percentCoupon({ value: 150 }), NOW);
assert.ok(result >= 0, `expected a non-negative total, got ${result}`);
});
});
});
Run: random order + coverage (node --test --test-randomize --experimental-test-coverage --test-coverage-include='src/**' --test-coverage-branches=75)
✖ should_never_return_negative_total_when_percent_value_exceeds_100
AssertionError [ERR_ASSERTION]: expected a non-negative total, got -500
ℹ tests 16
ℹ pass 15
ℹ fail 1
ℹ skipped 0
ℹ file | line % | branch % | funcs % | uncovered lines
ℹ apply-coupon.js | 100.00 | 100.00 | 100.00 |
Pre-commit steps
| Step | Result |
|---|---|
1. Each test in isolation (--test-name-pattern) | Same result as the full run: 15 pass, the percent > 100 test fails |
2. Random order (--test-randomize) | Ran in random order (two different seeds), same result |
| 3. All together | Same result |
| 4/7. Coverage shown | 100% line and branch on apply-coupon.js |
| 5. Linter / formatter / type checker | Not run: none is configured in this sample project |
| 6. Zero failures | Not met. 1 failure caused by a code defect |
| 8. OS matrix | Not run: Linux only so far |
Coverage summary
- Success: no coupon, percent, rounding at the half-cent, fixed, expiry instant inclusive, subtotal equal to minimum.
- Error: expired (one second past), one cent below minimum, unknown type, four invalid subtotals (negative, fractional, NaN, numeric string).
- Edge: fixed discount larger than subtotal, zero subtotal, percent above 100.
- Not covered on purpose: the default
now = new Date()path. Tests inject the clock to stay deterministic. A negative fixedvalueis also left out because the spec doesn't define it (see next steps).
Decisions
- Every expected value comes from the spec, not from reading the code. The rounding case uses 100.5 cents to check half-up rounding.
- The clock is injected (
NOW), so no test depends on real time. - No mocks: the function is pure.
- Inputs are built with small factories (
percentCoupon,fixedCoupon), so each test's setup stays to one or two lines.
Next steps
- Fix the code, not the test: clamp the percent result with
Math.max(0, …), or rejectvalueoutside 1–100. Which one is a product decision. - Clarify in the spec whether negative coupon values must be rejected, then add the test.
- Re-run steps 1–7 after the fix and run the suite in the CI OS matrix.
Connects securely to your tools. The creator never sees your data.
About this skill
Agent-written tests tend to share one flaw: they read the code and assert whatever it already does. The result passes on the first run and finds nothing. This skill writes tests from the spec, aims to break the code, and treats test code with the same quality bar as production code.
What it does:
- Pre-test analysis: finds the framework, mocking library, existing fixtures and helpers, linter and type checker already in the project, and reuses them instead of introducing new ones.
- Coverage planned from the spec: every behavior is listed and sorted into success, error, edge and state-transition cases, then mapped to unit, integration or E2E tests using the pyramid (about 80/20/10 as a guideline).
- Test style: Arrange-Act-Assert with one Act per test,
should_[expected]_when_[condition]names, mocks only at boundaries, no real time, randomness or network in unit tests. - Failing tests: when a test fails, it first asks whether the code is wrong. It never silently edits an assertion to match buggy behavior.
- Coverage gate: 75% branch coverage minimum, 80% target, counted per OS in a cross-platform CI matrix.
- 8-step pre-commit validation: each test in isolation, random order, all together, coverage, linter/formatter/type checker on test files, zero failures or skips, coverage output shown to you, final checklist.
Reference files:
pyramid-and-mocking.md: layer rules, P0–P3 priority matrix, test doubles, and over-mocking.anti-patterns.md: eight named smells (Mirror, Liar, Giant, Mockery, Inspector, Chain Gang, Flaky, Snowball) with wrong and right examples.file-structure.md: mirrored layout, per-language naming, factories, no catch-all helpers.pre-commit.md: the 8 steps with commands for pytest, Jest, Vitest, Go, Maven and .NET.multi-platform.md: CI matrix withfail-fast: false, and fixes for tests that only work on Linux.
It triggers on "write tests", "add coverage", "build a test suite", "fix flaky tests" or "verify this with tests".
Free and MIT-licensed, part of the claude-skills-czl collection.
Source on GitHub: this skill · full repository
How to use
Skills on Agensi run inside your AI agent. Copy the prompt, paste it into your agent, and it connects to Agensi and installs this skill for you. If a payment is needed, your agent shows it to you first.
Reviews
No reviews yet
Be one of the first to try it. Every listed skill passes our trust checks below.
Security scanned
Passed our 8-point scan before listing
Fresh listing
Recently published to Agensi
Free forever
No account required to browse
Trust & safety
Security scanned
Verified clean 11 days ago
- Free to download with an account
Requires