Skip to content
cccskills
FreePublished on GitHub

test-driven-development

Write tests before code; reproduce bugs before fixing them. Use when implementing logic, fixing bugs, or modifying behavior. Tests are proof — "seems right" isn't done.

The English summary is being prepared. Showing the original description.

ayunis-core/ayunis-core34 stars(GitHub 34 + cccskills 0)

Run score Not tested

See how to install

Files included (1)

  • SKILL.md8.8 KB

SKILL.md (original)

Review the instructions the agent will receive before you install.

Test-Driven Development

Write a failing test before writing the code that makes it pass. For bug fixes, reproduce the bug with a test before attempting a fix. A codebase with good tests is an agent's superpower; a codebase without tests is a liability.

When to use

  • Implementing new logic or behavior
  • Fixing a bug (use the Prove-It pattern below)
  • Modifying existing functionality
  • Adding edge case handling
  • Any change that could break existing behavior

When NOT to use: pure config changes, doc updates, static content with no behavioral impact.

The cycle: Red → Green → Refactor

  1. Red — Write a test that fails. A test that passes immediately proves nothing.
  2. Green — Write the minimum code to make it pass. Don't over-engineer.
  3. Refactor — Clean up with tests still green. Run tests after every refactor step.

Repeat. One concept at a time.

The Prove-It pattern (bug fixes)

When a bug is reported, don't start by trying to fix it. Start by writing a test that reproduces it.

  1. Write a test that demonstrates the bug. It must fail.
  2. The failing test confirms the bug exists and that you understand it.
  3. Implement the fix.
  4. The test now passes — fix proven, regression guarded.
  5. Run the additional validation required by the repository instructions and the applicable surface-specific skill; without repository guidance, run the full suite.

If you can't write a failing test for the bug, you don't understand the bug yet. Stop and figure it out before touching the code.

The test pyramid

Distribute testing effort by level:

        ╱╲
       ╱E2E╲          ~5% — full user flows, real browser
      ╱─────╲
     ╱integ. ╲        ~15% — boundaries, API, DB
    ╱────────╲
   ╱   unit   ╲       ~80% — pure logic, isolated, ms each
  ╱────────────╲

The Beyonce Rule: if you liked it, you should have put a test on it. Refactors don't catch your bugs — your tests do. Untested code that breaks is on you.

Test sizes

Classify by what the test consumes:

SizeConstraintsSpeedWhere
SmallSingle process, no I/O, no DB, no networkmsPure logic, transforms, validators
MediumLocalhost only, no external servicessecondsAPI + test DB, components
LargeExternal services allowedminutesE2E, perf, staging integration

The vast majority of the suite should be small. They're fast, reliable, and easy to debug.

Writing good tests

Test state, not interactions

Assert on the outcome of the operation, not on which methods were called internally. Interaction-based tests break on every refactor even when behavior is unchanged.

// Good — tests behavior
const tasks = await listTasks({ sortBy: "createdAt", sortOrder: "desc" });
expect(tasks[0].createdAt.getTime()).toBeGreaterThan(
  tasks[1].createdAt.getTime(),
);

// Bad — tests SQL string
expect(db.query).toHaveBeenCalledWith(
  expect.stringContaining("ORDER BY created_at DESC"),
);

DAMP over DRY in tests

In production code, DRY is usually right. In tests, DAMP (Descriptive And Meaningful Phrases) wins. Each test should read like a specification — self-contained, no tracing through shared helpers to figure out what's being verified. Duplication in tests is acceptable when it makes each test independently understandable.

Prefer real implementations

Use the simplest test double that gets the job done. The closer to real, the more confidence:

Real implementation > Fake (in-memory) > Stub (canned data) > Mock (interaction)

Mock only when the real implementation is too slow, non-deterministic, or has side effects you can't control (external APIs, email sending). Over-mocking creates tests that pass while production breaks.

Arrange-Act-Assert

it("marks tasks overdue when deadline has passed", () => {
  // Arrange
  const task = createTask({ title: "Test", deadline: new Date("2025-01-01") });
  // Act
  const result = checkOverdue(task, new Date("2025-01-02"));
  // Assert
  expect(result.isOverdue).toBe(true);
});

One assertion per concept

Each test verifies one behavior. Names read like a spec.

// Good
it('rejects empty titles', ...);
it('trims whitespace from titles', ...);
it('enforces maximum title length', ...);

// Bad
it('validates titles correctly', () => {
  // ten different things in one test
});

Anti-patterns

