Skip to main content

testability

Make features testable by design. Testing pyramid from fast (local) to slow (UI). Expose APIs securely for testing.

소스 정보

저장소
different-ai/agent-bank
최근 소스 활동
2026년 1월 23일 00:08
감지된 SKILL.md 언어
영어
스타
251
포크
45

설치 방법

기본적으로 소스를 먼저 확인하는 Prompt가 선택됩니다. 직접 명령으로 전환하거나 로컬 사본을 다운로드할 수도 있습니다.

소스 파일 검토

설치 여부를 결정하기 전에 SKILL.md와 SkillsMP에 표시된 보조 파일을 읽어 보세요.

SKILL.md 표시 중

SKILL.md
소스 지침 · 읽기 전용 미리보기
name
testability
description
Make features testable by design. Testing pyramid from fast (local) to slow (UI). Expose APIs securely for testing.
license
MIT
compatibility
opencode
metadata
{"audience":"developers","workflow":"development","trigger":"before-writing-code"}
## What I Do Ensure every feature you build is **testable from the start**. This skill teaches: 1. Testing pyramid (fast → slow) 2. API exposure patterns for testability 3. Local testing setup 4. Integration with staging tests 5. When to use each testing layer ## Core Philosophy > **"If you can't test it locally, you can't test it."** Every feature should be testable at multiple levels. Design for testability, don't bolt it on later. --- ## Testing Pyramid From **fastest** (run constantly) to **slowest** (run occasionally): ``` ▲ /U\ UI Tests (E2E) / I \ - Browser automation /-----\ - Run on staging only / API \ API/Integration Tests / TESTS \ - tRPC procedures /-----------\ - Can run locally / UNIT \ Unit Tests / TESTS \ - Pure functions /------------------\ - Fastest, run always ``` | Layer | Speed | Where | When to Use | | --------------- | ------ | -------------- | ----------------------------------- | | Unit | <1s | Local | Pure logic, utils, calculations | | API/Integration | 1-10s | Local + CI | tRPC, DB operations, business logic | | Staging | 30s-2m | Vercel preview | Full flow verification | | UI/E2E | 2-5m | Staging only | Critical user journeys | --- ## Layer 1: Unit Tests (Fastest) ### When to Use - Pure functions with no side effects - Calculations, formatting, validation - Business logic that doesn't touch DB/APIs ### Pattern ```typescript // packages/web/src/lib/utils/calculate-fee.ts export function calculateFee(amount: number, feePercent: number): number { return amount * (feePercent / 100); } // packages/web/src/lib/utils/calculate-fee.test.ts import { describe, it, expect } from 'vitest'; import { calculateFee } from './calculate-fee'; describe('calculateFee', () => { it('calculates 1% fee correctly', () => { expect(calculateFee(1000, 1)).toBe(10); }); it('handles zero amount', () => { expect(calculateFee(0, 5)).toBe(0); }); }); ``` ### Running Unit Tests ```bash cd packages/web pnpm test # Run all tests (watch mode) pnpm test -- --run # Run once and exit pnpm test:watch # Watch mode pnpm test -- --run --grep "fee" # Filter by name ``` > Repo note: `@zero-finance/web` Vitest discovers tests under `packages/web/src/test/**/*.test.ts`. Put new tests there (or update Vitest config) so they get picked up. --- ## Layer 2: API/Integration Tests ### When to Use - tRPC procedures - Database operations - External API integrations (mocked) - Business logic with dependencies ### Pattern: Testing tRPC Procedures ```typescript // packages/web/src/server/routers/earn/get-balance.test.ts import { describe, it, expect, vi, beforeEach } from 'vitest'; import { createTestContext } from '@/test/context'; import { earnRouter } from './index'; describe('earn.getBalance', () => { let ctx: ReturnType<typeof createTestContext>; beforeEach(() => { ctx = createTestContext({ user: { privyDid: 'test-user-did' }, workspaceId: 'test-workspace-id', }); }); it('returns balance for valid user', async () => { const caller = earnRouter.createCaller(ctx); const result = await caller.getBalance({ chainId: 8453 }); expect(result).toHaveProperty('balance'); expect(typeof result.balance).toBe('string'); }); }); ``` ### Pattern: Mocking External Services ```typescript // Mock Privy vi.mock('@privy-io/server-auth', () => ({ PrivyClient: vi.fn().mockImplementation(() => ({ getUser: vi.fn().mockResolvedValue({ id: 'test-user' }), })), })); // Mock Database vi.mock('@/db', () => ({ db: { query: { userSafes: { findFirst: vi.fn().mockResolvedValue({ safeAddress: '0x1234...', chainId: 8453, }), }, }, }, })); ``` ### Test Database Setup For tests that need a real database: ```typescript // packages/web/src/test/setup-db.ts import { drizzle } from 'drizzle-orm/neon-http'; import { neon } from '@neondatabase/serverless'; export async function createTestDb() { // Use a test-specific database const sql = neon(process.env.TEST_DATABASE_URL!); return drizzle(sql); } ``` --- ## Layer 3: Staging Tests (Vercel Preview) ### When to Use - Full end-to-end flows - Features that involve multiple services - UI changes that need visual verification - Flows that can't be mocked locally ### Workflow ```bash # 1. Push your branch git push -u origin feat/my-feature # 2. Wait for deployment LATEST=$(vercel ls --scope prologe 2>/dev/null | head -1) vercel inspect "$LATEST" --scope prologe --wait --timeout 5m # 3. Test on preview URL # Use Chrome MCP or manual testing ``` ### Integration with test-staging-branch Skill Load the `test-staging-branch` skill for: - Chrome automation login flow - Gmail OTP extraction - PR reporting ``` skill("test-staging-branch") ``` --- ## Layer 4: UI/E2E Tests (Slowest) ### When to Use - Critical user journeys only - After all other layers pass - For regression prevention ### Playwright Tests ```typescript // packages/web/tests/dashboard.spec.ts import { test, expect } from '@playwright/test'; test('user can view dashboard balance', async ({ page }) => { // Login would use test fixtures await page.goto('/dashboard'); await expect(page.getByText('Total Balance')).toBeVisible(); await expect(page.getByTestId('balance-amount')).toBeVisible(); }); ``` ### Running E2E Tests ```bash cd packages/web pnpm exec playwright test pnpm exec playwright test --ui # Interactive mode ``` --- ## Making Code Testable ### Pattern 1: Dependency Injection ```typescript // BAD - Hard to test export async function getBalance() { const safe = await db.query.userSafes.findFirst({...}); const balance = await fetch(`https://api.example.com/balance/${safe.address}`); return balance; } // GOOD - Testable export async function getBalance( deps: { getSafe: () => Promise<UserSafe>, fetchBalance: (address: string) => Promise<string>, } ) { const safe = await deps.getSafe(); const balance = await deps.fetchBalance(safe.address); return balance; } ``` ### Pattern 2: Extract Pure Functions ```typescript // BAD - Logic mixed with I/O export async function processTransfer(amount: number) { const fee = amount * 0.01; const total = amount + fee; await db.insert(transfers).values({ amount, fee, total }); return { amount, fee, total }; } // GOOD - Pure function extractable export function calculateTransferFees(amount: number) { const fee = amount * 0.01; const total = amount + fee; return { amount, fee, total }; } export async function processTransfer(amount: number) { const calculated = calculateTransferFees(amount); await db.insert(transfers).values(calculated); return calculated; } // Now calculateTransferFees is easily unit testable! ``` ### Pattern 3: Test IDs in UI ```tsx // Add data-testid for E2E tests <div data-testid="balance-card"> <span data-testid="balance-amount">{balance}</span> </div> ``` ### Pattern 4: API Routes for Testing Expose internal state via API routes that are: - Only available in development/test - Protected in production ```typescript // packages/web/src/app/api/test/state/route.ts import { NextResponse } from 'next/server'; export async function GET() { // Only in development if (process.env.NODE_ENV === 'production') { return NextResponse.json({ error: 'Not available' }, { status: 403 }); } // Return internal state for testing return NextResponse.json({ safesCount: await db.select().from(userSafes).count(), // ... other debug info }); } ``` --- ## Local Testing Setup ### Environment Files ```bash # .env.test - Test-specific config DATABASE_URL="postgres://test:test@localhost:5432/test_db" PRIVY_APP_ID="test-app-id" # ... mocked values ``` ### Test Utilities Create reusable test helpers: ```typescript // packages/web/src/test/fixtures.ts export const testUser = { privyDid: 'did:privy:test-user', email: 'test@example.com', }; export const testSafe = { address: '0x1234567890123456789012345678901234567890', chainId: 8453, }; // packages/web/src/test/context.ts export function createTestContext(overrides = {}) { return { user: testUser, workspaceId: 'test-workspace', db: mockDb, ...overrides, }; } ``` --- ## Testing Checklist (Per Feature) Before considering a feature "done": ``` [ ] Unit tests for pure functions [ ] Integration tests for tRPC procedures [ ] Mocks for external services [ ] Test IDs in UI components [ ] Manual test on staging (if applicable) [ ] E2E test for critical paths only ``` --- ## Common Anti-Patterns ### Don't: Test implementation details ```typescript // BAD - Tests internal state expect(component.state.isLoading).toBe(false); // GOOD - Tests observable behavior expect(screen.getByText('Loading...')).not.toBeVisible(); ``` ### Don't: Over-mock ```typescript // BAD - Mock everything vi.mock('@/db'); vi.mock('@/lib/api'); vi.mock('@/hooks/use-user'); // ... 10 more mocks // GOOD - Mock only external boundaries vi.mock('@/lib/external-api'); // Third-party only ``` ### Don't: Write E2E tests for everything ```typescript // BAD - E2E for simple validation test('email validation shows error', async ({ page }) => { // This should be a unit test! }); // GOOD - E2E for critical flows only test('user can complete payment flow', async ({ page }) => { // Multi-step, multi-service flow }); ``` --- ## Integration with Other Skills | Scenario | Skill to Use | | ------------------------ | --------------------- | | Testing fails on staging | `test-staging-branch` |
GitHub에서 보기
이 SKILL.md는 매우 커서 SkillsMP가 여기에는 첫 섹션만 미리 보여줍니다. GitHub에서 보기