テストの考え方

この講座では、ソフトウェア開発におけるテストの考え方について学びます。

  • テストがソフトウェア開発で重要な理由
  • テストの種類(ユニットテスト、統合テスト、E2Eテストなど)
  • 静的解析とフォーマッタの役割

1. なぜテストが必要なのか

1.1 システム開発におけるテストとは

システムにおけるテストとは、システムが要件や設計どおりに動作しているかを確認するための工程です。

テストを厳密に行うには、まずテストの計画を立て、その計画に沿ってテストケースを作成します。そして実際にテストを実行し、結果をレビューすることで品質を高めていきます。このように、テストは単にコードを動かして確認するだけでなく、体系的に品質を保証するための一連のプロセスです。

1.2 テストの自動化

システムやソフトウェア開発では、コードの変更を頻繁に、素早く、安全に本番環境へ届けることが求められます。手動でのテストだけに頼っていると、リリースのたびに多くの時間と人手が必要になり、デプロイの頻度を上げることが困難になります。

こうした課題を解決するのが、テストの自動化です。

自動テストを導入すると、コードを変更した際に、既存の機能が正しく動作しているかを素早く確認できます。手動で一つひとつ確認する必要がなくなるため、開発のスピードを落とさずに品質を維持できます。

また、CI/CDパイプラインの中にテストを組み込むことで、コードがリポジトリにプッシュされるたびに自動でテストが実行されます。これにより、問題のあるコードが本番環境に反映されることを防ぎ、品質を担保できます。

📝 CI/CDパイプラインとは
CI/CDとは、CI(継続的インテグレーション)とCD(継続的デリバリ) の略称です。コードの変更を自動でビルド・テスト・デプロイする仕組みのことを指します。「パイプライン」とは、これらの処理が順番に流れていく一連の自動化された工程のことです。CI/CDについては別の講座で詳しく学びますので、ここでは「テストを自動実行してくれる仕組みがある」という点だけ押さえておきましょう。

さらに、自動テストが整備されていると、デプロイに対する信頼性が向上します。「テストが通っているから安心してリリースできる」という状態が作れるため、リリースの頻度を高めることができます。

加えて、バグを開発の早い段階で発見できるようになります。バグは発見が遅れるほど修正のコストが大きくなるため、早期に検出できることは大きなメリットです。

📝 開発とテストの関係
継続的な開発では「壊れたらすぐ分かる」「壊れたらすぐ直せる」という状態を目指します。自動テストはその基盤となる仕組みです。

2. テストの種類

ソフトウェアテストにはさまざまな種類があります。テストを理解するうえで重要なのは、テストタイプテストの工程機能テストと非機能テストという3つの視点で整理することです。

2.1 テストタイプ

テストには、目的や手法に応じたさまざまなタイプがあります。ここでは、代表的なテストタイプを紹介します。

ユニットテスト

ユニットテストは、関数やメソッドなどの最小単位を対象として、自動化されたテストコードで検証するテストタイプです。開発者がコードを変更するたびに素早く実行でき、問題の早期発見に役立ちます。

具体的には、ある関数に特定の入力を与えたとき期待する出力が返るかを確認したり、エラーが発生すべき入力に対して適切に例外が発生するかを確認したりします。Pythonではpytestや標準ライブラリのunittestがよく使われます。

たとえば、以下のようなテストコードを書いて、結果がOKかNGかを自動で判定します。

def add(a, b):
    return a + b

# ユニットテストの例
def test_add():
    assert add(1, 2) == 3
    assert add(-1, 1) == 0

このコードでは、add関数に12を渡したとき3が返ることをassertで検証しています。テストコードの具体的な書き方や実行方法については、Pythonで自動テストをしようの講座で詳しく解説します。

E2Eテスト

E2Eテスト(エンドツーエンドテスト)は、ユーザの操作をシミュレーションし、システム全体が期待どおりに動作するかを確認するテストタイプです。たとえば、ログインしてからデータを登録し、検索結果に表示されることを確認するといった、一連の操作フローを通してテストします。

E2Eテストの自動化には、対象のシステムに応じたツールを使用します。たとえばWebアプリケーションの場合は、ブラウザを自動操作できるSeleniumPlaywrightCypressなどのツールがよく使われます。これらを使うことで、手動で行っていたブラウザ操作をコードで記述し、繰り返し自動実行できるようになります。APIを対象とする場合は、HTTPリクエストを自動送信してレスポンスを検証するといった方法でE2Eテストを行います。

