home / skills / frankxai / arcanea / tdd

This skill guides you through Test-Driven Development the Arcanean way, turning constraints into creativity by writing tests first and implementing minimally.

npx playbooks add skill frankxai/arcanea --skill tdd

Review the files below or copy the command above to add this skill to your agents.

Files (1)
SKILL.md
10.4 KB
---
name: arcanea-tdd
description: Test-Driven Development with Arcanean philosophy - write tests first, fail intentionally, implement minimally, refactor with confidence. Embodies the Arcanean principle that constraint liberates creativity.
version: 2.0.0
author: Arcanea
tags: [testing, tdd, development, quality, methodology]
triggers:
  - test
  - tdd
  - test driven
  - write tests
  - test first
  - feature implementation
---

# Test-Driven Development: The Arcanean Way

> *"The test is not a check. The test is a specification. Write the specification before the implementation, and the implementation reveals itself."*

---

## The TDD Philosophy

In Arcanea, we recognize that **constraints liberate creativity**. TDD embodies this principle perfectly:

```
CONSTRAINT: Write the test first
LIBERATION: The implementation becomes clear
CONSTRAINT: Make it fail first
LIBERATION: You know what success looks like
CONSTRAINT: Implement minimally
LIBERATION: No over-engineering
CONSTRAINT: Refactor with green tests
LIBERATION: Confidence to improve
```

---

## The Sacred Cycle

```
         ╭──────────────────╮
         │    RED           │
         │  Write a test    │
         │  that fails      │
         ╰────────┬─────────╯
                  │
                  ▼
         ╭──────────────────╮
         │    GREEN         │
         │  Write minimum   │
         │  code to pass    │
         ╰────────┬─────────╯
                  │
                  ▼
         ╭──────────────────╮
         │   REFACTOR       │
         │  Improve code    │
         │  tests stay green│
         ╰────────┬─────────╯
                  │
                  ╰──────────────╮
                                 │
         ╭───────────────────────╯
         │
         ▼
    (Next test)
```

### RED: The Failing Test

```typescript
// Write a test for behavior that doesn't exist yet
describe('UserService', () => {
  it('should create a user with valid email', async () => {
    const service = new UserService();
    const user = await service.create({ email: '[email protected]' });

    expect(user.id).toBeDefined();
    expect(user.email).toBe('[email protected]');
    expect(user.createdAt).toBeInstanceOf(Date);
  });
});

// Run the test - IT MUST FAIL
// This proves the test is testing something real
```

### GREEN: The Minimal Implementation

```typescript
// Write the MINIMUM code to make the test pass
// No more, no less
class UserService {
  async create(data: { email: string }) {
    return {
      id: crypto.randomUUID(),
      email: data.email,
      createdAt: new Date(),
    };
  }
}

// Run the test - IT MUST PASS
```

### REFACTOR: Improve With Confidence

```typescript
// Now improve the code
// The test protects you from breaking things
class UserService {
  private readonly users: Map<string, User> = new Map();

  async create(data: CreateUserInput): Promise<User> {
    const user = User.create(data);
    this.users.set(user.id, user);
    return user;
  }
}

// Run the test - IT MUST STILL PASS
```

---

## The TDD Workflow

### Step 1: Understand the Requirement
```
Before writing any code:
1. What is the feature/behavior?
2. What are the inputs?
3. What are the expected outputs?
4. What are the edge cases?
```

### Step 2: Write the Test
```
The test describes the behavior you want:
- Given [initial state]
- When [action occurs]
- Then [expected result]
```

### Step 3: Watch It Fail
```
Run the test and confirm:
- It fails for the RIGHT reason
- The error message makes sense
- You understand what's missing
```

### Step 4: Write Minimal Code
```
Implement only what's needed to pass:
- No additional features
- No optimization
- No "while I'm here" improvements
```

### Step 5: Watch It Pass
```
Run the test and confirm:
- It passes
- Other tests still pass
- You didn't break anything
```

