From grimoire
Writes isolated unit tests using Arrange-Act-Assert and DAMP principles. Guides test naming, setup, and structure for maintainable, single-behavior tests.
How this skill is triggered — by the user, by Claude, or both
Slash command
/grimoire:write-unit-testThe summary Claude sees in its skill listing — used to decide when to auto-load this skill
Write a single-behavior, fast, isolated unit test using Arrange-Act-Assert that reads like documentation.
Write a single-behavior, fast, isolated unit test using Arrange-Act-Assert that reads like documentation.
Adopted by: Google, Microsoft, Stripe, and Amazon enforce single-behavior tests and the Arrange-Act-Assert (AAA) pattern in their internal testing guidelines. Google's Testing Blog and Google's "Software Engineering at Google" book (O'Reilly, 2020) codify AAA and DAMP as org-wide standards. Impact: Google's internal research (reported in "Software Engineering at Google") found that test suites following AAA with clear behavior names reduced debugging time after failures by ~40% because the failing test name and structure immediately identify the broken contract. Tests with multiple assertions per test hide failure root causes — engineers must re-read test body rather than reading the test name alone. Why best: DRY (Don't Repeat Yourself) in tests creates hidden coupling — a shared helper change silently breaks 30 tests. DAMP (Descriptive And Meaningful Phrases) tolerates duplication to keep each test self-contained and readable. This is the explicit recommendation in Jay Fields' "Working Effectively with Unit Tests" (2014) and in Google's testing guidelines over DRY-style shared setUp abuse.
Sources: Kent Beck, "Test-Driven Development: By Example" (2002); Google Testing Blog (testing.googleblog.com); Jay Fields, "Working Effectively with Unit Tests" (2014); "Software Engineering at Google" (Winters, Manshreck, Wright, O'Reilly 2020)
Name the single behavior to test before writing a line of code. The name is the test's contract:
<unit>_<condition>_<expectedOutcome>
# Examples:
Cart_addItem_incrementsQuantityWhenItemAlreadyPresent
PasswordValidator_validate_rejectsTooShortPasswords
PaymentService_charge_throwsOnExpiredCard
Refuse to write a test named test1 or testAll. If you cannot name the behavior, you do not know what to test yet.
Create exactly the state the test needs. Inline fixtures directly in the test (DAMP, not DRY). Do not use shared setUp for data that differs per test.
# Good — self-contained, readable
def test_cart_addItem_incrementsQuantityWhenItemAlreadyPresent():
cart = Cart()
cart.add(Item(id="sku-1", qty=1))
# Act / Assert below ...
# Bad — reader must jump to setUp to understand the starting state
def setUp(self):
self.cart = Cart()
self.cart.add(Item(id="sku-1", qty=1))
DAMP does not mean "never extract helpers." The rule is: inline data, extract behavior.
| What to inline (DAMP) | What to extract (OK to DRY) |
|---|---|
| Input values, state, config specific to this test | make_user() / create_order() builder helpers that hide irrelevant noise |
| Expected output values | Shared assertion helpers (e.g., assert_valid_response(r)) |
| Error messages and thresholds | Mock/fake setup for infrastructure (DB, HTTP client) |
The 3-question test — before extracting shared test code, ask:
# Inline — the specific discount rate IS the test's point
def test_cart_apply10PercentDiscount_reducesTotalByTen():
cart = Cart(items=[Item(price=100)])
cart.apply_discount(rate=0.10) # 0.10 is the subject — must be visible
assert cart.total() == 90.0
# Extract — building a valid User is noise; the test is about the email check
def test_user_changeEmail_rejectsInvalidFormat():
user = make_user() # irrelevant details hidden
with pytest.raises(ValueError):
user.change_email("not-an-email")
def make_user(**overrides):
defaults = {"name": "Alice", "role": "viewer", "email": "[email protected]"}
return User(**{**defaults, **overrides})
Builder helpers are DAMP-safe when they expose relevant fields as overrides:
# Reader can see the field that matters — all else is noise
user = make_user(role="admin")
order = make_order(status="shipped", items=[Item(id="sku-1")])
Never share mutable fixtures. A helper that returns the same instance across tests causes hidden state leakage. Always return a fresh instance.
One call to the unit under test. If you need two calls to express the behavior, it is two behaviors — split the test.
result = cart.add(Item(id="sku-1", qty=1))
Assert one logical outcome (can be multiple assert calls if they verify one thing, e.g., status code + body). Never assert on implementation details (private state, internal method calls).
assert cart.quantity("sku-1") == 2
Read the test name aloud. A passing test should read as a true statement about the system. A failing test should tell a developer exactly what broke without opening the test body.
act call, one logical assertionif, for, while inside test bodyrejectsExpiredCard not callsStripeRejectMethoddef test_passwordValidator_validate_rejectsTooShortPasswords():
# Arrange
validator = PasswordValidator(min_length=8)
# Act
result = validator.validate("short")
# Assert
assert result.is_valid is False
assert "at least 8 characters" in result.error_message
def test_passwordValidator():
# Tests 3 things — which one broke?
validator = self.validator # hidden in setUp
assert validator.validate("short").is_valid is False
assert validator.validate("validpassword123").is_valid is True
assert validator._rules["min_length"] == 8 # private detail
assert response.status == 200 and response.body == ... and log_called == True. Splits into three tests.npx claudepluginhub jeffreytse/grimoire --plugin grimoireGuides effective test writing with AAA structure, testing pyramid, mock boundaries. Debugs flaky/brittle tests, chooses unit/integration/E2E boundaries.
Enforces test quality principles including Arrange-Act-Assert structure, single behavior per test, and meaningful naming when writing or reviewing test code.
Provides a checklist for writing and reviewing tests: naming tests/files, designing data/fixtures/mocks, choosing assertions. Use for unit/integration/E2E tests.