npx skillsauth add excatt/superclaude-plusplus testingInstall this skill globally with one command. Works with Claude Code, Cursor, and Windsurf.
3 of 9 scanners reported clean
Some scanners were skipped, did not run, or reported a non-clean status. Review each row below.
테스트 전략 및 구현 가이드를 실행합니다.
┌───────────┐
│ E2E │ 적게, 핵심 플로우만
│ Playwright│
├───────────┤
│Integration│ API, DB 연동
│ Vitest │
├───────────┤
│ Unit │ 많이, 빠르게
│ Vitest │
└───────────┘
| 유형 | 권장 도구 | 대안 | |-----|----------|------| | Unit/Integration | Vitest | Jest | | E2E | Playwright | Cypress | | Component | Testing Library | Storybook | | API | Vitest + supertest | - | | Visual | Playwright | Chromatic |
// vitest.config.ts
import { defineConfig } from 'vitest/config';
import react from '@vitejs/plugin-react';
export default defineConfig({
plugins: [react()],
test: {
globals: true,
environment: 'jsdom',
setupFiles: ['./tests/setup.ts'],
include: ['**/*.{test,spec}.{js,ts,jsx,tsx}'],
coverage: {
provider: 'v8',
reporter: ['text', 'html', 'lcov'],
exclude: ['node_modules', 'tests'],
thresholds: {
lines: 80,
branches: 80,
functions: 80,
},
},
},
});
// tests/setup.ts
import '@testing-library/jest-dom/vitest';
import { cleanup } from '@testing-library/react';
import { afterEach, vi } from 'vitest';
afterEach(() => {
cleanup();
});
// Mock global
vi.mock('next/navigation', () => ({
useRouter: () => ({
push: vi.fn(),
replace: vi.fn(),
back: vi.fn(),
}),
usePathname: () => '/',
useSearchParams: () => new URLSearchParams(),
}));
import { describe, it, expect, vi, beforeEach } from 'vitest';
describe('Calculator', () => {
it('adds two numbers', () => {
expect(add(1, 2)).toBe(3);
});
it('handles edge cases', () => {
expect(add(0, 0)).toBe(0);
expect(add(-1, 1)).toBe(0);
});
});
// 함수 모킹
const mockFn = vi.fn();
mockFn.mockReturnValue(42);
mockFn.mockResolvedValue({ data: 'test' });
// 모듈 모킹
vi.mock('./api', () => ({
fetchUser: vi.fn().mockResolvedValue({ id: 1, name: 'John' }),
}));
// 타이머 모킹
vi.useFakeTimers();
vi.advanceTimersByTime(1000);
vi.useRealTimers();
import { render, screen, waitFor } from '@testing-library/react';
import userEvent from '@testing-library/user-event';
import { describe, it, expect, vi } from 'vitest';
describe('LoginForm', () => {
it('submits form with valid data', async () => {
const user = userEvent.setup();
const onSubmit = vi.fn();
render(<LoginForm onSubmit={onSubmit} />);
await user.type(screen.getByLabelText(/email/i), '[email protected]');
await user.type(screen.getByLabelText(/password/i), 'password123');
await user.click(screen.getByRole('button', { name: /login/i }));
await waitFor(() => {
expect(onSubmit).toHaveBeenCalledWith({
email: '[email protected]',
password: 'password123',
});
});
});
});
// 1순위: 접근성 기반 (권장)
screen.getByRole('button', { name: /submit/i });
screen.getByLabelText(/email/i);
// 2순위: 시맨틱
screen.getByAltText(/profile/i);
screen.getByText(/welcome/i);
// 3순위: 테스트 ID (최후 수단)
screen.getByTestId('submit-button');
// mocks/handlers.ts
import { http, HttpResponse } from 'msw';
export const handlers = [
http.get('/api/users', () => {
return HttpResponse.json([
{ id: 1, name: 'John' },
]);
}),
http.post('/api/users', async ({ request }) => {
const body = await request.json();
return HttpResponse.json({ id: 3, ...body }, { status: 201 });
}),
];
// tests/setup.ts
import { server } from '../mocks/server';
beforeAll(() => server.listen());
afterEach(() => server.resetHandlers());
afterAll(() => server.close());
// playwright.config.ts
import { defineConfig, devices } from '@playwright/test';
export default defineConfig({
testDir: './e2e',
fullyParallel: true,
retries: process.env.CI ? 2 : 0,
use: {
baseURL: 'http://localhost:3000',
trace: 'on-first-retry',
screenshot: 'only-on-failure',
},
projects: [
{ name: 'chromium', use: { ...devices['Desktop Chrome'] } },
{ name: 'mobile', use: { ...devices['iPhone 14'] } },
],
webServer: {
command: 'npm run dev',
url: 'http://localhost:3000',
reuseExistingServer: !process.env.CI,
},
});
import { test, expect } from '@playwright/test';
test('user can login', async ({ page }) => {
await page.goto('/login');
await page.getByLabel('Email').fill('[email protected]');
await page.getByLabel('Password').fill('password123');
await page.getByRole('button', { name: 'Login' }).click();
await expect(page).toHaveURL('/dashboard');
await expect(page.getByText('Welcome back')).toBeVisible();
});
export class LoginPage {
constructor(private page: Page) {}
async goto() {
await this.page.goto('/login');
}
async login(email: string, password: string) {
await this.page.getByLabel('Email').fill(email);
await this.page.getByLabel('Password').fill(password);
await this.page.getByRole('button', { name: 'Login' }).click();
}
}
// 사용
test('login flow', async ({ page }) => {
const loginPage = new LoginPage(page);
await loginPage.goto();
await loginPage.login('[email protected]', 'password123');
});
# Vitest
npx vitest # watch 모드
npx vitest run # 단일 실행
npx vitest --coverage
# Playwright
npx playwright test
npx playwright test --ui
npx playwright codegen
## Test Strategy
### Test Types
| 유형 | 도구 | 커버리지 |
|------|------|---------|
| Unit | Vitest | 80% |
| E2E | Playwright | 핵심 플로우 |
### Example Tests
```typescript
// 테스트 코드
---
요청에 맞는 테스트 전략을 설계하세요.
testing
사용자 계획을 기존 도메인 모델에 대해 stress-test하는 인터뷰 세션. 용어를 날카롭게 다듬고, 결정이 굳어질 때마다 CONTEXT.md(도메인 어휘 사전)와 ADR을 인라인으로 갱신한다. 새 기능 요구사항 탐색은 `/brainstorm`을, 기존 도메인 모델·용어와의 정합성 점검은 이 스킬을 사용한다.
development
# Excel (XLSX) Spreadsheet Skill Claude Code supports comprehensive spreadsheet operations through the **xlsx** skill, enabling creation, editing, and analysis of Excel files (.xlsx, .xlsm, .csv, .tsv). ## Trigger - When user needs Excel spreadsheet creation or editing - Financial modeling or data analysis required - Spreadsheet formulas and calculations needed - Data import from CSV/TSV files ## Core Capabilities **Primary functions include:** - Creating new spreadsheets with formulas and f
tools
Generate structured implementation workflows from PRDs and feature requirements
development
실시간 통신 설계 가이드를 실행합니다.