### Step 6: Refactor
```
Now improve the code:
- Extract functions
- Rename for clarity
- Optimize if needed
- Remove duplication

The tests protect you.
```

### Step 7: Repeat
```
Next behavior → Next test → Next cycle
```

---

## Test Categories

### Unit Tests
```
Test individual units in isolation.

SCOPE: Single function, class, or module
SPEED: Milliseconds
DEPENDENCIES: Mocked/stubbed
WHEN: Every code change

Example:
it('should validate email format', () => {
  expect(isValidEmail('[email protected]')).toBe(true);
  expect(isValidEmail('not-an-email')).toBe(false);
});
```

### Integration Tests
```
Test how units work together.

SCOPE: Multiple components interacting
SPEED: Seconds
DEPENDENCIES: Some real, some mocked
WHEN: Before commits

Example:
it('should save user to database', async () => {
  const user = await userService.create({ email: '[email protected]' });
  const found = await userRepository.findById(user.id);
  expect(found).toEqual(user);
});
```

### End-to-End Tests
```
Test complete user flows.

SCOPE: Entire application
SPEED: Seconds to minutes
DEPENDENCIES: Real systems
WHEN: Before release

Example:
it('should complete user registration flow', async () => {
  await page.goto('/register');
  await page.fill('input[name="email"]', '[email protected]');
  await page.fill('input[name="password"]', 'SecurePass123!');
  await page.click('button[type="submit"]');
  await expect(page).toHaveURL('/dashboard');
});
```

---

## Test Patterns

### Arrange-Act-Assert (AAA)
```typescript
it('should add items to cart', () => {
  // ARRANGE: Set up the test
  const cart = new ShoppingCart();
  const item = { id: '1', name: 'Widget', price: 9.99 };

  // ACT: Perform the action
  cart.addItem(item);

  // ASSERT: Verify the result
  expect(cart.items).toContain(item);
  expect(cart.total).toBe(9.99);
});
```

### Given-When-Then (BDD Style)
```typescript
describe('Shopping Cart', () => {
  describe('given an empty cart', () => {
    describe('when an item is added', () => {
      it('then the cart contains the item', () => {
        // ...
      });

      it('then the total reflects the item price', () => {
        // ...
      });
    });
  });
});
```

### Test Fixtures
```typescript
describe('UserService', () => {
  let service: UserService;
  let mockRepository: jest.Mocked<UserRepository>;

  beforeEach(() => {
    mockRepository = createMockRepository();
    service = new UserService(mockRepository);
  });

  afterEach(() => {
    jest.clearAllMocks();
  });

  // Tests use fresh fixtures each time
});
```

---

## What to Test

### The Testing Pyramid
```
           /\
          /  \
         / E2E \      (Few: Slow, expensive, broad)
        /______\
       /        \
      /Integration\   (Some: Medium speed, focused)
     /____________\
    /              \
   /   Unit Tests   \  (Many: Fast, cheap, specific)
  /__________________\
```

### Behavior, Not Implementation
```typescript
// BAD: Testing implementation details
it('should call repository.save with user object', () => {
  await service.createUser(userData);
  expect(repository.save).toHaveBeenCalledWith(expect.any(User));
});

// GOOD: Testing behavior
it('should persist user and return with id', async () => {
  const user = await service.createUser(userData);
  const found = await service.findById(user.id);
  expect(found).toEqual(user);
});
```

### Edge Cases
```typescript
describe('divide', () => {
  it('should divide two numbers', () => {
    expect(divide(10, 2)).toBe(5);
  });

  it('should throw on division by zero', () => {
    expect(() => divide(10, 0)).toThrow('Division by zero');
  });

  it('should handle negative numbers', () => {
    expect(divide(-10, 2)).toBe(-5);
  });

  it('should handle floating point', () => {
    expect(divide(1, 3)).toBeCloseTo(0.333, 2);
  });
});
```

---

## Common Pitfalls

