【Python/TypeScript】意味のあるテストの書き方
python 16 min read

【Python/TypeScript】意味のあるテストの書き方

avatar-m-1

karrinn

著者

KEY TAKEAWAYS

この記事でわかること

  • テストの本質的な考え方:何を保証すべきか
  • 良いテスト・悪いテストの具体例(Python/TypeScript)
  • スナップショットテスト・ゴールデンデータの活用法
  • カバレッジ指標の正しい使い方と限界
  • 実務で使えるテスト戦略とツール選び

"テストとは、関数に何かを与えて期待した何かが返ってくることを永続的に保障していくもの。そして、ある修正に対する影響範囲を検知するもの。"

テストって結局何のために書くんでしょうか?カバレッジを上げるため?上司に言われたから?いいえ、違います。

テストの本質は「保証」「検知」です。この関数は正しい入力に対して正しい出力を返す、というシンプルな約束を守り続けること。そして、誰かがコードを変更したときに、その変更が意図しない場所に影響を与えていないかを検知すること。この2つがテストの核心です。

参考: Martin Fowler - Testing Guide

まず押さえておきたい3つの用語

TERM 01

ユニットテスト

関数やメソッド単位の小さなテスト。1つの機能が正しく動くかを検証します。高速で実行でき、バグの原因を特定しやすいのが特徴です。

TERM 02

統合テスト

複数のモジュールやコンポーネントを組み合わせたテスト。API呼び出しやデータベースとの連携など、実際の動作に近い環境でテストします。

TERM 03

スナップショットテスト

ある時点の「正しい状態」を保存し、その後の実行結果と比較するテスト。UIコンポーネントやAPIレスポンスの変更を検知するのに効果的です。

参考: pytest - Good Integration Practices

具体例で学ぶ:良いテスト vs 悪いテスト(Python編)

実際のコードで見てみましょう。Pythonとpytestを使った例です。

悪いテスト例

意味がない、または保守コストが高いテスト

bad_test.py
def test_always_passes(self):
"""常にパスするテスト"""
assert True  # 何も検証していない


def test_implementation_detail(self):
"""実装の詳細をテスト"""
todo = Todo.objects.create(title="Test")
# Djangoの内部実装に依存
assert todo._state.db == "default"
assert hasattr(todo, "_meta")


def test_django_framework_behavior(self):
"""フレームワークの動作をテスト"""
todo1 = Todo.objects.create(title="First")
todo2 = Todo.objects.create(title="Second")
# Djangoが既に保証している機能
assert todo2.id > todo1.id

良いテスト例

目的が明確で、ビジネスロジックを検証するテスト

good_test.py
def test_title_boundary_max_length(self):
"""境界値テスト: 最大200文字"""
max_title = "a" * 200
todo = Todo.objects.create(title=max_title)
assert len(todo.title) == 200


def test_title_boundary_exceeds_max_length(self):
"""境界値テスト: 201文字はエラー"""
todo = Todo.objects.create(title="a" * 201)
# SQLiteでは保存されるがDB依存の動作
assert len(todo.title) == 201


def test_is_overdue_with_past_date(self):
"""期限切れ判定ロジックのテスト"""
todo = Todo.objects.create(
title="Overdue task",
due_date=date.today() - timedelta(days=1),
)
assert todo.is_overdue() is True

コード例: test_models.py

フロントエンドも同じ:良いテスト vs 悪いテスト(TypeScript編)

React + TypeScript + Vitestでの例です。考え方はバックエンドと変わりません。

悪いテスト例

実装の詳細やクラス名に依存したテスト

bad_test.tsx
it("always passes", () => {
expect(true).toBe(true);
// 何も検証していない
});

it("checks component instance", () => {
const { container } = render(
<TodoForm onSubmit={vi.fn()} />
);
// 実装の詳細(CSSクラス)に依存
expect(container.querySelector(
".todo-form"
)).toBeTruthy();
});

it("has correct form class name", () => {
const { container } = render(
<TodoForm onSubmit={vi.fn()} />
);
// CSSクラス名のテスト(優先度低)
expect(container.querySelector("form"))
.toHaveClass("todo-form");
});

