Issue #12 | Phase C: Unit Testing and TDD in Practice

⏱ Est. reading time: 14 min Updated on 5/7/2026

12.1 Phase C/D: Testing and Acceptance (qa-engineer)

12.1.1 Prerequisite Confirmation

Before spawning qa-engineer, confirm:

flowchart TD
    A["ui-dev Phase A+B completed?"] -->|"Check TaskList"| B{"Task #1 #2
completed?"} C["logic-dev Phase A+B completed?"] -->|"Check TaskList"| D{"Task #3 #4
completed?"} B -->|Yes| E{"Files exist?"} D -->|Yes| E B -->|No| F["Wait or send reminder message"] D -->|No| F E -->|"index.html + style.css
+ app.js all exist"| G["✅ Can spawn qa-engineer"] E -->|"Files missing"| H["Check inbox files
Confirm agent's true status"] style G fill:#c8e6c9 style F fill:#fff9c4 style H fill:#ffcdd2

Confirmation commands:

# Check Task status
# Execute in Claude Code:
TaskList()

# Check files
ls -la index.html style.css app.js

# Check inbox (if TaskList is empty, session might have reset)
cat ~/.claude/teams/calc-dev/inboxes/team-lead.json | python3 -c "
import json, sys
msgs = json.load(sys.stdin)
for m in msgs:
    if '完成' in m.get('text','') or 'completed' in m.get('text','').lower():
        print(f'FROM: {m[\"from\"]}')
        print(f'MSG: {m[\"text\"][:100]}')
        print()
"

12.1.2 Prepare tmux Pane for qa-engineer

qa-engineer also needs an independent tmux pane to run. There are two ways:

Method 1: Reuse released agent pane (recommended)

After ui-dev and logic-dev complete, their tmux panes will be idle. You can reserve a 3rd pane in setup-team.sh:

# Modify scripts/setup-team.sh, add a 3rd pane
tmux split-window -h -t "$SESSION" -n "qa-engineer"
tmux select-pane -t "$SESSION:0.3" -T "qa-engineer"

Method 2: Let Claude Code create automatically (actual behavior)

When the Agent tool spawns, Claude Code will automatically create a pane in a new tmux session, no manual preparation needed. The panes in the calc-dev session are all empty shells; the agent actually runs in the automatically created session.

# After spawning qa-engineer, check which session it's actually running in:
tmux list-sessions
# Output might be similar to:
# 0: 1 windows
# 2: 1 windows    ← ui-dev is here
# 3: 1 windows    ← qa-engineer might be here
# 4: 1 windows    ← logic-dev is here
# calc-dev: 1 windows

# View processes of all panes
tmux list-panes -a -F '#{session_name}.#{pane_index} #{pane_current_command}'
# 2.1.118 = Claude Code agent process
# zsh = empty shell

12.1.3 Spawn qa-engineer

Execute after confirming prerequisites:

Agent({
  name: "qa-engineer",
  mode: "bypassPermissions",
  team_name: "calc-dev",
  prompt: `You are a qa-engineer, responsible for testing and quality assurance.

Do not call any Skills. Write code and tests directly.
All code has been completed by ui-dev and logic-dev.

First, read requirement.md to understand the requirements. Then, read index.html, style.css, and app.js to understand the code.

Task 1: Phase C — Unit Testing + Integration Testing (Task #5)
- The project already has test-runner.js (a lightweight testing framework created by logic-dev), extend it.
- Unit tests: Calculation engine (addition, subtraction, multiplication, division, parentheses, boundary values, division by zero)
- Unit tests: Expression parser (invalid input, nested parentheses)
- Unit tests: History (storage limit 10 entries, click to refill)
- Integration tests: DOM interaction (button click → display update)
- Integration tests: Keyboard events (complete keyboard operation flow)
- Integration tests: Mode switching (style toggle, localStorage, button visibility)
- Integration tests: Senior mode (long press backspace, advanced operations hidden)

Task 2: Phase D — E2E Testing and Acceptance (Task #6)
- E2E: Complete user flow (input → calculation → history → mode switching)
- E2E: Senior mode complete flow
- Responsive testing: Verify CSS media queries take effect in different viewports
- Accessibility testing: Verify ARIA labels, full keyboard navigation flow
- If bugs are found and can be fixed, fix them directly. If not, notify team-lead.
- Cross-reference requirement.md for item-by-item acceptance, list passed/failed items.

When each task is completed:
1. TaskUpdate marks completed
2. SendMessage notifies team-lead

Working directory: /Users/eric/work/teamtest`
})

12.1.4 Assign Tasks and Set Dependencies

// Assign qa-engineer's tasks
TaskUpdate({ taskId: "5", owner: "qa-engineer" })
TaskUpdate({ taskId: "6", owner: "qa-engineer" })

// Dependencies have been set in Phase A:
// Task #5 blockedBy Task #2 + Task #4 (wait for ui-dev + logic-dev's Phase B)
// Task #6 blockedBy Task #5 (wait for Phase C testing to complete)

12.1.5 Monitor qa-engineer Progress

flowchart TD
    QA["qa-engineer spawn"] --> R1["Read all source code"]
    R1 --> T5["Task #5: Phase C
Unit Testing + Integration Testing"] T5 --> T5A["Extend test-runner.js"] T5A --> T5B["test.js — 48 Unit Tests"] T5B --> T5C["test-dom.js — DOM Integration Tests"] T5C --> T5D["test-a11y.js — Accessibility Tests"] T5D --> T5E["test-runner.html — Browser Test Page"] T5E --> T5F["TaskUpdate completed #5"] T5F --> T6["Task #6: Phase D
E2E + Acceptance"] T6 --> T6A["E2E Complete Flow Testing"] T6A --> T6B["Cross-reference requirement.md for acceptance"] T6B --> T6C["TaskUpdate completed #6"] T6C --> NOTIFY["SendMessage notifies team-lead"] style QA fill:#e1f5fe style NOTIFY fill:#c8e6c9

View qa-engineer running status:

# Find qa-engineer's tmux session
tmux list-panes -a -F '#{session_name}.#{pane_index} #{pane_pid} #{pane_current_command}'

# Switch to qa-engineer's session to view
tmux switch-client -t <session-name>

# Or directly view file changes in the current session
watch -n 5 'ls -la test*.js test*.html 2>/dev/null'

Actual output timeline:

Time File Description
T+2min test.js Unit test framework + core function tests
T+5min test-runner.html Browser test runner
T+8min test-dom.js DOM interaction integration tests
T+10min test-a11y.js Accessibility tests
T+12min test-runner.html update Integrate all tests

12.1.6 Handle qa-engineer's Notifications

After qa-engineer completes, it will SendMessage to notify team-lead. However, based on the notification mechanism discussed earlier, messages might not pop up.

# Proactively check inbox to confirm completion
cat ~/.claude/teams/calc-dev/inboxes/team-lead.json | python3 -c "
import json, sys
msgs = json.load(sys.stdin)
for m in msgs:
    if m.get('from','').startswith('qa'):
        print(f'FROM: {m[\"from\"]}')
        print(f'TIME: {m[\"timestamp\"]}')
        print(f'MSG: {m[\"text\"][:200]}')
        print()
"

If qa-engineer finds a bug:

The qa-engineer prompt states "If bugs are found and can be fixed, fix them directly." If it cannot fix them, it will SendMessage to team-lead describing the issue. The team-lead can decide:

  1. Fix it themselves
  2. Respawns ui-dev / logic-dev to fix it

12.1.7 Testing Framework (test-runner.js)

qa-engineer uses the lightweight testing framework created by logic-dev, with no external dependencies:

class TestRunner {
  describe(name, fn) { /* Test suite */ }
  it(name, fn) { /* Test case */ }
  assert = {
    equal(actual, expected, msg) { /* ... */ },
    deepEqual(actual, expected, msg) { /* ... */ },
    truthy(value, msg) { /* ... */ },
    throws(fn, msg) { /* ... */ },
    closeTo(actual, expected, delta, msg) { /* ... */ }
  }
  run() { /* Execute all tests, output results */ }
}

12.1.8 Unit Tests (test.js) — 48 Cases

const { tokenize, parseExpression, evaluate, /* ... */ } = require('./app.js');

describe('tokenize', () => {
  test('Numbers and operators', () => expect(tokenize('1+2')).toEqual(['1','+','2']));
  test('With parentheses', () => expect(tokenize('(1+2)*3')).toEqual(['(','1','+','2',')','*','3']));
  test('Empty input', () => expect(tokenize('')).toEqual([]));
});

describe('evaluate', () => {
  test('Addition', () => expect(evaluate(parseExpression(tokenize('1+2')))).toBe(3));
  test('Division by zero protection', () => expect(evaluate(parseExpression(tokenize('1/0')))).toBe('Error'));
  test('Parentheses calculation', () => expect(evaluate(parseExpression(tokenize('2*(3+1)')))).toBe(8));
  test('Decimal precision', () => expect(evaluate(parseExpression(tokenize('0.1+0.2')))).toBe(0.3));
});

describe('Boundary conditions', () => {
  test('Multiple nested parentheses', () => {
    expect(evaluate(parseExpression(tokenize('((1+2)*(3+4))')))).toBe(21);
  });
  test('Large number calculation', () => {
    expect(evaluate(parseExpression(tokenize('999999*999999')))).toBe(999998000001);
  });
});