テストコード入門
基礎から重要性・書き方・Claude Code でのコードレビューまで——開発を始めたばかりの人が最初に読む記事
Claude Code でコードを書けるようになってきた。でも「テストコードって何?」「なぜ書かないといけないの?」という疑問を持っている方は多いはずです。
テストコードは「書かなくても動く」ものです。でも、書かずに開発を続けると、必ず「どこかを直したら別のところが壊れた」「リリース直前に大量のバグが出た」という状況に追い込まれます。
本記事では、テストコードとは何かという基礎から、なぜ重要なのか・何に気をつけるべきか、そして Claude Code を使ったコードレビューの方法まで、開発を始めた方が最初に知っておくべきことをまとめました。
目次
1. テストコードとは何か
テストコードとは、「自分が書いたコードが正しく動くかを自動で確認するためのコード」です。
たとえば、税込み価格を計算する関数を作ったとします。
// 税込み価格を計算する関数
function calcPriceWithTax(price) {
return price * 1.1;
}
この関数が正しく動くかを確認するテストコードは、こんな形になります。
// テストコード
test('1000円の税込みは1100円になる', () => {
expect(calcPriceWithTax(1000)).toBe(1100);
});
test('0円の税込みは0円になる', () => {
expect(calcPriceWithTax(0)).toBe(0);
});
テストを実行すると「✅ 合格」または「❌ 失敗」が表示されます。このチェックを人間が毎回手で確認する代わりに、コンピュータが自動でやってくれるのがテストコードの役割です。
- 準備(Arrange):テストに必要なデータや状態を用意する
- 実行(Act):テストしたい関数やメソッドを呼び出す
- 確認(Assert):期待した結果になっているかチェックする
2. なぜテストコードが必要なのか
理由①「どこかを直したら別のところが壊れた」を防ぐ
開発が進むにつれて、コードは複雑になっていきます。あるファイルを修正したら、全然別の機能が壊れてしまう——これをデグレ(デグレード)と呼びます。テストコードがあれば、修正のたびに全テストを実行することで、デグレを即座に検出できます。
- 「この関数、触っていいのか怖くて変更できない」状態になる
- リリース直前に大量のバグが発覚し、修正コストが爆発する
- 新しいメンバーがコードを変更するたびに何かが壊れる
- 「とりあえず動いているから触らない」レガシーコードが積み重なる
理由②「仕様の文書」としての役割
テストコードは「この関数はこういうときにこう動くべき」という仕様書になります。新しいメンバーがテストコードを読むだけで「このコードは何をするものか」が理解できます。コメントや README より正確で、常に最新の状態が保たれます。
理由③「自信を持ってデプロイできる」
テストがすべて通っている状態でリリースするのと、手動で「たぶん大丈夫」とリリースするのでは、精神的な安心感がまったく違います。CI/CD(自動デプロイ)と組み合わせると、テストが通らない限り本番環境にデプロイされない仕組みも作れます。
理由④「リファクタリングが怖くなくなる」
リファクタリング(コードの整理・改善)はテストがないと非常にリスクが高い作業です。「動いているコードを触って壊すかもしれない」という恐怖があるからです。テストがあれば、リファクタリング後にテストを実行して「合格」していれば動作が変わっていないことが保証されます。
テストを書くのに「時間がかかる」と感じる方が多いですが、実際にはバグを後から直すコストの方がはるかに大きいです。バグの発見・修正・影響調査・リリース・確認のコストを合計すると、最初にテストを書いておく方が圧倒的に効率的です。Google の研究では「テストを書かないと長期的に開発速度が最大50%低下する」という報告もあります。
3. テストの種類と使い分け
テストにはいくつかの種類があります。小さいものから順に理解しましょう。
| 種類 | 対象 | 速さ | 最初に書くべき? |
|---|---|---|---|
| ユニットテスト | 関数・クラス単体 | 非常に速い | ✅ まずここから |
| 統合テスト | 複数の機能・DB連携 | やや遅い | ✅ 次のステップ |
| E2Eテスト | ブラウザ操作全体 | 遅い | △ 余裕ができたら |
ユニットテスト(最初に書くべきもの)
1つの関数・クラスが単体で正しく動くかを確認します。外部 DB や API に依存しないので非常に高速です。テストを書き始めたばかりの人はまずユニットテストだけ書けるようになることを目標にしましょう。
統合テスト
複数のモジュールが連携したとき・データベースと組み合わせたときに正しく動くかを確認します。「ユーザー登録 → メール送信 → DB 保存」のような一連のフローをテストします。
E2Eテスト(End-to-End テスト)
Playwright や Cypress などを使って、実際のブラウザを操作してユーザーの行動をシミュレートするテストです。最も現実に近いですが実行に時間がかかるため、重要な画面フローに絞って書きます。
「ユニットテストを多く・統合テストを中程度・E2Eテストを少量」という配分が理想とされています。ユニットテストは速くて安くてたくさん書けます。E2Eテストは現実に近いが遅くてコストが高い。ピラミッドの底辺(ユニット)を厚くするのが基本です。
4. テストコードの書き方(基本パターン)
JavaScript / TypeScript(Vitest・Jest)
// テスト対象の関数(src/utils/tax.ts)
export function calcPriceWithTax(price: number): number {
if (price < 0) throw new Error('価格は0以上である必要があります');
return Math.round(price * 1.1);
}
// テストコード(src/utils/tax.test.ts)
import { describe, it, expect } from 'vitest';
import { calcPriceWithTax } from './tax';
describe('calcPriceWithTax', () => {
it('1000円の税込みは1100円になる', () => {
expect(calcPriceWithTax(1000)).toBe(1100);
});
it('0円の税込みは0円になる', () => {
expect(calcPriceWithTax(0)).toBe(0);
});
it('端数は四捨五入される', () => {
expect(calcPriceWithTax(100)).toBe(110);
expect(calcPriceWithTax(1)).toBe(1); // 1 * 1.1 = 1.1 → 1
});
it('負の値を渡すとエラーが投げられる', () => {
expect(() => calcPriceWithTax(-1)).toThrow('価格は0以上である必要があります');
});
});
Python(pytest)
# テスト対象の関数(utils/tax.py)
def calc_price_with_tax(price: float) -> int:
if price < 0:
raise ValueError("価格は0以上である必要があります")
return round(price * 1.1)
# テストコード(tests/test_tax.py)
import pytest
from utils.tax import calc_price_with_tax
def test_standard_price():
assert calc_price_with_tax(1000) == 1100
def test_zero_price():
assert calc_price_with_tax(0) == 0
def test_negative_price_raises_error():
with pytest.raises(ValueError, match="価格は0以上である必要があります"):
calc_price_with_tax(-1)
テストの実行コマンド
# Vitest(JavaScript/TypeScript)
npm run test
npm run test -- --watch # ファイル変更を監視して自動実行
npm run test -- --coverage # カバレッジレポートを出力
# Jest
npx jest
npx jest --watchAll
# pytest(Python)
pytest
pytest -v # 詳細表示
pytest --cov=src # カバレッジ計測
5. テストコードを書くときに気をつけること
① テストは「1テスト・1つの確認」にする
1つのテスト関数に複数の確認を詰め込むと、どこで失敗したか分かりにくくなります。
it('価格計算のテスト', () => {
expect(calcPriceWithTax(1000)).toBe(1100);
expect(calcPriceWithTax(0)).toBe(0);
expect(calcPriceWithTax(-1))
.toThrow();
});
it('1000円 → 1100円になる', () => {
expect(calcPriceWithTax(1000))
.toBe(1100);
});
it('0円 → 0円になる', () => {
expect(calcPriceWithTax(0)).toBe(0);
});
② テスト名は「何をテストしているか」が分かるように書く
it('test1', () => { ... });
it('エラーテスト', () => { ... });
it('価格', () => { ... });
it('価格が1000円のとき1100円を返す', () => {...});
it('負の値を渡すとエラーを投げる', () => {...});
it('0円のとき0円を返す', () => {...});
③ 外部サービス・DBはモック(偽物)に置き換える
本物のデータベースや外部 API をテストで使うと、テストが遅くなり・環境によって結果が変わり・テストのたびにデータが増えます。ユニットテストではモック(偽の動作をするオブジェクト)に置き換えます。
// メール送信サービスをモックに置き換える例(Vitest)
import { vi } from 'vitest';
import { sendWelcomeEmail } from './email';
vi.mock('./email', () => ({
sendWelcomeEmail: vi.fn().mockResolvedValue({ success: true })
}));
it('ユーザー登録時にウェルカムメールが送信される', async () => {
await registerUser({ email: 'test@example.com' });
expect(sendWelcomeEmail).toHaveBeenCalledWith('test@example.com');
});
④ 境界値・異常系を忘れずにテストする
多くのバグは「正常な入力」ではなく「端っこの値(境界値)」や「おかしな入力(異常系)」で発生します。
| テストの種類 | 例 |
|---|---|
| 正常系 | 普通の入力で期待通りの結果が返るか |
| 境界値 | 0・最大値・最小値・空文字・空配列など |
| 異常系 | null・undefined・負の数・型が違う値など |
⑤ テストコードもリファクタリングする
テストコードもメンテナンスが必要なコードです。同じセットアップ処理が繰り返されているなら beforeEach でまとめる、重複したアサーションはヘルパー関数にまとめる、など読みやすく保つことが大切です。
⑥ カバレッジ100%を目指さない
コードカバレッジ(テストが通過したコードの割合)100%を目指すと、意味のないテストを量産してしまいます。目安は70〜80%。重要なビジネスロジック・エラーが出やすい複雑な処理を優先してテストし、単純なゲッター・設定ファイルなどは後回しにします。
6. Claude Code でテストコードを生成する方法
Claude Code はテストコードの生成が得意です。「このコードのテストを書いて」と指示するだけで、適切なテストケースを自動生成してくれます。
基本的な依頼方法
# シンプルな指示
"src/utils/tax.ts のテストコードを Vitest で書いて"
# より具体的な指示(精度が上がる)
"src/auth/login.ts のテストコードを書いて。
以下のケースを必ずカバーしてほしい:
- 正しいメールアドレスとパスワードでログイン成功
- 間違ったパスワードでエラーが返る
- 存在しないメールアドレスでエラーが返る
- メールアドレスが空のときバリデーションエラーになる
使用フレームワーク:Vitest + Testing Library"
既存テストを追加・改善してもらう
# テストケースの追加
"src/utils/tax.test.ts を読んで、
テストが不足しているケースを追加して"
# 境界値テストの追加
"この関数の境界値テスト(0・最大値・null・undefined)を追加して"
# テストの可読性向上
"src/auth/login.test.ts のテストコードをリファクタリングして。
beforeEach で共通の準備処理をまとめて、テスト名をより明確にして"
カバレッジレポートを元に依頼する
# カバレッジ確認
npm run test -- --coverage
# カバレッジが低いファイルのテストを書いてもらう
"coverage/lcov-report を確認すると、
src/lib/formatter.ts のカバレッジが40%しかありません。
不足しているテストケースを追加してください"
Claude Code が生成したテストコードは必ず実際に実行して確認しましょう。テストが通るか・テストが正しく意図を確認しているかを目で確かめることが重要です。生成されたテストをそのままコミットせず、一度読んで理解してからコミットする習慣をつけましょう。
7. Claude Code でコードレビューをする方法
コードレビューとは「書いたコードを他の人に確認してもらうプロセス」です。バグの早期発見・コード品質の維持・知識の共有が目的です。Claude Code はこのレビュアーとしての役割も担えます。
コード全体のレビュー依頼
# 基本のレビュー依頼
"src/auth/ ディレクトリのコードをレビューして"
# 観点を指定したレビュー
"src/auth/login.ts をレビューして。
特に以下を重点的に確認してほしい:
1. セキュリティ上の問題(SQLインジェクション・XSS・認証の抜け穴)
2. エラーハンドリングの漏れ
3. パフォーマンスの問題
4. テストが書きにくい構造になっていないか"
PR 前のセルフレビュー
# 変更差分を確認してからレビュー依頼
"git diff origin/main...HEAD の変更内容を確認して、
プルリクエストを出す前に問題がないかレビューして。
特にバグ・セキュリティ・コードスタイルの観点で見てほしい"
# チェックリスト形式でレビューしてもらう
"以下の観点でこのコードをレビューして、
問題があれば箇条書きで指摘してほしい:
□ 変数名・関数名は意図を正確に表しているか
□ 同じコードが繰り返されていないか(DRY原則)
□ 関数が1つの責務に集中しているか
□ 適切なエラーハンドリングがされているか
□ コメントは不要な部分・不足している部分はないか"
セキュリティレビューを依頼する
"src/api/users.ts をセキュリティの観点でレビューして。
以下の脆弱性がないかチェックしてほしい:
- SQLインジェクション
- XSS(クロスサイトスクリプティング)
- 認証・認可の抜け穴
- 機密情報の露出(パスワード・APIキーのログ出力など)
- レート制限の欠如"
改善提案を求める
# リファクタリング提案
"このコードをリファクタリングする場合、
どんな改善ができるか提案して。
ただし動作は変えないこと"
# パフォーマンス改善
"このコードのパフォーマンスを改善できる箇所があれば指摘して。
特にN+1クエリ問題・不要な再レンダリングに注目してほしい"
レビュー結果を活用する
- 指摘された問題を修正してから再レビュー依頼:「修正しました。もう一度確認して」と繰り返すことで品質が上がる
- なぜ問題なのかを聞く:「なぜこの書き方が問題なのか説明して」と聞くと学習になる
- レビュー結果を CLAUDE.md に反映:繰り返し指摘される問題はチームの規約として CLAUDE.md に追加する
- 人間のレビューと組み合わせる:Claude のレビューを通過してから人間のレビュアーに出すと、指摘が減って効率的
8. テストを「書き続ける」習慣の作り方
最初から完璧を目指さない
テストを書き始めた最初は、すべてのコードにテストを書こうとしなくて大丈夫です。「新しく書いた関数には必ずテストを書く」というルールから始めましょう。既存のコードへのテスト追加は、そのコードを修正するタイミングに合わせて少しずつ行います。
TDD(テスト駆動開発)を試してみる
TDD とは「テストを先に書いてから実装する」開発手法です。最初は慣れないかもしれませんが、「コードが正しく動くとはどういうことか」を先に考えることで、設計が自然とよくなる効果があります。
# TDD のサイクル
# 1. 失敗するテストを書く(まだ実装がないので当然失敗)
it('ユーザー名が空のときバリデーションエラーになる', () => {
expect(validateUser({ name: '' })).toEqual({ valid: false, error: '名前を入力してください' });
});
# 2. テストが通る最低限の実装を書く
function validateUser(user) {
if (!user.name) return { valid: false, error: '名前を入力してください' };
return { valid: true };
}
# 3. リファクタリングしてコードをきれいにする
# → テストが通り続けることを確認しながら整理する
CI で自動実行する仕組みを作る
GitHub Actions などの CI(継続的インテグレーション)を設定すると、push のたびに自動でテストが実行されます。テストが失敗した PR はマージできない設定にしておくと、チーム全体のコード品質が自然に保たれます。
# .github/workflows/test.yml の基本形
name: Test
on:
push:
branches: [main]
pull_request:
jobs:
test:
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v4
- uses: actions/setup-node@v4
with:
node-version: '20'
- run: npm ci
- run: npm run test
- run: npm run lint
まとめ
まとめ
- テストコードとは:自分が書いたコードが正しく動くかを自動で確認するコード。「準備 → 実行 → 確認」の3ステップで構成される
- なぜ必要か:デグレ防止・仕様書としての役割・自信を持ったデプロイ・リファクタリングの安全網。後から直すコストの方がはるかに大きい
- まず書くべきはユニットテスト:1つの関数が正しく動くかを確認する最小単位。高速で書きやすい
- 気をつけること:1テスト1確認・テスト名を明確に・境界値と異常系を忘れない・カバレッジ100%を目指さない(70〜80%が目安)
- Claude Code でテスト生成:「このファイルのテストを書いて」と依頼するだけ。観点を具体的に指定すると精度が上がる。生成されたテストは必ず実行して確認する
- Claude Code でコードレビュー:セキュリティ・エラーハンドリング・パフォーマンスなど観点を指定してレビュー依頼。人間のレビュー前に通しておくと効率的
- 習慣化のコツ:最初は「新しく書いた関数には必ずテストを書く」だけでよい。CI で自動実行する仕組みを作るとチーム全体に広がる
テストコードは「書かなくても動く」ものです。でも、書かないと必ずどこかで大きなツケが回ってきます。まずは小さな関数1つにテストを1つ書くところから始めてみてください。最初の1つが書けるようになれば、あとは自然とテストを書く習慣が身についていきます。
Claude Code はテストコードの生成・コードレビューの両方で強力な助けになります。うまく活用して、安心して開発できる環境を作っていきましょう。
関連記事もあわせてどうぞ
Claude Codeでチーム開発を始める完全ガイド・CLAUDE.mdの書き方・Claude Code Hooks実践