### The Oracle Problem
```
PROBLEM: Tests that test themselves
EXAMPLE: expect(add(2, 2)).toBe(add(2, 2));
SOLUTION: Use concrete expected values
```

### Test Pollution
```
PROBLEM: Tests depend on each other or shared state
SOLUTION: Each test creates its own fixtures, cleanup after
```

### Over-Mocking
```
PROBLEM: Everything is mocked, testing mocks not reality
SOLUTION: Mock at boundaries, test real logic
```

### Fragile Tests
```
PROBLEM: Tests break when implementation changes
SOLUTION: Test behavior, not implementation
```

### Slow Tests
```
PROBLEM: Tests take too long, developers skip them
SOLUTION: Fast unit tests, fewer integration tests
```

---

## TDD Tips

### Start with the Simplest Test
```
Don't start with complex edge cases.
Start with the happy path.
Build complexity incrementally.
```

### One Assertion Per Test (Usually)
```
Each test should verify one behavior.
Multiple assertions are okay if testing one logical thing.
```

### Name Tests Clearly
```
WEAK: testUserCreate()
STRONG: should_create_user_with_valid_email()
         should_reject_user_with_invalid_email()
         should_assign_unique_id_to_new_user()
```

### Delete Bad Tests
```
Tests that frequently break for wrong reasons
Tests that don't catch bugs
Tests that are hard to understand
DELETE THEM. Bad tests are worse than no tests.
```

---

## Quick Reference

### TDD Checklist
```
□ Test written before code
□ Test fails first
□ Error message is clear
□ Minimum code written to pass
□ All tests pass
□ Code refactored
□ Tests still pass
□ Tests are readable
□ Tests are fast
□ Tests don't depend on each other
```

### Test Quality Criteria
```
□ Tests document behavior (can read to understand feature)
□ Tests catch bugs (when code breaks, tests fail)
□ Tests allow refactoring (can change implementation safely)
□ Tests run quickly (no excuse to skip them)
□ Tests are maintainable (easy to update when behavior changes)
```

---

*"The test is the specification made executable. Write the specification first, and the implementation reveals itself."*

Overview

This skill teaches Test-Driven Development (TDD) using the Arcanean philosophy: constraints free creativity. It guides you through the red-green-refactor cycle, prioritizing failing tests as executable specifications and minimal implementations. The goal is faster feedback, safer refactoring, and clearer behavior-driven code.

How this skill works

The skill walks you through writing tests before code, running them to confirm failure, implementing the smallest change to pass, and then refactoring with tests green. It categorizes tests (unit, integration, end-to-end), provides patterns (AAA, Given-When-Then), and a checklist to keep tests fast, reliable, and behavior-focused. Practical examples show minimal implementations and progressive improvements.

When to use it

  • When building new features to define expected behavior first
  • When refactoring legacy code to add safety via tests
  • When onboarding developers to a consistent workflow
  • When reducing bugs in production by catching regressions early
  • When designing APIs or domain models where specifications matter

Best practices

  • Write the simplest failing test that describes the behavior (start with happy path)
  • Implement only what’s needed to pass the test—no extras
  • Keep tests fast and isolated; mock at boundaries, not core logic
  • Test behavior, not implementation details to avoid fragile tests
  • Use clear, descriptive test names and maintain small fixtures that reset between tests

Example use cases

  • Create a UserService: write a failing test for user creation, then implement minimal create() and iterate
  • Add validation: write tests for invalid inputs and edge cases before adding checks
  • Refactor a module: add unit tests for critical behavior, then refactor with confidence
  • Build an integration check: write an integration test for saving to the database before committing changes
  • Ship a feature: use E2E tests to verify full user flows before release

FAQ

What if my test never fails for the right reason?

Adjust the test to assert a concrete expected result and isolate dependencies so the failure points to missing behavior, not external flakiness.

How many assertions should a test contain?

Prefer one logical behavior per test. Multiple assertions are acceptable when they assert a single outcome or closely related expectations.