良いテスト例

ユーザーの視点でUIの動作を検証

good_test.tsx
it("submits form with valid data", async () => {
const onSubmit = vi.fn();
const user = userEvent.setup();

render(<TodoForm onSubmit={onSubmit} />);

await user.type(
screen.getByLabelText(/タイトル/i),
"New TODO"
);
await user.selectOptions(
screen.getByLabelText(/優先度/i),
"high"
);
await user.click(
screen.getByRole("button", {name: /作成/i})
);

expect(onSubmit).toHaveBeenCalledWith({
title: "New TODO",
priority: "high",
// ...
});
});

コード例: TodoForm.test.tsx | 参考: Vitest Official

実務で使えるテスト手法の比較

テスト手法どんな時に使う?具体例
境界値テスト入力値の上限・下限付近をテスト200文字、201文字、0文字など
同値分割有効な値・無効な値のパターンを代表値でテスト優先度: low/medium/high、期限: 過去/今日/未来
ハッピーパス正常系の代表的な処理フローユーザー登録→ログイン→データ作成
スナップショットゴールデンデータとの一致を確認APIレスポンス、コンポーネントの描画結果

参考: Boundary Value Analysis and Equivalence Partitioning

私が最もおすすめする手法:スナップショットテスト

Pro Tip: ゴールデンデータを使ったスナップショットテスト

ある時点の「正しい状態」を保存しておき、その後の実行結果と比較する手法です。特にAPIレスポンスや複雑なデータ構造のテストで威力を発揮します。

スナップショットテストの何が良いかって?リグレッション(意図しない変更)の検知力が圧倒的なんです。

実装例(Python + Parquet形式)

snapshot_test.py
def test_all_todos_snapshot(self, api_client):
"""TODO一覧APIのスナップショットテスト"""
response = api_client.get("/api/todos/")
data = response.json()

# ゴールデンデータと比較
snapshot_comparator.assert_matches_snapshot(
data,
"todos_all",
exclude_fields=["id", "created_at", "updated_at"],
)
# IDや日時は実行ごとに変わるので除外

# ゴールデンデータはParquet形式で保存
# - 大量データでも効率的
# - 人間が読めるフォーマット
# - バージョン管理可能

コード例: test_todos.py | 参考: Effective Snapshot Testing - Kent C. Dodds

実務で使えるおすすめテストツール

pytest

Python

Pythonの定番テストフレームワーク。シンプルな記法、豊富なプラグイン、強力なfixtureシステムが特徴。Django、Flask、FastAPIなど主要フレームワークに対応。

Vitest

TypeScript

Viteベースの爆速テストフレームワーク。Jest互換のAPIで移行も簡単。TypeScript対応が優れており、ES Modulesネイティブサポート。React/Vueに最適。

Playwright

E2E

Microsoftが開発するE2Eテストフレームワーク。Chromium、Firefox、WebKitをサポート。自動待機機能が優秀で、フレイキーなテストを減らせる。

Django Test

Django

Django標準のテストフレームワーク。TestCaseクラスでDBトランザクション管理を自動化。Django特有の機能(ミドルウェア、ビュー、認証)のテストに特化。

参考: pytest | Vitest | Playwright | Django Testing

組織によって変わるテスト戦略

大企業とスタートアップでは、テストの書き方・考え方が大きく異なります。自分の環境に合ったアプローチを選びましょう。

大企業のアプローチ

要件定義〜詳細設計まできっちり作られている場合が多い

  • 設計書ベース:設計の各項目が満たされているかを確認
  • テスト仕様書:テストケースが事前に定義済み
  • トレーサビリティ:要件→設計→テストの紐付けが重要
  • 何を保証するか:仕様書に書かれている通りに動くこと

スタートアップのアプローチ

要件がふんわりしていることも多い

  • 自分で考える:何を保証すべきかを自分で決める
  • ビジネスロジック優先:重要な機能から書く
  • 変更に強く:仕様変更を想定したテスト設計
  • 何を保証するか:ユーザーにとって重要な動作