Anti-patternWhy it hurtsFix
Testing implementation detailsTests break on every refactorTest inputs and outputs
Flaky tests (timing, ordering)Erodes trust in the suiteDeterministic assertions, isolated state
Testing framework codeWastes effortOnly test YOUR code
Snapshot abuseNobody reviews, breaks on any changeUse sparingly, review every change
No test isolationPass alone, fail togetherPer-test setup/teardown
Mocking the unit under testTests prove nothing about real behaviorMock dependencies, never the subject

Common rationalizations

RationalizationReality
"I'll write tests after the code works."You won't. And post-hoc tests test the implementation you wrote, not the behavior you needed.
"This is too simple to test."Simple code gets complicated. The test documents the expected behavior for the next person.
"Tests slow me down."Tests slow you down now. They speed you up every time you change the code later.
"I tested it manually."Manual testing doesn't persist. Tomorrow's refactor breaks it with no way to know.
"The code is self-explanatory."Tests are the spec. They describe what the code should do, not what it currently does.
"It's just a prototype."Prototypes become production. Tests from day one avoid the test-debt crisis.
"I'll add the bug-reproduction test after I've fixed it."If you fix first, the test only verifies your assumption about the fix, not the original bug. The test must fail before the fix.
"All tests pass."Did you actually run them? "All tests pass" with no command output is the agent equivalent of "trust me." Show the output.
"This test is too hard to write — let me skip it."Hard-to-test code is a design smell. The friction is signal, not noise. Restructure the code so it can be tested.

Red flags

  • Writing code without a corresponding test
  • Tests that pass on the first run (verify they actually fail when you break the code under test)
  • Bug fixes without a reproduction test that failed before the fix
  • Test names that don't describe a behavior ("works", "handles errors", "test 3")
  • Skipping or disabling tests to make the suite pass
  • Mocking the unit under test
  • "All tests pass" without producing the test runner's output

Verification

After implementation:

  • Every new behavior has a corresponding test
  • All tests required by the repository instructions (the full suite when none are specified) pass — and the test runner's output proves it
  • Bug fixes include a reproduction test that failed before the fix
  • Test names describe behavior, not implementation
  • No skipped or disabled tests
  • Coverage hasn't decreased (if tracked)

Reviews

No reviews yet. Tell others what you thought.

Comments

No login needed. Share how it worked for you, ask a question, or note where it did not run.

No comments yet. Be the first to write one.

Comments are published right away. Do not include personal information such as email addresses or phone numbers. Up to 2 URLs.

0 / 1,000 characters

This form is protected by reCAPTCHA, and the Google Privacy Policy and Terms of Service apply.

More skills from this repository

Query Ayunis deployed-environment telemetry in AppSignal — incidents, occurrence counts and error traces via the `appsignal-cli` binary; structured log attributes (user.id, org.id, nestjs.context, custom metadata) via the v2 logs API. Use when investigating a production/staging incident or log data — NOT for local dev logs (see backend-debugging).

The English summary is being prepared. Showing the original description.

ayunis-core/ayunis-core34Updated Oct 26, 2026

Ayunis Core backend specifics — module boundaries, key files, and project conventions. Complements the nestjs-hexagonal-backend skill.

The English summary is being prepared. Showing the original description.

ayunis-core/ayunis-core34Updated Oct 26, 2026

Frontend development in ayunis-core. Use when creating, modifying, or debugging frontend code (React, Feature-Sliced Design, API client).

The English summary is being prepared. Showing the original description.

ayunis-core/ayunis-core34Updated Oct 26, 2026

Debug backend runtime errors (500s, crashes, unexpected behavior). Use when something is broken at runtime — not for writing new code.

The English summary is being prepared. Showing the original description.

ayunis-core/ayunis-core34Updated Oct 26, 2026

Shared test fixtures for backend specs — port mock factories, domain builders, and shared IDs in a per-module testing/ folder. Use when a module's *.use-case.spec.ts files repeat the same mock-repository literal, inline entity construction, or hardcoded UUIDs across files.

The English summary is being prepared. Showing the original description.

ayunis-core/ayunis-core34Updated Oct 26, 2026

Analyze Cursor Bugbot PR comments through a root-cause lens: is the finding valid, and is the suggested fix the real fix or a bandaid over a deeper problem? Use when the user asks to check, assess, or triage bugbot comments.

The English summary is being prepared. Showing the original description.

ayunis-core/ayunis-core34Updated Oct 26, 2026

See all skills by ayunis-core

Report a problem with this skill