Skip to content
Development
Skill

/shipyard-testing

Guides test design with AAA structure, DAMP naming, mock boundaries, and the testing pyramid. Use when writing tests, structuring test suites, choosing test boundaries, or fixing test quality issues such as flakiness, over-mocking, or brittle tests. Also use when choosing

From plugin
shipyard
6725 skills20 agents25 commands4 hooks

Claude Code

$ npx -y skills add lgbarn/shipyard --skill shipyard-testing --agent claude-code

SKILL.md

shipyard-testing.SKILL.md
name: shipyard-testing
description: Guides test design with AAA structure, DAMP naming, mock boundaries, and the testing pyramid. Use when writing tests, structuring test suites, choosing test boundaries, or fixing test quality issues such as flakiness, over-mocking, or brittle tests. Also use when choosing between unit, integration, and E2E tests, when tests break during refactoring (a sign of testing implementation details), or when test setup exceeds 20 lines.

<!-- TOKEN BUDGET: 400 lines / ~1200 tokens -->

Writing Effective Tests

<activation>

When This Skill Activates

  • Writing or modifying test files (`*.test.*`, `*.spec.*`, `*_test.go`, `test_*.py`)
  • Setting up test infrastructure or test utilities
  • Debugging flaky, brittle, or slow tests
  • Deciding between unit, integration, and E2E tests
  • Choosing when and how to use mocks, stubs, or fakes

Natural Language Triggers

  • "add tests", "test coverage", "testing strategy", "write tests for", "how should I test"

</activation>

Overview

Test behaviors through public APIs. Verify state, not interactions.

**Core principle:** If refactoring breaks your tests but not your users, your tests are wrong.

**Relationship to TDD:** The `shipyard:shipyard-tdd` skill covers WHEN to write tests (test-first, red-green-refactor). This skill covers HOW to write tests that are effective, maintainable, and trustworthy.

The Iron Law

TEST BEHAVIORS, NOT IMPLEMENTATIONS

A test should break only when the system's observable behavior changes -- never because of refactoring, renaming internals, or restructuring code.

**No exceptions:**

  • Don't test private methods
  • Don't assert on internal state
  • Don't verify method call sequences
  • Don't couple tests to data structures users never see

<instructions>

Test Structure (AAA)

Every test follows Arrange-Act-Assert:

Arrange -- Set up preconditions and inputs
Act     -- Execute the behavior under test
Assert  -- Verify the expected outcome

Separate the three sections with blank lines. One Act per test. One logical assertion per test.

<examples>

<example type="good" title="Clear AAA structure, one behavior per test">

def test_expired_subscription_denies_access():
    # Arrange
    user = create_user(subscription_end=yesterday())

    # Act
    result = check_access(user, resource="premium-content")

    # Assert
    assert result.denied is True
    assert result.reason == "subscription expired"

Clear name, tests one behavior, obvious structure. </example>

<example type="bad" title="Multiple behaviors, internal state tested">

def test_subscription():
    user = create_user(subscription_end=yesterday())
    assert check_access(user, "premium-content").denied
    user.subscription_end = tomorrow()
    assert check_access(user, "premium-content").allowed
    assert user.access_log == [("denied", "premium-content"), ("allowed", "premium-content")]

Two behaviors in one test, tests internal log, vague name. </example>

</examples>

Keep Tests DAMP, Not DRY

Prefer **Descriptive And Meaningful Phrases** over eliminating duplication. Duplicating setup across tests is fine if it makes each test self-contained and readable. Extract shared setup into helpers only when it improves clarity, not to reduce line count.

No Logic in Tests

Tests are straight-line code. No loops, conditionals, ternaries, or string concatenation. If you need logic, the test is too complex -- split it or simplify the design.

What to Test

Test These

  • **Behaviors:** What the system does from a user's perspective
  • **Edge cases:** Empty inputs, boundaries, overflow, zero, null
  • **Error paths:** Invalid input, missing dependencies, timeouts
  • **State transitions:** Before and after an operation

Skip These

  • Trivial getters/setters with no logic
  • Generated code (protobuf, ORM migrations)
  • Configuration files
  • Third-party library internals

Test Via Public APIs

Invoke the system the same way its callers do. If the only way to test something is through a private method, the design needs to change -- extract it into a collaborator with its own public interface.

Naming Tests

Name tests after the behavior, not the method.

