Skip to main content Skills Marketplace コミュニティが作成したAIスキルを発見・探索
Codex または Claude でインストール この Prompt をコピーして Codex、Claude、または他のアシスタントに貼り付けると、Skill ページを確認してインストールできます。
直接コマンドでは確認用 Prompt が省略されます。実行前にソースを確認してください。
npx skills add https://github.com/miles990/claude-software-skills --skill testing-strategiesコマンドは1行のまま表示されます。コピー前に横へスクロールして全体を確認してください。
ローカルで確認しますか?SkillsMP が現在取得できるファイルをダウンロードできます。
Zipをダウンロード ダウンロード中... name testing-strategies description Unit, integration, E2E testing and TDD practices domain software-engineering version 1.0.0 tags ["testing","unit-test","integration-test","e2e","tdd","mocking"] triggers {"keywords":{"primary":["test","testing","unit test","integration test","e2e","tdd","jest","pytest"],"secondary":["mock","stub","fixture","coverage","assertion","cypress","playwright"]},"context_boost":["quality","ci","automation","reliability"],"context_penalty":["design","architecture","frontend"],"priority":"high"} collaboration {"prerequisites":[],"delegation_triggers":[{"trigger":"Backend service to be tested","delegate_to":"backend","context":"Service architecture, dependencies"},{"trigger":"Frontend component to be tested","delegate_to":"frontend","context":"Component props, user interactions"},{"trigger":"API contract tests","delegate_to":"api-design","context":"API specifications, expected behaviors"},{"trigger":"Database fixtures and test data","delegate_to":"database","context":"Schema structure, seed data patterns"}],"receives_context_from":[{"skill":"backend","receives":["Service dependencies to mock","Integration test scenarios","Database transaction boundaries"]},{"skill":"frontend","receives":["Component structure","User interaction patterns","State management approach"]},{"skill":"api-design","receives":["API contract specifications","Expected response formats"]},{"skill":"database","receives":["Test database setup scripts","Seed data patterns"]}],"provides_context_to":[{"skill":"backend","provides":["Test coverage requirements","Mocking best practices"]},{"skill":"frontend","provides":["Component testing patterns","E2E test scenarios"]}]}
Testing Strategies
Overview
Testing pyramid, patterns, and practices for building reliable software.
Testing Pyramid
/\
/ \
/ E2E\ Few, slow, expensive
/──────\
/ \
/Integration\ Some, medium speed
/──────────────\
/ \
/ Unit Tests \ Many, fast, cheap
/____________________\
Level Speed Scope Quantity Unit Fast (ms) Single function/class Many (70%) Integration Medium (s) Multiple components Some (20%) E2E Slow (min) Full system Few (10%)
Unit Testing
Structure: Arrange-Act-Assert
describe ('calculateDiscount' , () => {
it ('applies 10% discount for orders over $100' , () => {
const order = { items : [{ price : 150 }] };
const discountService = new DiscountService ();
const result = discountService.calculateDiscount (order);
expect (result).toBe (15 );
});
it ('returns 0 for orders under $100' , () => {
const order = { items : [{ price : 50 }] };
discountService = ();
result = discountService. (order);
(result). ( );
});
});
const
new
DiscountService
const
calculateDiscount
expect
toBe
0
Mocking
const mockEmailService = {
send : jest.fn ().mockResolvedValue ({ success : true })
};
const mockUserRepo = {
findById : jest.fn ().mockResolvedValue ({ id : '1' , email : 'test@example.com' })
};
describe ('NotificationService' , () => {
let service : NotificationService ;
beforeEach (() => {
jest.clearAllMocks ();
service = new NotificationService (mockEmailService, mockUserRepo);
});
it ('sends email to user' , async () => {
await service.notifyUser ('1' , 'Hello!' );
expect (mockUserRepo.findById ).toHaveBeenCalledWith ('1' );
expect (mockEmailService.send ).toHaveBeenCalledWith (
'test@example.com' ,
'Hello!'
);
});
it ('throws when user not found' , async () => {
mockUserRepo.findById .mockResolvedValue (null );
await expect (service.notifyUser ('999' , 'Hello!' ))
.rejects .toThrow ('User not found' );
});
});
Testing Edge Cases describe ('parseAge' , () => {
it ('parses valid age string' , () => {
expect (parseAge ('25' )).toBe (25 );
});
it ('handles zero' , () => {
expect (parseAge ('0' )).toBe (0 );
});
it ('handles boundary values' , () => {
expect (parseAge ('1' )).toBe (1 );
expect (parseAge ('150' )).toBe (150 );
});
it ('throws on negative numbers' , () => {
expect (() => parseAge ('-5' )).toThrow ('Age cannot be negative' );
});
it ('throws on non-numeric input' , () => {
expect (() => parseAge ('abc' )).toThrow ('Invalid age format' );
});
it ('throws on empty string' , () => {
expect (() => parseAge ('' )).toThrow ('Age is required' );
});
it ('throws on null' , () => {
expect (() => parseAge (null as any )).toThrow ();
});
});
Integration Testing
API Testing import request from 'supertest' ;
import { app } from '../app' ;
import { db } from '../database' ;
describe ('POST /api/users' , () => {
beforeEach (async () => {
await db.users .deleteMany ({});
});
afterAll (async () => {
await db.disconnect ();
});
it ('creates a new user' , async () => {
const response = await request (app)
.post ('/api/users' )
.send ({
email : 'test@example.com' ,
name : 'Test User'
})
.expect (201 );
expect (response.body ).toMatchObject ({
id : expect.any (String ),
email : 'test@example.com' ,
name : 'Test User'
});
const user = await db.users .findOne ({ email : 'test@example.com' });
expect (user).not .toBeNull ();
});
it ('returns 400 for invalid email' , async () => {
const response = await request (app)
.post ('/api/users' )
.send ({
email : 'invalid-email' ,
name : 'Test User'
})
.expect (400 );
expect (response.body .error ).toBe ('Invalid email format' );
});
it ('returns 409 for duplicate email' , async () => {
await request (app)
.post ('/api/users' )
.send ({ email : 'test@example.com' , name : 'First' });
const response = await request (app)
.post ('/api/users' )
.send ({ email : 'test@example.com' , name : 'Second' })
.expect (409 );
expect (response.body .error ).toBe ('Email already exists' );
});
});
Database Testing with Testcontainers import { PostgreSqlContainer } from '@testcontainers/postgresql' ;
import { Pool } from 'pg' ;
describe ('UserRepository' , () => {
let container : StartedPostgreSqlContainer ;
let pool : Pool ;
let repo : UserRepository ;
beforeAll (async () => {
container = await new PostgreSqlContainer ().start ();
pool = new Pool ({ connectionString : container.getConnectionUri () });
await runMigrations (pool);
repo = new UserRepository (pool);
}, 60000 );
afterAll (async () => {
await pool.end ();
await container.stop ();
});
beforeEach (async () => {
await pool.query ('TRUNCATE users CASCADE' );
});
it ('creates and retrieves user' , async () => {
const created = await repo.create ({
email : 'test@example.com' ,
name : 'Test'
});
const found = await repo.findById (created.id );
expect (found).toEqual (created);
});
});
E2E Testing
Playwright import { test, expect } from '@playwright/test' ;
test.describe ('User Authentication' , () => {
test ('successful login flow' , async ({ page }) => {
await page.goto ('/login' );
await page.fill ('[data-testid="email-input"]' , 'user@example.com' );
await page.fill ('[data-testid="password-input"]' , 'password123' );
await page.click ('[data-testid="login-button"]' );
await expect (page).toHaveURL ('/dashboard' );
await expect (page.locator ('[data-testid="welcome-message"]' ))
.toContainText ('Welcome, user@example.com' );
});
test ('shows error for invalid credentials' , async ({ page }) => {
await page.goto ('/login' );
await page.fill ('[data-testid="email-input"]' , 'wrong@example.com' );
await page.fill ('[data-testid="password-input"]' , 'wrongpassword' );
await page.click ('[data-testid="login-button"]' );
await expect (page.locator ('[data-testid="error-message"]' ))
.toBeVisible ()
.toContainText ('Invalid credentials' );
});
});
test.describe ('Shopping Cart' , () => {
test ('add item and checkout' , async ({ page }) => {
await page.goto ('/login' );
await page.fill ('[data-testid="email-input"]' , 'buyer@example.com' );
await page.fill ('[data-testid="password-input"]' , 'password' );
await page.click ('[data-testid="login-button"]' );
await page.goto ('/products' );
await page.click ('[data-testid="product-1"] [data-testid="add-to-cart"]' );
await expect (page.locator ('[data-testid="cart-count"]' )).toHaveText ('1' );
await page.click ('[data-testid="cart-icon"]' );
await page.click ('[data-testid="checkout-button"]' );
await page.fill ('[data-testid="address"]' , '123 Test St' );
await page.click ('[data-testid="place-order"]' );
await expect (page).toHaveURL (/\/orders\/\d+/ );
await expect (page.locator ('[data-testid="order-status"]' ))
.toContainText ('Order Confirmed' );
});
});
Visual Regression Testing import { test, expect } from '@playwright/test' ;
test ('homepage visual regression' , async ({ page }) => {
await page.goto ('/' );
await page.waitForSelector ('[data-testid="hero-section"]' );
await expect (page).toHaveScreenshot ('homepage.png' , {
maxDiffPixels : 100 ,
threshold : 0.2
});
});
test ('responsive design' , async ({ page }) => {
await page.setViewportSize ({ width : 375 , height : 667 });
await page.goto ('/' );
await expect (page).toHaveScreenshot ('homepage-mobile.png' );
});
Test-Driven Development (TDD)
Red-Green-Refactor Cycle
test ('passwordValidator rejects passwords without numbers' , () => {
const result = validatePassword ('NoNumbers!' );
expect (result.valid ).toBe (false );
expect (result.errors ).toContain ('Must contain at least one number' );
});
function validatePassword (password : string ): ValidationResult {
const errors : string [] = [];
if (!/\d/ .test (password)) {
errors.push ('Must contain at least one number' );
}
return { valid : errors.length === 0 , errors };
}
const VALIDATION_RULES = [
{ pattern : /\d/ , message : 'Must contain at least one number' },
{ pattern : /[A-Z]/ , message : 'Must contain at least one uppercase letter' },
{ pattern : /[a-z]/ , message : 'Must contain at least one lowercase letter' },
{ pattern : /.{8,}/ , message : 'Must be at least 8 characters' }
];
function validatePassword (password : string ): ValidationResult {
const errors = VALIDATION_RULES
.filter (rule => !rule.pattern .test (password))
.map (rule => rule.message );
return { valid : errors.length === 0 , errors };
}
Testing Patterns
Test Fixtures
export const validUser = {
email : 'test@example.com' ,
name : 'Test User' ,
role : 'user'
};
export const adminUser = {
...validUser,
role : 'admin' ,
email : 'admin@example.com'
};
import { validUser, adminUser } from '../fixtures/users' ;
describe ('UserService' , () => {
it ('creates user with valid data' , async () => {
const result = await service.create (validUser);
expect (result.email ).toBe (validUser.email );
});
});
Factory Functions
import { faker } from '@faker-js/faker' ;
export function createUser (overrides : Partial <User > = {} ): User {
return {
id : faker.string .uuid (),
email : faker.internet .email (),
name : faker.person .fullName (),
createdAt : faker.date .past (),
...overrides
};
}
it ('handles users with long names' , () => {
const user = createUser ({ name : 'A' .repeat (100 ) });
const result = formatUserCard (user);
expect (result.displayName ).toHaveLength (50 );
});
Testing Async Code
it ('fetches user data' , async () => {
const user = await userService.getById ('123' );
expect (user.name ).toBe ('John' );
});
it ('fetches user data' , () => {
return userService.getById ('123' ).then (user => {
expect (user.name ).toBe ('John' );
});
});
it ('throws on invalid id' , async () => {
await expect (userService.getById ('invalid' ))
.rejects .toThrow ('User not found' );
});
it ('debounces search input' , async () => {
const onSearch = jest.fn ();
render (<SearchBox onSearch ={onSearch} debounceMs ={300} /> );
await userEvent.type (screen.getByRole ('textbox' ), 'test' );
expect (onSearch).not .toHaveBeenCalled ();
await waitFor (() => {
expect (onSearch).toHaveBeenCalledWith ('test' );
}, { timeout : 500 });
});
Code Coverage
Coverage Metrics Metric What It Measures Line Percentage of lines executed Branch Percentage of if/else branches taken Function Percentage of functions called Statement Percentage of statements executed
Jest Configuration
module .exports = {
collectCoverageFrom : [
'src/**/*.{ts,tsx}' ,
'!src/**/*.d.ts' ,
'!src/**/*.stories.tsx' ,
'!src/test/**'
],
coverageThreshold : {
global : {
branches : 80 ,
functions : 80 ,
lines : 80 ,
statements : 80
}
}
};
Related Skills
[[code-quality]] - Writing testable code
[[devops-cicd]] - CI integration
[[performance-optimization]] - Performance testing
Sharp Edges(常見陷阱)
SE-1: 測試實作而非行為
嚴重度 : high
情境 : 測試過度耦合內部實作,重構時測試全部壞掉
原因 : 測試私有方法、mock 太細、驗證內部狀態
症狀 :
改了一行程式碼,10 個測試失敗
測試檔案比程式碼還長
重構時花更多時間修測試
檢測 : expect.*\.toHaveBeenCalledTimes\(\d{2,}\)|mock.*private|spy.*internal
解法 : 測試公開 API/行為、使用 black-box testing、減少 mock 數量
SE-2: 假陽性測試 (False Positive)
嚴重度 : critical
情境 : 測試永遠通過,但實際上沒有驗證任何東西
原因 : 忘記 await、expect 沒有執行、條件判斷錯誤
症狀 :
測試通過但 bug 仍然存在
刪掉測試中的關鍵 assertion 測試還是通過
Coverage 高但信心低
檢測 : it\(.*\{\s*\}\)|expect\(.*\)(?!\.)|\.resolves(?!\.)|\.rejects(?!\.)
解法 : TDD(先寫失敗的測試)、review 測試程式碼、使用 ESLint no-floating-promises
SE-3: Flaky Tests(不穩定測試)
嚴重度 : high
情境 : 測試有時通過有時失敗,沒有程式碼變更
原因 : 依賴時間、依賴外部服務、競態條件、共享狀態
症狀 :
CI 需要 retry 才能通過
本地通過但 CI 失敗
團隊開始忽略失敗的測試
檢測 : new Date\(\)|Date\.now\(\)|setTimeout.*\d{4,}|sleep\(\d+\)
解法 : 使用 fake timers、隔離測試狀態、避免 hard-coded delays、mock 外部依賴
SE-4: 測試金字塔倒置
嚴重度 : medium
情境 : E2E 測試太多,單元測試太少,CI 超慢
原因 : 「E2E 測試更接近真實」的誤解、不想寫單元測試
症狀 :
CI 跑 30+ 分鐘
測試失敗難以定位問題
E2E 測試經常 flaky
檢測 : describe.*E2E|playwright.*test|cypress.*it (數量遠超 unit test)
解法 : 遵循 70% unit / 20% integration / 10% E2E 比例、E2E 只測關鍵路徑
SE-5: 過度 Mocking
嚴重度 : medium
情境 : Mock 太多導致測試失去意義,只是在測試 mock
原因 : 為了隔離而 mock 所有依賴、測試執行時間焦慮
症狀 :
測試通過但整合時失敗
Mock 的行為與真實行為不符
更新依賴後 mock 過時
檢測 : jest\.mock.*jest\.mock.*jest\.mock|mock\(.*\).*mock\(.*\).*mock\(
解法 : 只 mock 外部依賴(網路、檔案系統)、使用真實的 in-memory 實作、寫更多整合測試
Validations
V-1: 禁止空的測試
類型 : regex
嚴重度 : critical
模式 : (it|test)\s*\([^)]+,\s*(async\s*)?\(\)\s*=>\s*\{\s*\}\s*\)
訊息 : Empty test detected - test has no assertions
修復建議 : Add meaningful assertions with expect()
適用 : *.test.ts, *.test.js, *.spec.ts, *.spec.js
V-2: 測試缺少 assertion
類型 : regex
嚴重度 : high
模式 : (it|test)\s*\([^)]+,\s*(async\s*)?\(\)\s*=>\s*\{[^}]*\}(?![^}]*expect)
訊息 : Test without expect() assertion may be a false positive
修復建議 : Add at least one expect() assertion
適用 : *.test.ts, *.test.js, *.spec.ts, *.spec.js
V-3: 禁止 fit/fdescribe (focused tests)
類型 : regex
嚴重度 : critical
模式 : \b(fit|fdescribe|it\.only|describe\.only|test\.only)\s*\(
訊息 : Focused test will skip other tests in CI
修復建議 : Remove f prefix or .only before committing
適用 : *.test.ts, *.test.js, *.spec.ts, *.spec.js
V-4: 禁止 skip tests 無說明
類型 : regex
嚴重度 : medium
模式 : (xit|xdescribe|it\.skip|describe\.skip|test\.skip)\s*\([^)]+\)
訊息 : Skipped test without documented reason
修復建議 : Add comment explaining why test is skipped and tracking issue
適用 : *.test.ts, *.test.js, *.spec.ts, *.spec.js
V-5: 測試中使用 setTimeout
類型 : regex
嚴重度 : high
模式 : setTimeout\s*\(\s*[^,]+,\s*\d{3,}\s*\)
訊息 : Hard-coded delays in tests cause flakiness and slow tests
修復建議 : Use jest.useFakeTimers() or waitFor() from testing-library
適用 : *.test.ts, *.test.js, *.spec.ts, *.spec.js