/shipyard-testing
Use when writing tests, structuring test suites, choosing test boundaries, or debugging test quality issues like flakiness, over-mocking, or brittle tests. Also use when deciding between unit/integration/E2E tests, when tests break during refactoring (sign of testing
$ npx -y skills add lgbarn/shipyard --skill shipyard-testing --agent claude-codeHow it fires
How this skill gets triggered: by you, by Claude, or both.
- Fires itselfAuto-invocation. Claude auto-loads it when your prompt matches the work.
- You can call itInvoke it directly when you want it.
- Slash command
/shipyard-testing
Context preview
The summary Claude sees to decide when to auto-load this skill.
Use when writing tests, structuring test suites, choosing test boundaries, or debugging test quality issues like flakiness, over-mocking, or brittle tests. Also use when deciding between unit/integration/E2E tests, when tests break during refactoring (sign of testing
SKILL.md
shipyard-testing.SKILL.mdname: shipyard-testing
description: Use when writing tests, structuring test suites, choosing test boundaries, or debugging test quality issues like flakiness, over-mocking, or brittle tests. Also use when deciding between unit/integration/E2E tests, when tests break during refactoring (sign of testing implementation details), or when test setup exceeds 20 lines. Covers AAA structure, DAMP naming, mock boundaries, and the testing pyramid.
<!-- 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 coverage?** If a unit test and i
Read more
name: shipyard-testing description: Use when writing tests, structuring test suites, choosing test boundaries, or debugging test quality issues like flakiness, over-mocking, or brittle tests. Also use when deciding between unit/integration/E2E tests, when tests break during refactoring (sign of testing implementation details), or when test setup exceeds 20 lines. Covers AAA structure, DAMP naming, mock boundaries, and the testing pyramid.
<!-- 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 coverage?** If a unit test and i
Showing the first part of this file.
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.
Repo: lgbarn/shipyard
Other skills on shipyard.
- /code-simplification
Use after implementing features, before claiming a phase is complete, when reviewing AI-generated code, or when code feels overly complex. Also use when you notice repeated patterns across files, a function exceeds 40 lines, nesting exceeds 3 levels, or an abstraction has only
Open skill - /documentation
Use when shipping features with public interfaces that lack docs, generating documentation, updating README files, writing API docs, creating architecture documentation, or when documentation is incomplete or outdated. Also use when adding breaking changes, implementing complex
Open skill - /git-workflow
Use when starting feature work that needs a branch, creating worktrees for isolation, making atomic commits during development, or completing a development branch via merge, PR, preserve, or discard. Also use when the user says "set up worktree", "create PR", "finish this
Open skill - /import-spec-file
Import a handwritten spec document into Shipyard, replacing brainstorming. Use when a freeform spec, requirements, or design document exists.
Open skill - /import-spec
Import a spec-kit feature spec into Shipyard, replacing brainstorming. Use when a spec-kit feature directory exists with spec.md.
Open skill - /infrastructure-validation
Use when working with Terraform (.tf, .tfvars), Ansible (playbooks, roles, inventory), Docker (Dockerfile, docker-compose.yml), Kubernetes (manifests, Helm charts), CloudFormation, or any infrastructure-as-code files. Also use when running terraform plan/apply, building Docker
Open skill

