ring/dev-team/skills/dev-unit-testing/SKILL.md
Fred Amaral f862c50a56
feat(dev-cycle): reclassify gate cadence for ~40-50% speedup
Reclassify gates 1,2,4,5,6w,7w,8 (backend) and 1,2,4,5,6,7 (frontend) from subtask to task cadence. Gates 0, 3, 9 remain subtask-level. All 8 reviewers still run, all quality thresholds preserved.

Additional changes: standards pre-cache at Step 1.5 (cached_standards in state); Gate 0.5 merged into Gate 0 exit criteria via ring:dev-implementation Step 7; dev-report aggregates cycle-wide via accumulated_metrics (single cycle-end dispatch); dev-refactor clusters findings by (file, pattern_category) with findings:[] traceability array; read-after-write state verification removed; per-subtask visual reports opt-in only. State schema v1.1.0 (additive - backward compatible).

New shared patterns: standards-cache-protocol.md, gate-cadence-classification.md.

X-Lerian-Ref: 0x1
2026-04-17 21:25:17 -03:00

18 KiB

name description trigger skip_when NOT_skip_when sequence related input_schema output_schema verification
ring:dev-unit-testing Gate 3 of development cycle - ensures unit test coverage meets threshold (85%+) for all acceptance criteria using TDD methodology. - After implementation and SRE complete (Gate 0/1/2) - Task has acceptance criteria requiring test coverage - Need to verify implementation meets requirements - Not inside a development cycle (ring:dev-cycle) - Task is documentation-only, configuration-only, or non-code - No code implementation was produced (nothing to test) - Changes are limited to CI/CD, infrastructure, or deployment configuration - "Manual testing validates all criteria" → Manual tests are not executable. Gate 3 requires unit tests. - "Integration tests are better" → Gate 3 scope is unit tests only. - "Coverage is close to 85%" → Close enough is not passing. Meet exact threshold.
after before
ring:dev-implementation
ring:dev-devops
ring:dev-ring:sre
ring:codereview
complementary
ring:test-driven-development
ring:qa-analyst
required optional
name type description
unit_id string Task or subtask identifier
name type items description
acceptance_criteria array string List of acceptance criteria to test
name type items description
implementation_files array string Files from Gate 0 implementation
name type enum description
language string
go
typescript
python
Programming language
name type default description
coverage_threshold float 85.0 Minimum coverage percentage (cannot be below 85)
name type description
gate0_handoff object Full handoff from Gate 0
name type items description
existing_tests array string Existing test files
format required_sections metrics
markdown
name pattern required
Testing Summary ^## Testing Summary true
name pattern required
Coverage Report ^## Coverage Report true
name pattern required
Traceability Matrix ^## Traceability Matrix true
name pattern required
Handoff to Next Gate ^## Handoff to Next Gate true
name type values
result enum
PASS
FAIL
name type
coverage_actual float
name type
coverage_threshold float
name type
tests_written integer
name type description
criteria_covered string X/Y format
name type
iterations integer
automated manual
command description success_pattern
go test ./... -covermode=atomic -coverprofile=coverage.out && go tool cover -func=coverage.out | grep total Go tests pass with coverage total:.*[8-9][0-9]|100
command description success_pattern
npm test -- --coverage | grep -E 'All files|Statements' TypeScript tests pass with coverage [8-9][0-9]|100
Every acceptance criterion has at least one test
No skipped or pending tests

Dev Unit Testing (Gate 3)

Overview

Ensure every acceptance criterion has at least one unit test proving it works. Follow TDD methodology: RED (failing test) -> GREEN (implementation) -> REFACTOR.

Core principle: Untested acceptance criteria are unverified claims. Each criterion MUST map to at least one executable unit test.

<block_condition>

  • Coverage below 85% = FAIL
  • Any acceptance criterion without test = FAIL </block_condition>

Coverage threshold: 85% minimum (Ring standard). PROJECT_RULES.md can raise, not lower.

CRITICAL: Role Clarification

This skill ORCHESTRATES. QA Analyst Agent EXECUTES.

Who Responsibility
This Skill Gather requirements, dispatch agent, track iterations
QA Analyst Agent Write tests, run coverage, report results

Step 1: Validate Input

REQUIRED INPUT (from ring:dev-cycle orchestrator):
<verify_before_proceed>
- unit_id exists
- acceptance_criteria is not empty
- implementation_files is not empty
- language is valid (go|typescript|python)
</verify_before_proceed>