性能テスト

性能テスト(パフォーマンステスト)は、システムが高負荷の状況でも正常に動作するかを確認するテストタイプです。レスポンスタイムや同時接続数の上限などを測定し、システムが求められる性能要件を満たしているかを検証します。

大量のリクエストを同時に送信してシステムの限界を確認する負荷テストや、長時間にわたって安定して動作するかを確認する耐久テストなども、性能テストの一種です。自動化ツールとしては、k6JMeterLocustなどが広く使われています。これらのツールを使うことで、数千〜数万の同時リクエストをシミュレーションし、システムのボトルネックを特定できます。

セキュリティテスト

セキュリティテストは、アプリケーションに脆弱性がないかを検証するテストタイプです。SQLインジェクションやクロスサイトスクリプティング(XSS)などの攻撃に対する耐性を確認します。

セキュリティテストの自動化には、OWASP ZAPSnykTrivyなどのツールが使われます。近年では、CI/CDパイプラインの中にこれらのセキュリティスキャンを組み込み、開発プロセスの早い段階で脆弱性を検出する手法が広まっています。

回帰テスト

回帰テスト(リグレッションテスト)は、コードの変更や機能の追加によって、既存の機能が壊れていないかを確認するテストタイプです。新しい機能を追加したときや、バグを修正したときに、その変更が他の部分に悪影響を与えていないかを検証します。

回帰テストは、既存のユニットテストやE2Eテストをそのまま再実行することで実現できます。自動テストとの相性が非常に良く、CI/CDパイプラインで変更のたびに自動実行することで、デグレード(品質の後退)を防ぐことができます。

テストタイプ 目的 代表的なツール
ユニットテスト 関数やメソッド単位の動作検証 pytest、unittest
E2Eテスト ユーザ操作のシミュレーションによるシステム全体の検証 Selenium、Playwright、Cypress
性能テスト 高負荷時のレスポンスや安定性の検証 k6、JMeter、Locust
セキュリティテスト 脆弱性の検出と耐性の検証 OWASP ZAP、Snyk、Trivy
回帰テスト コード変更による既存機能への影響確認 既存のテストスイートを再実行

2.2 テストの工程

テストの分け方についてもう一つ、テスト工程によって分ける方法があります。

システム開発は基本的に要件定義 → 基本設計 → 詳細設計 → 開発(コード作成) の順で進みます。テスト工程は、この開発の流れと対応する形で定義されています。開発工程で決めた内容を、対応するテスト工程で検証するという関係です。

単体テスト

単体テストは、プログラムの最小単位である関数やメソッドを個別にテストする工程です。前述のテストタイプで紹介した「ユニットテスト」と同じレベルのテストを指しますが、テスト工程としては「単体テスト」と呼ぶのが一般的です。

詳細設計で定義した各モジュールの仕様どおりにコードが動作するかを確認します。開発者が自分の書いたコードが正しく動作するかを検証するために行い、外部のシステムやデータベースには依存せず、純粋にロジックの正しさを確認します。

結合テスト

結合テストは、複数のモジュールやコンポーネントを組み合わせて、それらが正しく連携して動作するかを確認する工程です。基本設計で定義したモジュール間のインターフェースや、データの流れが正しく機能するかを検証します。

たとえば、APIとデータベースを接続した状態でデータの読み書きが正しく行われるか、複数のサービス間でデータの受け渡しが正しく行われるかなどをテストします。

総合テスト

総合テスト(システムテスト)は、システム全体を対象として、要件定義で定めた要件どおりに動作するかを確認する工程です。結合テストまでは部品や連携の単位でテストしていましたが、総合テストではシステム全体を一つのまとまりとして検証します。

本番に近い環境で、実際のユーザの操作を想定したシナリオに沿ってテストを行います。

受け入れテスト

受け入れテストは、開発したシステムが発注者や利用者の要件を満たしているかを最終確認する工程です。開発チームではなく、発注者側やエンドユーザが主体となって実施することが多いです。

この工程を通過することで、システムが「納品可能な状態である」と判断されます。

