d9e5668cec
Fixes and improvements from exhaustive audit: Consistency fixes in SKILL.md: - 'Pipe & Filter' → 'Pipe and Filter' (one stray '&' found and corrected) - 'Singleton for factory instance' → clarified to 'register factory as singleton-scoped via DI container' (less misleading wording) - Documentation Format section updated with note that SKILL.md itself is the authoritative source for related-pattern combinations Coverage fix — Related Patterns sections: - Added '## Related Patterns' to ALL 94 pattern files (was 0/94) - Each section lists 3–6 related patterns with relationship descriptions - Covers: why they're related, when to prefer one vs the other, and which are often confused SOLID principles → Creational → Structural → Behavioral → Architectural → Concurrency → Functional → Resilience → Data Access → Messaging → Testing → Error Handling → Microservice — all 13 categories covered Code verification: - Python: 0 failures (all 85 testable blocks pass) - Go: 0 failures (all 76 testable blocks pass) - JavaScript: 0 failures (all 78 testable blocks pass) - All 239 code blocks verified correct after edits Final skill state: - 108 files, 36,524 lines across 13 reference categories - 94/94 pattern files have Related Patterns sections - 2,815-line SKILL.md with 67 decision trees, 23 scenarios, 0 broken references, 0 naming inconsistencies
7.6 KiB
7.6 KiB
Arrange-Act-Assert (AAA)
Problem
Tests become hard to read when setup, execution, and verification are interleaved. Reviewers can't quickly tell what is being tested, how, or what the expected outcome is. This leads to maintenance burden and missed bugs.
Solution
Structure every test in exactly three phases:
- Arrange — Set up the preconditions: create objects, configure stubs, prepare input data.
- Act — Execute the single behaviour under test.
- Assert — Verify the outcome: return values, state changes, or interactions.
Keep each phase visually separated (blank line or comment). The Act phase should be a single statement or very small cluster.
When to Use
- Any unit or integration test in any language.
- When you want a consistent, scannable test format across a team.
- Code reviews benefit from immediately visible structure.
When to Avoid
- Very trivial one-liner assertions where the structure adds noise.
- BDD-style acceptance tests where Given-When-Then is a better fit.
- Property-based tests where the framework generates arrange and assert.
Pseudocode
function test_transfer_deducts_from_source():
// Arrange
source = Account(balance=100)
target = Account(balance=50)
// Act
transfer(source, target, amount=30)
// Assert
assert source.balance == 70
assert target.balance == 80
Python
"""Arrange-Act-Assert pattern in Python."""
from dataclasses import dataclass
@dataclass
class Account:
owner: str
balance: float
def deposit(self, amount: float) -> None:
if amount <= 0:
raise ValueError("Deposit must be positive")
self.balance += amount
def withdraw(self, amount: float) -> None:
if amount > self.balance:
raise ValueError("Insufficient funds")
self.balance -= amount
def transfer(source: Account, target: Account, amount: float) -> None:
source.withdraw(amount)
target.deposit(amount)
# ── Tests ────────────────────────────────────────────────────
def test_transfer_deducts_from_source():
# Arrange
source = Account(owner="Alice", balance=100.0)
target = Account(owner="Bob", balance=50.0)
# Act
transfer(source, target, amount=30.0)
# Assert
assert source.balance == 70.0
assert target.balance == 80.0
print(f"PASS: source={source.balance}, target={target.balance}")
def test_transfer_insufficient_funds():
# Arrange
source = Account(owner="Alice", balance=10.0)
target = Account(owner="Bob", balance=50.0)
# Act & Assert
try:
transfer(source, target, amount=50.0)
assert False, "Expected ValueError"
except ValueError as e:
assert str(e) == "Insufficient funds"
print(f"PASS: got expected error: {e}")
def test_deposit_positive_amount():
# Arrange
account = Account(owner="Carol", balance=0.0)
# Act
account.deposit(25.0)
# Assert
assert account.balance == 25.0
print(f"PASS: balance after deposit={account.balance}")
if __name__ == "__main__":
test_transfer_deducts_from_source()
test_transfer_insufficient_funds()
test_deposit_positive_amount()
print("\nAll AAA tests passed.")
Go
// arrange_act_assert_test.go
package main
import (
"errors"
"fmt"
)
// ── Production code ──────────────────────────────────────────
type Account struct {
Owner string
Balance float64
}
func (a *Account) Deposit(amount float64) error {
if amount <= 0 {
return errors.New("deposit must be positive")
}
a.Balance += amount
return nil
}
func (a *Account) Withdraw(amount float64) error {
if amount > a.Balance {
return errors.New("insufficient funds")
}
a.Balance -= amount
return nil
}
func Transfer(source, target *Account, amount float64) error {
if err := source.Withdraw(amount); err != nil {
return err
}
return target.Deposit(amount)
}
// ── Tests ────────────────────────────────────────────────────
func testTransferDeductsFromSource() {
// Arrange
source := &Account{Owner: "Alice", Balance: 100.0}
target := &Account{Owner: "Bob", Balance: 50.0}
// Act
err := Transfer(source, target, 30.0)
// Assert
if err != nil {
panic(fmt.Sprintf("unexpected error: %v", err))
}
if source.Balance != 70.0 || target.Balance != 80.0 {
panic("balance mismatch")
}
fmt.Printf("PASS: source=%.0f, target=%.0f\n", source.Balance, target.Balance)
}
func testTransferInsufficientFunds() {
// Arrange
source := &Account{Owner: "Alice", Balance: 10.0}
target := &Account{Owner: "Bob", Balance: 50.0}
// Act
err := Transfer(source, target, 50.0)
// Assert
if err == nil {
panic("expected error")
}
if err.Error() != "insufficient funds" {
panic("wrong error message")
}
fmt.Printf("PASS: got expected error: %v\n", err)
}
func testDepositPositiveAmount() {
// Arrange
account := &Account{Owner: "Carol", Balance: 0.0}
// Act
err := account.Deposit(25.0)
// Assert
if err != nil {
panic(fmt.Sprintf("unexpected error: %v", err))
}
if account.Balance != 25.0 {
panic("balance mismatch")
}
fmt.Printf("PASS: balance after deposit=%.0f\n", account.Balance)
}
func main() {
testTransferDeductsFromSource()
testTransferInsufficientFunds()
testDepositPositiveAmount()
fmt.Println("\nAll AAA tests passed.")
}
JavaScript
// arrange_act_assert.js
class Account {
constructor(owner, balance) {
this.owner = owner;
this.balance = balance;
}
deposit(amount) {
if (amount <= 0) throw new Error("Deposit must be positive");
this.balance += amount;
}
withdraw(amount) {
if (amount > this.balance) throw new Error("Insufficient funds");
this.balance -= amount;
}
}
function transfer(source, target, amount) {
source.withdraw(amount);
target.deposit(amount);
}
// ── Tests ────────────────────────────────────────────────────
function testTransferDeductsFromSource() {
// Arrange
const source = new Account("Alice", 100);
const target = new Account("Bob", 50);
// Act
transfer(source, target, 30);
// Assert
console.assert(source.balance === 70);
console.assert(target.balance === 80);
console.log(`PASS: source=${source.balance}, target=${target.balance}`);
}
function testTransferInsufficientFunds() {
// Arrange
const source = new Account("Alice", 10);
const target = new Account("Bob", 50);
// Act & Assert
try {
transfer(source, target, 50);
throw new Error("Expected error");
} catch (e) {
console.assert(e.message === "Insufficient funds");
console.log(`PASS: got expected error: ${e.message}`);
}
}
function testDepositPositiveAmount() {
// Arrange
const account = new Account("Carol", 0);
// Act
account.deposit(25);
// Assert
console.assert(account.balance === 25);
console.log(`PASS: balance after deposit=${account.balance}`);
}
testTransferDeductsFromSource();
testTransferInsufficientFunds();
testDepositPositiveAmount();
console.log("\nAll AAA tests passed.");
Related Patterns
- Given-When-Then — Given-When-Then is the BDD equivalent of AAA; semantically identical but phrased for stakeholder readability.
- Specification — a specification pattern can formalise the assertions in the Assert phase.
- Test Data Builder — use a Test Data Builder to make the Arrange phase concise and expressive.