testing について
ソフトウェア開発者向けのフレームワーク非依存のテストスキルです。TDD、テストの階層化、モック、カバレッジを扱い、pytest、vitest、jest などで信頼性と保守性の高い振る舞いテストを支援します。
主な機能
- 01TDD のレッド・グリーン・リファクタリング
- 02ユニット・統合・E2E テストの階層化
- 03外部依存のモック方針
- 04テスト構成と命名の指針
- 05カバレッジ目標と完了チェックリスト
- 06pytest、vitest、jest の例
活用シーン
- 01機能実装前に失敗するテストを作成
- 02ユニット・統合・E2E テストの配分を計画
- 03API やデータベースなど外部依存のモックを選定
- 04テストのカバレッジ、独立性、安定性を確認
インストール
コマンドをコピーしてターミナルで実行します。
npx skills add https:/ / github.com/ scientiacapital/ skills --skill testingインストール後、Claude Code で/testingを入力して読み込まれたか確認します。
同梱ファイル
SKILL.md と一緒にインストールされる参考資料やスクリプト。
config.jsonreference/coverage-strategies.mdintegration-testing.mdtest-organization.mdunit-testing.md詳細
このスキルでは、メンテナンスの負担にならずに信頼性を提供するテストを書くことを重視します。テストは高速で信頼性が高く、実装の詳細ではなく振る舞いに焦点を当てるべきです。
<quick_start> TDD の Red-Green-Refactor サイクル:
-
RED: まず失敗するテストを書きます
test('adds numbers', () => { expect(add(1, 2)).toBe(3); // Fails - add() doesn't exist }); -
GREEN: 通るための最小限のコードを書きます
const add = (a, b) => a + b; // Test passes -
REFACTOR: テストがグリーンのままリファクタリングします
テストピラミッド: ユニット 70%、インテグレーション 25%、E2E 5% </quick_start>
<success_criteria> 次の条件を満たすとき、テストは成功です:
- TDD サイクルに従っている: 実装コードの前にテストを書いている
- テストピラミッドのバランスが取れている: ユニット約 70%、インテグレーション約 25%、E2E 約 5%
- テストが独立しており、任意の順序で実行できる
- フレーキーなテストがない(信頼性を確認するために 3 回実行する)
- カバレッジが目標を満たしている: 行 70〜80%、クリティカルパス 100%
- テスト名が振る舞いを説明している(何を + いつ + 期待される結果)
- モックは外部依存に対してのみ使用し、自作コードには使用しない </success_criteria>
<core_principles>
テストの心得
- テストはドキュメントである - 失敗するテストは、まだ実装されていない仕様です
- 実装ではなく振る舞いをテストする - テストはリファクタリングに耐えるべきです
- 高速なフィードバックループ - ユニットテストは秒ではなくミリ秒で実行されます
- デフォルトで分離 - 各テストは独立しているべきです
- Arrange-Act-Assert - すべてのテストで明確な構造を保ちます </core_principles>
<tdd_workflow>
TDD: Red-Green-Refactor
┌─────────────────────────────────────────────────────────┐
│ TDD CYCLE │
│ │
│ ┌─────────┐ │
│ │ RED │ ◄─── Write a failing test │
│ └────┬────┘ │
│ │ │
│ ▼ │
│ ┌─────────┐ │
│ │ GREEN │ ◄─── Write minimum code to pass │
│ └────┬────┘ │
│ │ │
│ ▼ │
│ ┌─────────┐ │
│ │REFACTOR │ ◄─── Clean up while tests stay green │
│ └────┬────┘ │
│ │ │
│ └──────────────► Back to RED │
└─────────────────────────────────────────────────────────┘
ルール
- まず失敗するテストを書く - 失敗するテストなしにプロダクションコードを書いてはいけません
- 失敗するのに十分なテストだけを書く - コンパイルエラーも失敗に含まれます
- 通るのに十分なコードだけを書く - それ以上でもそれ以下でもありません
- グリーンのときだけリファクタリングする - 失敗するテストがある状態でリファクタリングしてはいけません
よくある TDD の間違い
| 間違い | なぜ間違っているか | 代わりに |
|---|---|---|
| コードの後にテストを書く | テストが確証バイアスになる | Red-Green-Refactor |
| プライベートメソッドをテストする | 振る舞いではなく実装をテストしている | 公開インターフェースをテストする |
| テストの複雑さを大きく飛躍させる | 失敗のデバッグが難しい | 小さなステップで進める |
| リファクタリングのステップを飛ばす | 技術的負債が蓄積する | 常にクリーンアップする |
| </tdd_workflow> |
<test_pyramid>
テストピラミッド
┌───────────┐
│ E2E │ Few, slow, expensive
│ Tests │ (minutes)
└─────┬─────┘
│
┌──────────┴──────────┐
│ Integration Tests │ Some, medium speed
│ (API, Database) │ (seconds)
└──────────┬───────────┘
│
┌─────────────────┴─────────────────┐
│ Unit Tests │ Many, fast, cheap
│ (Functions, Components) │ (milliseconds)
└────────────────────────────────────┘
分布のガイドライン
| 種類 | 割合 | 速度 | スコープ |
|---|---|---|---|
| ユニット | 70〜80% | 各 <10ms | 単一の関数/コンポーネント |
| インテグレーション | 15〜25% | 各 <1s | 複数のコンポーネント、DB |
| E2E | 5〜10% | 各 <30s | ユーザーフロー全体 |
どこで何をテストするか
ユニットテスト:
- 純粋関数
- ビジネスロジック
- データ変換
- バリデーションルール
- コンポーネントのレンダリング
インテグレーションテスト:
- API エンドポイント
- データベース操作
- サービス間のやり取り
- コンポーネントの統合
E2E テスト:
- クリティカルなユーザーフロー(ログイン、チェックアウト)
- ハッピーパスのみ
- スモークテスト </test_pyramid>
<when_to_mock>
モッキング戦略
ロンドン派 vs デトロイト派
ロンドン派(モッキスト):
- すべての依存をモックする
- 完全に分離してテストする
- テストは非常に焦点が絞られている
デトロイト派(クラシシスト):
- 外部サービスのみをモックする
- 自然な単位をまとめてテストする
- テストはより現実に即している
推奨: 実用的なアプローチ
- 外部サービス(API、ユニットテストにおける DB)をモックする
- 必要でない限り自作コードをモックしない
- インテグレーションテストでは実際の実装を使用する
何をモックするか
| モックする | モックしない |
|---|---|
| 外部 API | 自作の純粋関数 |
| ファイルシステム(ユニットテストにおいて) | データ変換 |
| ネットワークリクエスト | ビジネスロジック |
| 時間/乱数 | インメモリのデータ構造 |
| 高コストな計算 | 単純なユーティリティ |
モッキングパターン
// GOOD: Mock external dependency
const mockFetch = vi.fn().mockResolvedValue({ data: [] });
// BAD: Mocking your own utilities
const mockFormatDate = vi.fn(); // Don't do this
// GOOD: Dependency injection for testability
function createService(httpClient = fetch) {
return {
getData: () => httpClient('/api/data')
};
}
// In test:
const mockClient = vi.fn();
const service = createService(mockClient);
</when_to_mock>
<test_structure>
テストの構成
ファイルの命名
src/
├── components/
│ ├── Button.tsx
│ └── Button.test.tsx # Colocated test
├── utils/
│ ├── format.ts
│ └── format.test.ts
└── __tests__/ # Or separate folder
└── integration/
└── api.test.ts
テストの命名
// Pattern: describe what + when + expected result
describe('UserService', () => {
describe('createUser', () => {
it('returns user object when given valid email', () => {});
it('throws ValidationError when email is invalid', () => {});
it('sends welcome email after successful creation', () => {});
});
});
// Alternative: BDD style
describe('UserService', () => {
describe('when creating a user with valid data', () => {
it('should return the created user', () => {});
it('should send a welcome email', () => {});
});
describe('when email is invalid', () => {
it('should throw ValidationError', () => {});
});
});
Arrange-Act-Assert
it('calculates total with discount', () => {
// Arrange - set up test data
const cart = createCart([
{ price: 100, quantity: 2 },
{ price: 50, quantity: 1 }
]);
const discount = 0.1;
// Act - perform the action
const total = calculateTotal(cart, discount);
// Assert - verify result
expect(total).toBe(225); // (200 + 50) * 0.9
});
</test_structure>
<what_not_to_test>
テストしないもの
次のものは省略します
- フレームワークのコード - React の useState、Express のルーティング
- サードパーティライブラリ - 独自のテストがあります
- 単純なゲッター/セッター - ロジックがなければテストは不要です
- 実装の詳細 - プライベートメソッド、内部状態
- 1行の関数 - 複雑なロジックがある場合を除きます
注力する項目
- ビジネスロジック - バグが潜む場所です
- エッジケース - null、空の配列、境界値
- エラーパス - 問題が起きたときの動作
- ユーザー向けの動作 - ユーザーが実際に行うこと
- リグレッション - 一度修正したバグの再発
カバレッジの目標
| 指標 | 目標 | 備考 |
|---|---|---|
| 行カバレッジ | 70-80% | 高ければよいとは限りません |
| 分岐カバレッジ | 70-80% | 行カバレッジより重要です |
| 重要な処理経路 | 100% | 認証、支払い、データの変更 |
警告: カバレッジが100%でも、優れたテストとは限りません。意味のある内容をテストせずに、すべての行を実行する不適切なテストもあります。 </what_not_to_test>
詳しいパターンについては、適切なリファレンスを読み込んでください。| トピック | リファレンスファイル | 読み込むタイミング |
|---|---|---|
| 単体テストのパターン | reference/unit-testing.md | 単体テストの作成、モックの使用時 |
| 統合テスト | reference/integration-testing.md | API テスト、データベーステスト時 |
| テストの構成 | reference/test-organization.md | テストスイートの構成時 |
| カバレッジ戦略 | reference/coverage-strategies.md | カバレッジ目標の設定時 |
読み込むには: トピックを指定するか、コンテキストから判断してください。
<framework_patterns>
フレームワーク別クイックリファレンス
pytest (Python)
# Fixtures
@pytest.fixture
def user():
return User(name="test")
def test_user_greet(user):
assert user.greet() == "Hello, test"
# Parametrize
@pytest.mark.parametrize("input,expected", [
("hello", "HELLO"),
("world", "WORLD"),
])
def test_uppercase(input, expected):
assert uppercase(input) == expected
vitest/jest (TypeScript)
// Basic test
test('adds numbers', () => {
expect(add(1, 2)).toBe(3);
});
// Mock
vi.mock('./api', () => ({
fetchUser: vi.fn().mockResolvedValue({ name: 'test' })
}));
// Component test
import { render, screen } from '@testing-library/react';
test('renders button', () => {
render(<Button>Click</Button>);
expect(screen.getByRole('button')).toHaveTextContent('Click');
});
Testing Library の原則
- テスト ID ではなくロールでクエリします
- 実装ではなく、ユーザーに見える内容をテストします
fireEventよりuserEventを優先します- 内部状態のテストは避けます </framework_patterns>
コードの完成とする前に、次の項目を確認してください。
- 単体テストで正常系をカバーしている
- 単体テストでエラーケースをカバーしている
- エッジケースをテストしている(null、空、境界値)
- API エンドポイントの統合テストがある
- 不安定なテストがない(3回実行して確認)
- テストが独立している(任意の順序で実行できる)
- テスト名が動作を説明している
- ハードコードされたタイムアウトがない(waitFor を使用)
- テスト間でモックがリセットされる
- カバレッジがプロジェクトの基準を満たしている
結果サイドカーの出力
最後の手順として、~/.claude/skill-analytics/last-outcome-testing.json に書き込みます。
{"ts":"[UTC ISO8601]","skill":"testing","version":"1.0.0","variant":"default",
"status":"[success|partial|error]","runtime_ms":[estimated ms from start],
"metrics":{"tests_written":[n],"coverage_delta_pct":[n]},
"error":null,"session_id":"[YYYY-MM-DD]"}
一部の段階が失敗しても結果が生成された場合は、ステータスに「partial」を使用してください。出力が何も生成されなかった場合に限り、「error」を使用してください。