Claude Code によるコードのテストと品質
テストは保守可能なプロジェクトの基礎です。単なるセーフティネットではありません バグに対してだけでなく、 実行可能ドキュメント コードがどのように実行されるかを説明します 行動すべきだ。 Claude Code はテスト作成を劇的にスピードアップします。 TDD からガイド付きリファクタリング、デバッグ支援からエッジケースのカバレッジまで。
この記事では、各レベルのクロード コードを活用する方法を検討します。 テストピラミッド、具体的なプロンプト、実用的な例、ベスト プラクティスを示します。 堅牢で保守可能なテストを実現します。
シリーズ概要
| # | アイテム | 集中 |
|---|---|---|
| 1 | クロードコードの紹介 | セットアップと最初のステップ |
| 2 | コンセプトと要件 | アイデアから仕様まで |
| 3 | コードベースのアーキテクチャ | プロジェクトの構造 |
| 4 | コンテキストとCLAUDE.md | 高度な構成 |
| 5 | コードの構造 | ガイド付き実装 |
| 6 | 迅速なエンジニアリング | 高度なプロンプト |
| 7 | あなたはここにいます - テストと品質 | テストとデバッグ |
| 8 | ドキュメント | README と API ドキュメント |
| 9 | デプロイとDevOps | CI/CD と Docker |
| 10 | プロジェクトの進化 | メンテナンス |
テストピラミッド
テスト ピラミッドは、速度のバランスをとるための最適な戦略を定義します。 カバー範囲と保守性。 Claude Code はあらゆるレベルで役立ちます。
テストピラミッド
/\
/ \
/ E2E\ Pochi (5-10%)
/------\ Lenti, costosi, fragili
/ \ Testano flussi utente completi
/Integration\ Moderati (15-25%)
/--------------\ Velocita media
/ \ Testano interazioni tra componenti
/ Unit Tests \ Molti (70-80%)
/--------------------\ Veloci, isolati, stabili
/ \ Testano singole unita
/________________________\
レベル別の機能
| レベル | スピード | 絶縁 | 料金 | 自信 |
|---|---|---|---|---|
| ユニット | ~1ms | 高い | ベース | 内部ロジック |
| 統合 | ~100ミリ秒 | 中くらい | 中くらい | インタラクション |
| E2E | ~5秒 | ベース | 高い | 完全なシステム |
クロードコードを使用したテスト戦略
Claude Code は、既存のコードから完全なテストを生成することに優れています。 最適な戦略は次のとおりです。
推奨されるワークフロー
| 段階 | アクション | クロード・コード |
|---|---|---|
| 1 | テスト対象のコードを分析する | 「この機能にはどのようなテスト ケースが必要ですか?」 |
| 2 | ハッピーパスのテストを生成する | 「成功事例の単体テストを生成する」 |
| 3 | エッジケースを追加する | 「エッジケースやエラーに対するテストを追加する」 |
| 4 | レビューとリファクタリング | 「これらのテストを見直して読みやすさを向上させてください」 |
| 5 | 適用範囲を確認する | 「カバーされていない行はどれですか?」 |
単体テストの生成
単体テストでは、個々の関数またはクラスを完全に分離してテストします。 これらは高速で信頼性が高く、開発中に即座にフィードバックを提供します。
単体テストの生成を求めるプロンプト
Generate comprehensive unit tests for this service:
```typescript
[PASTE SERVICE CODE HERE]
```
TESTING REQUIREMENTS:
- Framework: Jest with TypeScript
- Coverage target: >90% for this critical service
- Mock ALL external dependencies (repositories, external services)
TEST CATEGORIES TO COVER:
1. Happy Path: Normal successful operations
2. Validation: Invalid inputs, edge cases
3. Error Handling: Expected errors are thrown correctly
4. Edge Cases: Empty arrays, null values, boundary conditions
5. State Changes: Verify side effects (calls to dependencies)
OUTPUT FORMAT:
- Complete test file with all imports
- Use describe/it blocks with clear descriptions
- Include beforeEach for setup
- Group tests by method
- Use AAA pattern (Arrange, Act, Assert)
- Add comments explaining non-obvious test cases
NAMING CONVENTION:
- Describe: "ServiceName"
- Nested describe: "methodName"
- It: "should [expected behavior] when [condition]"
完全な例: UserService 単体テスト
import {{ '{' }} UserService {{ '}' }} from './user.service';
import {{ '{' }} UserRepository {{ '}' }} from './user.repository';
import {{ '{' }} EmailService {{ '}' }} from '@shared/services/email.service';
import {{ '{' }} ValidationError, NotFoundError, ConflictError {{ '}' }} from '@shared/errors';
import {{ '{' }} createUserFixture, validUserDto {{ '}' }} from './fixtures/user.fixtures';
describe('UserService', () => {{ '{' }}
let service: UserService;
let mockUserRepository: jest.Mocked<UserRepository>;
let mockEmailService: jest.Mocked<EmailService>;
beforeEach(() => {{ '{' }}
// Create fresh mocks for each test
mockUserRepository = {{ '{' }}
findById: jest.fn(),
findByEmail: jest.fn(),
create: jest.fn(),
update: jest.fn(),
softDelete: jest.fn(),
findAll: jest.fn(),
{{ '}' }} as any;
mockEmailService = {{ '{' }}
sendWelcomeEmail: jest.fn(),
sendPasswordResetEmail: jest.fn(),
{{ '}' }} as any;
service = new UserService(mockUserRepository, mockEmailService);
{{ '}' }});
afterEach(() => {{ '{' }}
jest.clearAllMocks();
{{ '}' }});
// ================================================
// CREATE USER
// ================================================
describe('createUser', () => {{ '{' }}
describe('happy path', () => {{ '{' }}
it('should create user and send welcome email when valid data provided', async () => {{ '{' }}
// Arrange
const dto = validUserDto;
const expectedUser = createUserFixture({{ '{' }} id: 'user-123', ...dto {{ '}' }});
mockUserRepository.findByEmail.mockResolvedValue(null);
mockUserRepository.create.mockResolvedValue(expectedUser);
mockEmailService.sendWelcomeEmail.mockResolvedValue(undefined);
// Act
const result = await service.createUser(dto);
// Assert
expect(result.id).toBe('user-123');
expect(result.email).toBe(dto.email);
expect(mockUserRepository.create).toHaveBeenCalledWith(
expect.objectContaining({{ '{' }}
name: dto.name,
email: dto.email,
{{ '}' }})
);
expect(mockEmailService.sendWelcomeEmail).toHaveBeenCalledWith(
expectedUser.email,
expectedUser.name
);
{{ '}' }});
{{ '}' }});
describe('validation errors', () => {{ '{' }}
it('should throw ConflictError when email already exists', async () => {{ '{' }}
// Arrange
const existingUser = createUserFixture({{ '{' }} email: 'exists@test.com' {{ '}' }});
mockUserRepository.findByEmail.mockResolvedValue(existingUser);
// Act & Assert
await expect(
service.createUser({{ '{' }} ...validUserDto, email: 'exists@test.com' {{ '}' }})
).rejects.toThrow(ConflictError);
expect(mockUserRepository.create).not.toHaveBeenCalled();
expect(mockEmailService.sendWelcomeEmail).not.toHaveBeenCalled();
{{ '}' }});
it('should throw ValidationError when password is too weak', async () => {{ '{' }}
// Arrange
mockUserRepository.findByEmail.mockResolvedValue(null);
// Act & Assert
await expect(
service.createUser({{ '{' }} ...validUserDto, password: '123' {{ '}' }})
).rejects.toThrow(ValidationError);
{{ '}' }});
{{ '}' }});
describe('edge cases', () => {{ '{' }}
it('should trim whitespace from email', async () => {{ '{' }}
// Arrange
mockUserRepository.findByEmail.mockResolvedValue(null);
mockUserRepository.create.mockResolvedValue(createUserFixture());
// Act
await service.createUser({{ '{' }}
...validUserDto,
email: ' user@test.com '
{{ '}' }});
// Assert
expect(mockUserRepository.findByEmail).toHaveBeenCalledWith('user@test.com');
{{ '}' }});
it('should normalize email to lowercase', async () => {{ '{' }}
// Arrange
mockUserRepository.findByEmail.mockResolvedValue(null);
mockUserRepository.create.mockResolvedValue(createUserFixture());
// Act
await service.createUser({{ '{' }}
...validUserDto,
email: 'User@TEST.com'
{{ '}' }});
// Assert
expect(mockUserRepository.findByEmail).toHaveBeenCalledWith('user@test.com');
{{ '}' }});
{{ '}' }});
{{ '}' }});
// ================================================
// GET USER BY ID
// ================================================
describe('getUserById', () => {{ '{' }}
it('should return user when found', async () => {{ '{' }}
// Arrange
const user = createUserFixture({{ '{' }} id: 'user-123' {{ '}' }});
mockUserRepository.findById.mockResolvedValue(user);
// Act
const result = await service.getUserById('user-123');
// Assert
expect(result).toEqual(user);
expect(mockUserRepository.findById).toHaveBeenCalledWith('user-123');
{{ '}' }});
it('should throw NotFoundError when user does not exist', async () => {{ '{' }}
// Arrange
mockUserRepository.findById.mockResolvedValue(null);
// Act & Assert
await expect(service.getUserById('non-existent'))
.rejects.toThrow(NotFoundError);
{{ '}' }});
it('should throw ValidationError when id is empty', async () => {{ '{' }}
await expect(service.getUserById('')).rejects.toThrow(ValidationError);
{{ '}' }});
{{ '}' }});
{{ '}' }});
結合テスト
統合テストでは、コンポーネントが正しく連携して動作することを検証します。 ルーティング、ミドルウェア、検証、データベース アクセスを含む完全な API をテストします。
統合テストのプロンプト
Generate integration tests for this API:
ENDPOINTS:
- POST /api/users - Create user (requires auth)
- GET /api/users/:id - Get user by ID
- PATCH /api/users/:id - Update user (owner only)
- DELETE /api/users/:id - Soft delete (admin only)
TESTING REQUIREMENTS:
- Framework: Jest + Supertest
- Database: Test PostgreSQL (use transactions, rollback after each test)
- Auth: JWT tokens (mock or real test tokens)
TEST SCENARIOS FOR EACH ENDPOINT:
1. Success case with valid request
2. Validation errors (400) - invalid body, missing fields
3. Authentication errors (401) - missing/invalid token
4. Authorization errors (403) - wrong role/permissions
5. Not found errors (404) - resource doesn't exist
6. Conflict errors (409) - duplicate resources
SETUP/TEARDOWN:
- beforeAll: Create test database, run migrations
- beforeEach: Start transaction
- afterEach: Rollback transaction
- afterAll: Close database connection
Include helper functions for:
- Creating authenticated requests
- Generating test users
- Cleaning up test data
結合テストの例
import request from 'supertest';
import {{ '{' }} app {{ '}' }} from '../app';
import {{ '{' }} db {{ '}' }} from '../database';
import {{ '{' }} createTestUser, getAuthToken, cleanupTestData {{ '}' }} from './helpers';
import {{ '{' }} UserRole {{ '}' }} from '@shared/types';
describe('Users API Integration Tests', () => {{ '{' }}
let adminToken: string;
let userToken: string;
let testUserId: string;
beforeAll(async () => {{ '{' }}
await db.connect();
await db.migrate();
// Create test users and get tokens
const admin = await createTestUser({{ '{' }} role: UserRole.ADMIN {{ '}' }});
const user = await createTestUser({{ '{' }} role: UserRole.USER {{ '}' }});
adminToken = await getAuthToken(admin);
userToken = await getAuthToken(user);
testUserId = user.id;
{{ '}' }});
afterAll(async () => {{ '{' }}
await cleanupTestData();
await db.disconnect();
{{ '}' }});
// ================================================
// POST /api/users - Create User
// ================================================
describe('POST /api/users', () => {{ '{' }}
const validPayload = {{ '{' }}
name: 'Integration Test User',
email: 'integration@test.com',
password: 'SecurePass123!',
{{ '}' }};
describe('success cases', () => {{ '{' }}
it('should create user with valid data (201)', async () => {{ '{' }}
const response = await request(app)
.post('/api/users')
.set('Authorization', `Bearer ${{ '{' }}adminToken{{ '}' }}`)
.send({{ '{' }} ...validPayload, email: `unique-${{ '{' }}Date.now(){{ '}' }}@test.com` {{ '}' }});
expect(response.status).toBe(201);
expect(response.body.data).toMatchObject({{ '{' }}
name: validPayload.name,
email: expect.stringContaining('@test.com'),
{{ '}' }});
expect(response.body.data.password).toBeUndefined();
expect(response.body.data.id).toBeDefined();
{{ '}' }});
{{ '}' }});
describe('validation errors (400)', () => {{ '{' }}
it('should return 400 when email is invalid', async () => {{ '{' }}
const response = await request(app)
.post('/api/users')
.set('Authorization', `Bearer ${{ '{' }}adminToken{{ '}' }}`)
.send({{ '{' }} ...validPayload, email: 'not-an-email' {{ '}' }});
expect(response.status).toBe(400);
expect(response.body.error.code).toBe('VALIDATION_ERROR');
{{ '}' }});
{{ '}' }});
describe('authentication errors (401)', () => {{ '{' }}
it('should return 401 without auth token', async () => {{ '{' }}
const response = await request(app)
.post('/api/users')
.send(validPayload);
expect(response.status).toBe(401);
expect(response.body.error.code).toBe('UNAUTHORIZED');
{{ '}' }});
{{ '}' }});
describe('authorization errors (403)', () => {{ '{' }}
it('should return 403 when non-admin tries to create user', async () => {{ '{' }}
const response = await request(app)
.post('/api/users')
.set('Authorization', `Bearer ${{ '{' }}userToken{{ '}' }}`)
.send(validPayload);
expect(response.status).toBe(403);
expect(response.body.error.code).toBe('FORBIDDEN');
{{ '}' }});
{{ '}' }});
{{ '}' }});
// ================================================
// DELETE /api/users/:id - Soft Delete (Admin Only)
// ================================================
describe('DELETE /api/users/:id', () => {{ '{' }}
it('should soft delete user when admin (204)', async () => {{ '{' }}
const userToDelete = await createTestUser();
const response = await request(app)
.delete(`/api/users/${{ '{' }}userToDelete.id{{ '}' }}`)
.set('Authorization', `Bearer ${{ '{' }}adminToken{{ '}' }}`);
expect(response.status).toBe(204);
// Verify user is soft deleted
const getResponse = await request(app)
.get(`/api/users/${{ '{' }}userToDelete.id{{ '}' }}`)
.set('Authorization', `Bearer ${{ '{' }}adminToken{{ '}' }}`);
expect(getResponse.status).toBe(404);
{{ '}' }});
it('should return 403 when non-admin tries to delete', async () => {{ '{' }}
const response = await request(app)
.delete(`/api/users/${{ '{' }}testUserId{{ '}' }}`)
.set('Authorization', `Bearer ${{ '{' }}userToken{{ '}' }}`);
expect(response.status).toBe(403);
{{ '}' }});
{{ '}' }});
{{ '}' }});
クロード コードによるリファクタリング ガイド
Claude Code は動作を維持しながらリファクタリングを提案できます。 既存のテストがセーフティネットになります。
I want to refactor this code. I have tests that cover its behavior:
CURRENT CODE:
```typescript
[PASTE CODE]
```
EXISTING TESTS (passing):
```typescript
[PASTE TESTS]
```
REFACTORING GOALS:
1. Reduce cyclomatic complexity
2. Extract reusable functions
3. Improve naming
CONSTRAINTS:
- All existing tests MUST still pass
- Don't change public interface
- No new dependencies
Provide:
1. Refactored code
2. Explanation of each change
3. Any new tests needed for extracted functions
クロード コードによるデバッグ支援
Claude Code は、エラーを分析して根本原因を見つけるのに優れています。
This test is failing and I can't figure out why:
TEST:
```typescript
it('should apply discount to order total', async () => {{ '{' }}
const order = await orderService.createOrder({{ '{' }}
items: [{{ '{' }} productId: 'prod-1', quantity: 2 {{ '}' }}],
couponCode: 'SAVE10',
{{ '}' }});
expect(order.total).toBe(90); // Expected $90 after 10% discount
expect(order.discount).toBe(10);
{{ '}' }});
```
ERROR:
```
Expected: 90
Received: 100
```
IMPLEMENTATION:
```typescript
async createOrder(dto: CreateOrderDto): Promise<Order> {{ '{' }}
const items = await this.getOrderItems(dto.items);
const subtotal = items.reduce((sum, item) => sum + item.price * item.quantity, 0);
let discount = 0;
if (dto.couponCode) {{ '{' }}
const coupon = await this.couponRepository.findByCode(dto.couponCode);
if (coupon) {{ '{' }}
discount = subtotal * (coupon.percentage / 100);
{{ '}' }}
{{ '}' }}
return this.orderRepository.create({{ '{' }}
items,
subtotal,
discount,
total: subtotal - discount,
{{ '}' }});
{{ '}' }}
```
Help me:
1. Identify why the test is failing
2. Determine if it's a test bug or implementation bug
3. Provide the fix
Claude コードによるコードレビュー
Claude Code を使用して、テスト自体のコード レビューを実行します。
Review these tests for quality and completeness:
```typescript
[PASTE TESTS]
```
ANALYZE:
1. Test Coverage Gaps
- Missing happy paths
- Missing error scenarios
- Missing edge cases
2. Test Quality Issues
- Tests that test implementation vs behavior
- Flaky test patterns
- Poor isolation (shared state)
- Missing assertions
3. Maintainability Issues
- Unclear test names
- Code duplication
- Missing comments for complex scenarios
4. Best Practices Violations
- Not using AAA pattern
- Multiple assertions testing different things
- Mocking too much or too little
For each issue provide:
- Location (line/describe block)
- Problem description
- Recommended fix with code example
テスト治具と工場
適切に構造化されたフィクスチャにより、テストがより読みやすく、保守しやすくなります。
// fixtures/user.fixtures.ts
import {{ '{' }} User, UserRole, UserStatus {{ '}' }} from '@/entities/user.entity';
import {{ '{' }} CreateUserDto {{ '}' }} from '@/dto/create-user.dto';
// ================================================
// BASE FIXTURES - Valid default values
// ================================================
export const validUserDto: CreateUserDto = {{ '{' }}
name: 'Test User',
email: 'test@example.com',
password: 'SecurePassword123!',
{{ '}' }};
export const validUser: Partial<User> = {{ '{' }}
id: 'user-123',
name: 'Test User',
email: 'test@example.com',
role: UserRole.USER,
status: UserStatus.ACTIVE,
createdAt: new Date('2025-01-01'),
updatedAt: new Date('2025-01-01'),
{{ '}' }};
// ================================================
// FACTORY FUNCTION - Create users with overrides
// ================================================
let userCounter = 0;
export function createUserFixture(overrides: Partial<User> = {{ '{' }}{{ '}' }}): User {{ '{' }}
userCounter++;
return {{ '{' }}
id: `user-${{ '{' }}userCounter{{ '}' }}`,
name: 'Test User',
email: `test-${{ '{' }}userCounter{{ '}' }}@example.com`,
password: '$2b$10$hashedpassword',
role: UserRole.USER,
status: UserStatus.ACTIVE,
createdAt: new Date(),
updatedAt: new Date(),
deletedAt: null,
...overrides,
{{ '}' }} as User;
{{ '}' }}
// ================================================
// INVALID FIXTURES - For testing validation
// ================================================
export const invalidUserDtos = {{ '{' }}
missingName: {{ '{' }}
email: 'test@example.com',
password: 'SecurePassword123!',
{{ '}' }},
invalidEmail: {{ '{' }}
name: 'Test User',
email: 'not-an-email',
password: 'SecurePassword123!',
{{ '}' }},
weakPassword: {{ '{' }}
name: 'Test User',
email: 'test@example.com',
password: '123',
{{ '}' }},
{{ '}' }};
// ================================================
// SCENARIO FIXTURES - Specific test scenarios
// ================================================
export const userScenarios = {{ '{' }}
admin: () => createUserFixture({{ '{' }} role: UserRole.ADMIN {{ '}' }}),
inactive: () => createUserFixture({{ '{' }} status: UserStatus.INACTIVE {{ '}' }}),
deleted: () => createUserFixture({{ '{' }}
status: UserStatus.DELETED,
deletedAt: new Date(),
{{ '}' }}),
{{ '}' }};
// ================================================
// HELPERS
// ================================================
export function resetUserFixtures(): void {{ '{' }}
userCounter = 0;
{{ '}' }}
Claude コードを使用した TDD ワークフロー
テスト駆動開発は、Claude Code を使用するとさらにうまく機能します。 最初にテストを作成してから、クロードに実装を提案させます。
クロードコードを使用した TDD
| ステップ | Tu | クロード・コード |
|---|---|---|
| 1.レッド | 失敗する書き込みテスト | テストケースを提案します |
| 2.グリーン | 実装を依頼する | 最小限のコードを生成する |
| 3. リファクタリング | 改善を求める | リファクタリングを提案する |
| 4.リピート | 新しいテストを追加 | エッジケースを提案する |
I'm following TDD. Here's my failing test:
```typescript
describe('calculateDiscount', () => {{ '{' }}
it('should apply 10% discount for orders over $100', () => {{ '{' }}
expect(calculateDiscount(150)).toBe(15);
{{ '}' }});
it('should apply 20% discount for orders over $500', () => {{ '{' }}
expect(calculateDiscount(600)).toBe(120);
{{ '}' }});
it('should return 0 for orders under $100', () => {{ '{' }}
expect(calculateDiscount(50)).toBe(0);
{{ '}' }});
{{ '}' }});
```
1. Implement the MINIMUM code to make these tests pass
2. Don't add features not covered by tests
3. After implementation, suggest additional edge cases I should test
テストカバレッジとギャップ分析
クロード コードを使用して、発見されていないコードを特定し、不足しているテストを生成します。
My coverage report shows uncovered lines:
FILE: src/services/order.service.ts
UNCOVERED LINES: 45-52, 78-85, 120-135
Here's the code at those lines:
```typescript
// Lines 45-52
if (order.status === 'cancelled') {{ '{' }}
throw new ValidationError('Cannot modify cancelled order');
{{ '}' }}
// Lines 78-85
if (items.some(item => item.quantity > item.stock)) {{ '{' }}
const outOfStock = items.filter(i => i.quantity > i.stock);
throw new ValidationError(`Insufficient stock for: ${{ '{' }}outOfStock.map(i => i.name).join(', '){{ '}' }}`);
{{ '}' }}
// Lines 120-135
if (coupon && coupon.expiresAt < new Date()) {{ '{' }}
throw new ValidationError('Coupon has expired');
{{ '}' }}
if (coupon && coupon.usageCount >= coupon.maxUsage) {{ '{' }}
throw new ValidationError('Coupon usage limit reached');
{{ '}' }}
```
Generate unit tests to cover these scenarios. Include:
1. Test for each branch
2. Boundary conditions
3. Descriptive test names
テストのベストプラクティス
アンチパターン
- 順序依存のテスト
- テスト間で共有される状態
- 曖昧な名前: 「機能するはずです」
- 複数の無関係なアサーション
- テストが細かすぎる
- 過剰なモック(モックのテスト)
- 理由もなくテストが遅い
- 不安定なテストを無視する
ベストプラクティス
- 独立した個別のテスト
- テストごとに新しいセットアップを行う
- わかりやすい名前
- テスト用の論理ステートメント
- 実装ではなく動作をテストする
- 外部依存関係のみをモックする
- 共有設定を最適化する
- 不安定なテストを修正または削除する
テスト品質チェックリスト
合併前
- すべてのテストはローカルで合格します
- 重要なコードのカバレッジ >= 80%
- 不安定なテストはありません (3 回実行)
- テスト名はわかりやすいものです
- エッジケースはカバーされています
- エラーパスがテストされる
- テストでは console.log がありません
- 器具は清潔で再利用可能
- モックは現実的です
- テストは高速です (ユニット合計 10 秒未満)
結論と次のステップ
Claude Code が真価を発揮するのはテストです。完全なテストを最初から生成できます 既存のコードから、考慮していなかったエッジケースを提案し、 開発速度を犠牲にすることなく、高いカバレッジを維持するのに役立ちます。
覚えておいてください: クロード コードによって生成されたテストは、常に次のようにする必要があります。 検証済み。 AI は合格するテストを生成できますが、実際には重要な動作をテストしません。 あるいは、動作ではなく実装に結びつきすぎているということです。
Nel 次の記事 クロードコードの使用方法を見ていきます。 専門的な文書: 完全な README、API ドキュメント OpenAPI、アーキテクチャ決定記録、および JSDoc を使用します。
覚えておくべき重要なポイント
- ピラミッド: 70% ユニット、20% 統合、10% E2E
- 単位: 完全な分離、疑似依存関係
- 統合: 実際の DB を使用して API をエンドツーエンドでテストする
- TDD: 最初にテストを作成し、後で実装する
- カバレッジ: クリティカルなコードでは 80% 以上を目指す
- 備品: 再利用可能なデータのためのファクトリー関数
- 確認する: 生成されたテストを常に確認する