| Good | Bad | |------|-----| | `rejects_empty_email_with_validation_error` | `test_validate` | | `returns_cached_result_when_within_ttl` | `test_cache` | | `retries_three_times_before_failing` | `test_retry_logic` | | `grants_access_when_subscription_active` | `test_check_access_method` |

Patterns that work:

  • `[action]_[condition]_[expected_result]`
  • `should [expected behavior] when [condition]`
  • Plain descriptive sentence

A test name containing "and" usually means two tests.

Choosing Test Level

digraph test_level {
    rankdir=TB;
    start [label="What are you testing?", shape=diamond];
    pure [label="Pure logic?\nNo I/O, no side effects", shape=diamond];
    boundary [label="System boundary?\nDB, API, file, queue", shape=diamond];
    journey [label="Critical user journey?\nLogin, checkout, signup", shape=diamond];

    unit [label="UNIT TEST\nFast, isolated, many", shape=box, style=filled, fillcolor="#ccffcc"];
    integration [label="INTEGRATION TEST\nBoundary focused, fewer", shape=box, style=filled, fillcolor="#ffffcc"];
    e2e [label="E2E TEST\nFull stack, minimal", shape=box, style=filled, fillcolor="#ffcccc"];

    start -> pure;
    pure -> unit [label="yes"];
    pure -> boundary [label="no"];
    boundary -> integration [label="yes"];
    boundary -> journey [label="no"];
    journey -> e2e [label="yes"];
    journey -> unit [label="no\n(rethink)"];
}

**The pyramid:** Many unit tests. Fewer integration tests. Minimal E2E tests.

**Push tests down.** If a behavior can be tested at a lower level, test it there. Lower = faster, more stable, cheaper to maintain. Only go higher when the lower level can't verify the behavior (serialization, wiring, full user flow).

**Duplicate coverag

Read more
@lgbarn
Ships withshipyard

A Claude Code plugin for structured project execution. Plan work in phases, build with parallel agents and TDD, review with security audits and quality gates, and ship with confidence.

6725 skills20 agents
Get the whole plugin

Stars on GitHub

Momentum

stars in total
67
Star history of lgbarn/shipyard on GitHub, a chart by star-history.com

Other skills on shipyard.

code-simplification

code-simplification

Finds and removes duplication, dead code, over-engineering, and AI-generated bloat such as verbose error handling and redundant…

@lgbarn@lgbarnInstall Skill
documentation

documentation

Generates and updates code docs, README files, API docs, and architecture documentation. Use when a feature with public…

@lgbarn@lgbarnInstall Skill
git-workflow

git-workflow

Manages branches, worktrees, atomic commits, and branch completion by merge, PR, preserve, or discard. Use when starting feature…

@lgbarn@lgbarnInstall Skill
import-spec-file

import-spec-file

Imports a handwritten spec document into Shipyard in place of brainstorming. Use when a freeform spec, requirements, or design…

@lgbarn@lgbarnInstall Skill
import-spec

import-spec

Imports a spec-kit feature spec into Shipyard in place of brainstorming. Use when a spec-kit feature directory with spec.md…

@lgbarn@lgbarnInstall Skill
infrastructure-validation

infrastructure-validat…

Validates infrastructure-as-code with tool chains and checks for common mistakes. Use when working with Terraform (.tf, .tfvars),…

@lgbarn@lgbarnInstall Skill

More skills from other plugins.

brainstorming
Auto-invoked

brainstorming

You MUST use this before any creative work - creating features, building components, adding functionality, modifying behavior, or…

@obra@obraInstall Skill
ask-matt

ask-matt

Ask which skill or flow fits your situation. A router over the skills in this repo.

skills13d309k
@mattpocock@mattpocockInstall Skill
accessibility
Auto-invoked

accessibility

Design, implement, and audit accessible UI to WCAG 2.2 Level AA across Web, iOS, and Android — semantic ARIA roles and labels,…

ecc2w293k
@affaan-m@affaan-mInstall Skill
compiler-commit

compiler-commit

Use when you want to verify compiler changes and commit with the correct convention. Runs tests, lint, and format, then commits…

react4d252k
@facebook@facebookInstall Skill

Questions about shipyard-testing.

Which coding agents can use shipyard-testing?
shipyard-testing installs in Claude Code, Codex, Cursor, Gemini CLI, GitHub Copilot, OpenCode and Windsurf with the skills CLI, using each agent's flag shown on this page. Flowy's auto-invocation, which fires a skill when your prompt matches, runs in Claude Code only.