Issue #12 | Phase C: Unit Testing and TDD in Practice
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:#ffcdd2Confirmation 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:#c8e6c9View 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:
- Fix it themselves
- 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);
});
});