筆者の経験から:大企業の場合、正直に言うと設計書があれば「それが全て」です。設計書の項目を網羅的にテストすればOK。一方、スタートアップでは「いい感じにしといて」と言われることも多いので、さっき説明したテストの本質(保証と検知)に立ち返って考えるのが役に立ちます。

カバレッジ指標:固執すべきでないが、無視していいものでもない

「カバレッジ100%目指せ!」とか「カバレッジなんて意味ない」とか、極端な意見を聞くこと、ありませんか?実は、どちらも正しくありません

カバレッジのメリット

  • テストされていない箇所を可視化できる
  • チームで目標を共有しやすい(例: 80%以上)
  • 重要なロジックの漏れを防げる

カバレッジのデメリット

  • 高いカバレッジ = 良いテストではない
  • 数値目標が目的化すると意味のないテストが増える
  • テストの質は測れない

筆者の結論:目安として使おう

バックエンドは90%以上、フロントエンドは80%以上を目安にしつつ、getter/setterや設定値の確認など優先度の低いテストは除外してOK。重要なのは「ビジネスロジックがしっかりテストされているか」です。2024年の調査では、テスト自動化を導入している企業の76%がカバレッジ指標を何らかの形で活用しています。

参考: Google Testing Blog - Code Coverage Best Practices | Productive Coverage (ICSE 2024)

データで見るテストの現状

プロジェクトで使用されるテストタイプ(2024)

Source: JetBrains Developer Survey 2024

テスト自動化の割合

Source: Global Software Testing Market 2024

データ: JetBrains Developer Ecosystem 2024 | Software Testing Statistics 2024

Column: テスト駆動開発の歴史

ソフトウェアテストの歴史は意外と浅く、体系的な手法が確立されたのは2000年代に入ってからです。Kent Beckが1999年に発表した「Extreme Programming」の中でテスト駆動開発(TDD)を提唱し、それが現代のテストプラクティスの基礎となりました。

2009年にはMartin Fowlerが「Test Pyramid」の概念を広め、ユニットテスト・統合テスト・E2Eテストのバランスについての指針を示しました。この考え方は今でも多くの開発チームで採用されています。

興味深いのは、スナップショットテストが主流になったのはここ5年ほどのこと。Jestが2016年にスナップショットテスト機能を導入し、それ以降フロントエンド開発で広く使われるようになりました。技術の進化とともに、テストの手法も日々アップデートされているんです。

参考: Test Pyramid - Martin Fowler

サンプルコードリポジトリ

Python/TypeScriptの実践的なテストコード例(良い例・悪い例・優先度の低い例)

GitHubで見る

よくある質問 (FAQ)

よく聞く悩みですが、「テストを書かない時間」の方が後で高くつきます。バグが本番で見つかったときの修正コストは、開発時の10〜100倍と言われています。まずは重要なビジネスロジックだけでもテストを書くことをおすすめします。

確かに既存コードへのテスト追加は大変です。でも、全部を一度にやる必要はありません。修正するファイルから順にテストを追加していく「ボーイスカウトルール」がおすすめ。少しずつでも、着実にテストカバレッジは上がっていきます。

「自分が所有していないコードだけをモックする」が基本原則です。外部API、データベース、サードパーティライブラリはモックしますが、自分のコードは実際に動かしてテストする方が効果的です。

必須ではありませんが、重要なユーザーフローには書くべきです。ログイン、決済、データ登録など、絶対に壊れてはいけない機能に絞ってE2Eテストを書きましょう。全画面をE2Eでテストする必要はありません。

テスト実装チェックリスト

  • テストフレームワークのセットアップ完了
  • 境界値テストの実装
  • 同値分割テストの実装
  • ハッピーパスの確認
  • 異常系・エラーハンドリングのテスト
  • スナップショットテストの導入
  • CI/CDへのテスト組み込み
  • カバレッジ指標の確認

もっと詳しく知りたい人へ(参考文献)

関連トピック

コメント (0)

まだコメントはありません。最初のコメントを残しませんか?

コメントを投稿

メールアドレスが公開されることはありません。必須項目には * が付いています