テスト工程 対応する開発工程 検証する内容 主な実施者
単体テスト 詳細設計 各モジュールが仕様どおりに動作するか 開発者
結合テスト 基本設計 モジュール間の連携が正しく機能するか 開発者・テスト担当者
総合テスト 要件定義 システム全体が要件を満たしているか テスト担当者
受け入れテスト 要件定義 発注者・利用者の期待を満たしているか 発注者・エンドユーザ

2.3 機能テストと非機能テスト

テストは、何を検証するかという観点から、大きく機能テスト非機能テストの2つに分類できます。

機能テスト

機能テストは、システムが仕様どおりの機能を提供しているかを確認するテストです。「ボタンを押したら画面が遷移する」「フォームに入力したデータが正しく保存される」といった、ユーザから見える振る舞いが正しいかどうかを検証します。

ユニットテスト、結合テスト、E2Eテストなどは、いずれも機能テストの側面を持っています。

非機能テスト

非機能テストは、機能以外の品質特性を検証するテストです。システムが「正しく動く」だけでなく、「快適に使える」「安全である」「安定している」といった品質を確認します。

性能テストやセキュリティテストは、非機能テストの代表例です。そのほかにも、システムの使いやすさを評価するユーザビリティテストや、障害発生時の復旧能力を確認する信頼性テストなども非機能テストに含まれます。

分類 検証する内容 該当するテストタイプの例
機能テスト 仕様どおりの機能が動作するか ユニットテスト、結合テスト、E2Eテスト、回帰テスト
非機能テスト 性能・セキュリティ・使いやすさなどの品質 性能テスト、セキュリティテスト、ユーザビリティテスト
💡 ポイント
テストタイプ(どのようにテストするか)、テストの工程(いつテストするか)、機能・非機能(何を検証するか)は、それぞれ異なる切り口です。たとえば、結合テストの工程で性能テスト(非機能テスト)を実施するといったように、これらは組み合わせて考えます。

3. 静的解析とフォーマッタ

テスト以外にも、コードの品質を保つための仕組みがあります。その代表的なものが静的解析フォーマッタです。

3.1 静的解析

静的解析とは、プログラムを実行せずにソースコードを解析し、潜在的な問題やコードスタイルの違反を検出する手法です。英語ではStatic AnalysisLinting(リンティング)と呼ばれます。

テストがプログラムを「実行して」動作を検証するのに対し、静的解析はコードを「実行せずに」構造や書き方を検証します。たとえば、未使用の変数がないか、命名規則に従っているか、型の不整合がないかといった点をチェックできます。

静的解析を導入することで、バグの早期発見やコードレビューの負担軽減につながります。スタイルに関する指摘をツールに任せることで、レビューでは設計やロジックに集中できるようになります。

Pythonでは、コードスタイルをチェックするFlake8や、型の不整合を検出するmypyが代表的な静的解析ツールです。

3.2 フォーマッタ

フォーマッタは、コードのインデントや空白、改行などの書式を自動的に統一するツールです。静的解析がルール違反を「検出」するのに対し、フォーマッタはルールに従って「自動修正」する点が異なります。

フォーマッタを使うことで、チーム内でコードの見た目が統一され、差分の読みやすさが向上します。開発者が書式を気にする必要がなくなるため、コードの内容に集中できるというメリットもあります。

Pythonでは、Blackが広く使われているフォーマッタです。Blackは設定項目が少なく、チーム内でスタイルの議論をなくすことを目的に設計されています。

種類 役割 Pythonの代表的なツール
静的解析(リンタ) コードスタイルの違反や潜在的な問題を検出する Flake8、Ruff
型チェック 型の不整合を検出する mypy
フォーマッタ コードの書式を自動的に統一する Black、Ruff
💡 ポイント
静的解析とフォーマッタは、テストと組み合わせて使うことでコードの品質をより高く保つことができます。具体的なツールの使い方は、次の講座で実際に手を動かしながら学びます。

4. まとめ

この講座では、ソフトウェア開発におけるテストの考え方について学びました。

  • ソフトウェア開発では自動テストが不可欠であり、デプロイの信頼性と頻度を高めるための基盤となる
  • テストにはユニットテストE2Eテスト性能テストセキュリティテスト回帰テストなどのタイプがある
  • テスト工程は開発工程と対応しており、単体テスト → 結合テスト → 総合テスト → 受け入れテストの順に進む
  • テストは機能テスト非機能テストの2つの観点でも分類できる
  • 静的解析はコードを実行せずに問題を検出し、フォーマッタはコードの書式を自動で統一する