```text
- unit_id: [task/subtask being tested]
- acceptance_criteria: [list of ACs to test]
- implementation_files: [files from Gate 0]
- language: [go|typescript|python]

OPTIONAL INPUT:
- coverage_threshold: [default 85.0, cannot be lower]
- gate0_handoff: [full Gate 0 output]
- existing_tests: [existing test files]

if any REQUIRED input is missing:
  → STOP and report: "Missing required input: [field]"
  → Return to orchestrator with error

if coverage_threshold < 85:
  → STOP and report: "Coverage threshold cannot be below Ring minimum (85%)"
  → Use 85% as threshold

Step 2: Initialize Testing State

testing_state = {
  unit_id: [from input],
  coverage_threshold: max(85, [from input]),
  coverage_actual: null,
  verdict: null,
  iterations: 0,
  max_iterations: 3,
  traceability_matrix: [],
  tests_written: 0,
  # Goroutine leak detection (Go only)
  goroutine_check: null,       # NOT_APPLICABLE | REQUIRED
  goroutine_files: 0,          # Count of files with goroutines
  goleak_coverage: null,       # "X/Y" packages with goleak
  leaks_detected: 0,           # Count of actual leaks
  goroutine_verdict: null      # PASS | NEEDS_ACTION | FAIL
}

Step 3: Dispatch QA Analyst Agent

<dispatch_required agent="ring:qa-analyst"> Write unit tests for all acceptance criteria with 85%+ coverage. </dispatch_required>

Task:
  subagent_type: "ring:qa-analyst"
  description: "Write unit tests for [unit_id]"
  prompt: |
    ⛔ WRITE UNIT TESTS for All Acceptance Criteria

    ## Input Context
    - **Unit ID:** [unit_id]
    - **Language:** [language]
    - **Coverage Threshold:** [coverage_threshold]%

    ## Acceptance Criteria to Test
    [list acceptance_criteria with AC-1, AC-2, etc.]

    ## Implementation Files to Test
    [list implementation_files]

    ## Standards Source (Cache-First Pattern)

    **Standards Source (Cache-First Pattern):** This sub-skill reads standards from `state.cached_standards` populated by dev-cycle Step 1.5. If invoked outside a cycle (standalone), it falls back to direct WebFetch with a warning. See `shared-patterns/standards-cache-protocol.md` for protocol details.

    ## Standards Reference

    For Go: https://raw.githubusercontent.com/LerianStudio/ring/main/dev-team/docs/standards/golang.md
    For TS: https://raw.githubusercontent.com/LerianStudio/ring/main/dev-team/docs/standards/typescript.md

    **Cache-first loading protocol:**
    For each required standards URL:
      IF state.cached_standards[url] exists:
        → Read content from state.cached_standards[url].content
        → Log: "Using cached standard: {url} (fetched {state.cached_standards[url].fetched_at})"
      ELSE:
        → WebFetch url (fallback — should not happen if orchestrator ran Step 1.5)
        → Log warning: "Standard {url} was not pre-cached; fetched inline"

    Focus on: Testing Patterns section

    ## Requirements

    ### Test Coverage
    - Minimum: [coverage_threshold]% branch coverage
    - Every AC MUST have at least one test
    - Edge cases REQUIRED (null, empty, boundary, error conditions)

    ### Test Naming
    - Go: `Test{Unit}_{Method}_{Scenario}`
    - TS: `describe('{Unit}', () => { it('should {scenario}', ...) })`

    ### Test Structure
    - One behavior per test
    - Arrange-Act-Assert pattern
    - Mock all external dependencies
    - no database/API calls (unit tests only)

    ### Edge Cases Required per AC Type

    <cannot_skip>
    - Minimum 3 edge cases per AC type
    - null, empty, boundary conditions required
    - Error conditions required
    </cannot_skip>

    | AC Type | Required Edge Cases | Minimum |
    |---------|---------------------|---------|
    | Input validation | null, empty, boundary, invalid format | 3+ |
    | CRUD operations | not found, duplicate, concurrent | 3+ |
    | Business logic | zero, negative, overflow, boundary | 3+ |
    | Error handling | timeout, connection failure, retry | 2+ |

    ### Multi-Tenant Dual-Mode Testing (Go backend only)

    Every repository/service test that accesses a resource (PostgreSQL, MongoDB, Redis, S3, RabbitMQ) must verify BOTH modes. The resolvers in lib-commons v4 work transparently — the same code path handles both modes. Tests verify this contract.

    **Required pattern:** Add dual-mode sub-tests for any test that touches a resource:

    ```go
    func TestCreateAccount(t *testing.T) {
        modes := []struct {
            name         string
            multiTenant  string
        }{
            {"single-tenant", "false"},
            {"multi-tenant", "true"},
        }
        for _, mode := range modes {
            t.Run(mode.name, func(t *testing.T) {
                t.Setenv("MULTI_TENANT_ENABLED", mode.multiTenant)
                // ... same test logic, same assertions
                // Resolvers handle the connection routing transparently
            })
        }
    }
    ```

    **What to verify in multi-tenant mode:**
    - Context contains tenant ID → resolver returns tenant-specific connection
    - Context WITHOUT tenant ID → resolver returns error (not default connection)
    - Backward compat: no MULTI_TENANT_* env vars → works as single-tenant

    **What does NOT need dual-mode tests:**
    - Pure business logic (no resource access)
    - Utility/helper functions
    - Frontend/TypeScript tests

    ## Required Output Format

    ### Test Files Created
    | File | Tests | Lines |
    |------|-------|-------|
    | [path] | [count] | +N |

    ### Coverage Report
    **Command:** [coverage command]
    **Result:**
    ```
    [paste actual coverage output]
    ```

    | Package/File | Coverage |
    |--------------|----------|
    | [name] | [X%] |
    | **TOTAL** | **[X%]** |

    ### Traceability Matrix
    | AC ID | Criterion | Test File | Test Function | Status |
    |-------|-----------|-----------|---------------|--------|
    | AC-1 | [criterion text] | [file] | [function] | ✅/❌ |
    | AC-2 | [criterion text] | [file] | [function] | ✅/❌ |

    ### Quality Checks
    | Check | Status |
    |-------|--------|
    | No skipped tests | ✅/❌ |
    | No assertion-less tests | ✅/❌ |
    | Edge cases per AC | ✅/❌ |
    | Test isolation | ✅/❌ |

    ### VERDICT
    **Coverage:** [X%] vs Threshold [Y%]
    **VERDICT:** PASS / FAIL
    
    If FAIL:
    - **Gap Analysis:** [what needs more tests]
    - **Files needing coverage:** [list with line numbers]    

Step 3.5: Goroutine Leak Detection (Go only)

CONDITIONAL: Only execute if language == "go"

After unit tests pass, detect goroutine usage and verify goleak coverage.

See ring:dev-goroutine-leak-testing for full detection patterns and dispatch templates. See architecture.md for goleak standards.

Detection Logic

if language != "go":
  → Skip to Step 4

# Detect goroutine patterns: "go func(", "go methodCall("
if no goroutine patterns found:
  → testing_state.goroutine_check = "NOT_APPLICABLE"
  → Skip to Step 4

# Goroutines detected
→ testing_state.goroutine_check = "REQUIRED"
→ Dispatch ring:qa-analyst with test_mode="goroutine-leak"

Dispatch

<dispatch_required agent="ring:qa-analyst" test_mode="goroutine-leak"> MUST dispatch with test_mode="goroutine-leak" to detect leaks and verify goleak coverage. </dispatch_required>

Parse Output and Handle Verdict

Verdict Action
PASS Proceed to Step 4
NEEDS_ACTION Dispatch ring:backend-engineer-golang to add goleak tests, re-run
FAIL Dispatch ring:backend-engineer-golang to fix leaks, re-run

Step 4: Parse QA Analyst Output

Parse agent output:

1. Extract coverage percentage from Coverage Report
2. Extract traceability matrix
3. Extract verdict

testing_state.coverage_actual = [extracted coverage]
testing_state.traceability_matrix = [extracted matrix]
testing_state.tests_written = [count from Test Files Created]

if verdict == "PASS" and coverage_actual >= coverage_threshold:
  → testing_state.verdict = "PASS"
  → Proceed to Step 6

if verdict == "FAIL" or coverage_actual < coverage_threshold:
  → testing_state.verdict = "FAIL"
  → testing_state.iterations += 1
  → if iterations >= max_iterations: Go to Step 7 (Escalate)
  → Go to Step 5 (Dispatch Fix)

Step 5: Dispatch Fix to Implementation Agent

Coverage below threshold → Return to Gate 0 for more tests

Task:
  subagent_type: "[implementation_agent from Gate 0]"  # e.g., "ring:backend-engineer-golang"
  description: "Add tests to meet coverage threshold for [unit_id]"
  prompt: |
    ⛔ COVERAGE BELOW THRESHOLD - Add More Tests

    ## Current Status
    - **Coverage Actual:** [coverage_actual]%
    - **Coverage Threshold:** [coverage_threshold]%
    - **Gap:** [threshold - actual]%
    - **Iteration:** [iterations] of [max_iterations]

    ## Gap Analysis (from QA)
    [paste gap analysis from QA output]

    ## Files Needing Coverage
    [paste files list from QA output]

    ## Requirements
    1. Add tests to cover the identified gaps
    2. Focus on edge cases and error paths
    3. Run coverage after each addition
    4. Stop when coverage >= [threshold]%

    ## Required Output
    - Tests added: [list]
    - New coverage: [X%]
    - Coverage command output    

After fix → Go back to Step 3 (Re-dispatch QA Analyst)

Step 6: Prepare Success Output

Generate skill output:

## Testing Summary
**Status:** PASS
**Unit ID:** [unit_id]
**Iterations:** [testing_state.iterations]

## Coverage Report
**Threshold:** [coverage_threshold]%
**Actual:** [coverage_actual]%
**Status:** ✅ PASS

| Package/File | Coverage |
|--------------|----------|
[from QA output]
| **TOTAL** | **[coverage_actual]%** |

## Traceability Matrix
| AC ID | Criterion | Test | Status |
|-------|-----------|------|--------|
[from testing_state.traceability_matrix]

**Criteria Covered:** [X]/[Y] (100%)

## Quality Checks
| Check | Status |
|-------|--------|
| Coverage ≥ threshold | ✅ |
| All ACs tested | ✅ |
| No skipped tests | ✅ |
| Edge cases present | ✅ |
| Goroutine leak check (Go only) | [✅/N/A] |

## Goroutine Leak Report (Go only)
[If language == "go" and goroutines detected]

| Metric | Value |
|--------|-------|
| Goroutines detected | [count] |
| Packages with goleak | [X]/[Y] |
| Leaks found | 0 |
| Status | ✅ PASS |

[If language != "go" or no goroutines: "N/A - No goroutines detected"]

## Handoff to Next Gate
- Testing status: COMPLETE
- Coverage: [coverage_actual]% (threshold: [coverage_threshold]%)
- All criteria tested: ✅
- Ready for Gate 4 (Review): YES

Step 7: Escalate - Max Iterations Reached

Generate skill output:

## Testing Summary
**Status:** FAIL
**Unit ID:** [unit_id]
**Iterations:** [max_iterations] (MAX REACHED)

## Coverage Report
**Threshold:** [coverage_threshold]%
**Actual:** [coverage_actual]%
**Gap:** [threshold - actual]%
**Status:** ❌ FAIL

## Gap Analysis
[from last QA output]

## Files Still Needing Coverage
[from last QA output]

## Handoff to Next Gate
- Testing status: FAILED
- Ready for Gate 4: no
- **Action Required:** User must manually add tests or adjust scope

⛔ ESCALATION: Max iterations (3) reached. Coverage still below threshold.
User intervention required.

Severity Calibration

Severity Criteria Examples
CRITICAL Test infrastructure broken, no coverage possible Test framework failure, build broken
HIGH Coverage below threshold, missing AC tests 84% coverage (below 85%), untested acceptance criteria
MEDIUM Test quality issues, edge case gaps Missing edge case tests, poor assertion messages
LOW Test naming, documentation gaps Non-standard test names, missing test descriptions

Report all severities. CRITICAL/HIGH = immediate fix. MEDIUM = fix in iteration. LOW = document for follow-up.


Pressure Resistance

See shared-patterns/shared-pressure-resistance.md for universal pressure scenarios.

User Says Your Response
"84% is close enough" "85% is minimum threshold. 84% = FAIL. Adding more tests."
"Manual testing covers it" "Gate 3 requires executable unit tests. Dispatching QA analyst."
"Skip testing, deadline" "Testing is MANDATORY. Untested code = unverified claims."

Anti-Rationalization Table

See shared-patterns/shared-anti-rationalization.md for universal anti-rationalizations.

Gate 3-Specific Anti-Rationalizations

Rationalization Why It's WRONG Required Action
"Tool shows 83% but real is 90%" Tool output IS real. Your belief is not. Fix issue, re-measure
"Excluding dead code gets us to 85%" Delete dead code, don't exclude it. Delete dead code
"84.5% rounds to 85%" Rounding is not allowed. 84.5% < 85%. Write more tests
"Close enough with all AC tested" "Close enough" is not passing. Meet exact threshold
"Integration tests cover this" Gate 3 = unit tests only. Different scope. Write unit tests

Unit Test vs Integration Test

Type Characteristics Gate 3?
Unit Mocks all external deps, tests single function YES
Integration Hits real database/API/filesystem no

Execution Report Format

## Testing Summary
**Status:** [PASS|FAIL]
**Unit ID:** [unit_id]
**Duration:** [Xm Ys]
**Iterations:** [N]

## Coverage Report
**Threshold:** [X%]
**Actual:** [Y%]
**Status:** [✅ PASS | ❌ FAIL]

## Traceability Matrix
| AC ID | Criterion | Test | Status |
|-------|-----------|------|--------|
| AC-1 | [text] | [test] | ✅/❌ |

**Criteria Covered:** [X/Y]

## Handoff to Next Gate
- Testing status: [COMPLETE|FAILED]
- Coverage: [X%]
- Ready for Gate 4: [YES|no]