Pythonのテストフレームワーク
テストはソフトウェア開発プロセスにおいては必要不可欠な要素ですよね。
Pythonでの開発においても、多くのテストフレームワークが利用されています。
以下にPythonテストフレームワークとその特徴について上げました。
| Pythonテストフレームワーク | 特徴 |
|---|---|
| unittest | ・Pythonの標準ライブラリの一部で、JavaのJUnitを模倣したもの ・テストケースはクラスベースで作成され、継承とセットアップ/ティアダウンメソッドを利用します |
| nose2 | ・unittestの拡張版として開発され、より使いやすくなっている ・プラグインアーキテクチャを備えており、カスタマイズが容易 https://docs.nose2.io/en/latest/ |
| doctest | ・docstring内にテストケースを書くことができ、ドキュメントとしても機能する https://docs.python.org/ja/3/library/doctest.html |
| Pytest | ・シンプルでありながら強力な機能を持つ、非常に人気のテストフレームワークです。 ・クラスベースでなく、関数ベースのテストをサポートしており、テストの書き方が直感的です。 |
Pytestのメリット
次にPytestの特徴的なメリットについて記載します。
シンプルな構文
Pytestはテスト関数をシンプルに書くことができます。
テストケースをクラスにまとめる必要あはなく、Pythonの関数として定義するというのが特徴です。
# test_example.py
def test_addition():
assert 1 + 1 == 2
def test_subtraction():
assert 5 - 3 == 2
強力なアサーション
Pytestのアサーションは非常に読みやすく詳細です。エラーメッセージも詳細で、デバッグが容易です。
# test_assertion.py
def test_list():
a = [1, 2, 3]
b = [1, 2, 3, 4]
assert a == b
プラグインのサポート
Pytestでは、プラグインを使用して機能を拡張できます。例として、pytest-xdistを使用して並列テストを実行します。
公式サイトにおいては800以上のプラグインがあるとされています。
pip install pytest-xdist pytest -n 4
フィクスチャ
Pytestのフィクスチャを使用して、テストデータや設定を簡単に共有できます。
# test_fixture.py
import pytest
@pytest.fixture
def sample_data():
return {"key": "value"}
def test_example_1(sample_data):
assert "key" in sample_data
def test_example_2(sample_data):
assert sample_data["key"] == "value"
パラメータ化されたテスト
同じテストを異なるパラメータで簡単に実行できます。
# test_parameterized.py
import pytest
@pytest.mark.parametrize("a, b, expected", [(1, 2, 3), (2, 3, 5), (3, 5, 8)])
def test_addition(a, b, expected):
assert a + b == expected
このようなテストを効率的に開発できる機能によってPytestは人気がある理由の一つだと思います。
テストコードを記載する観点について
Pytestでカバーする範囲としては単体テスト(UTともいう)の範囲になります。
単体テストの目的
単体テストの目的は、個々のユニット(関数、メソッド、クラス)が正しく動作することを確認することです。
現在Fast APIを利用したAPIの開発を行っています。
そのAPIの単体テストに焦点を当ててどのような観点があるかというと、
エンドポイントが期待通りのレスポンスを返すか、及び適切なサイドエフェクト(データベースの更新など)を持つか確認します。
APIの単体テストの観点
主なAPIテスト観点をチームとしてまとめておくことが必要です。
そうしなければ、それぞれが独自の視点でテストを行うことになってしまいテスト品質の統一がとれなくなってしまう=アプリケーションの品質低下につながるためです。
- ステータスコード
- APIエンドポイントが正しいHTTPステータスコードを返しているか確認します。
- レスポンスの内容
- 返されるデータが期待通りであるか確認します。
- エラーハンドリング
- 不正なパラメータやリクエストで適切なエラーメッセージが返されるか確認します。
- セキュリティ
- 認証や認可が適切に機能しているか確認します。
- サイドエフェクト
- API呼び出しにより、データベース等の状態が期待通りに変更されるか確認します。
Pytestを使って上記観点を取り入れたイメージのコードを記載しました。
import pytest
import requests
# テスト対象のAPIのベースURL
BASE_URL = "http://example.com/api"
# GET メソッドのテスト
def test_get_method():
# API呼び出し
response = requests.get(f"{BASE_URL}/items/1")
# ステータスコードの確認
assert response.status_code == 200
# レスポンスの内容確認
assert response.json()["name"] == "Item1"
# POST メソッドのテスト
def test_post_method():
# POSTで送信するデータ
payload = {"name": "NewItem"}
# API呼び出し
response = requests.post(f"{BASE_URL}/items", json=payload)
# ステータスコードの確認
assert response.status_code == 201
# データベースにアイテムが追加されたか確認
new_item = requests.get(f"{BASE_URL}/items/{response.json()['id']}")
assert new_item.json()["name"] == "NewItem"
# PUT メソッドのテスト
def test_put_method():
# PUTで送信するデータ
payload = {"name": "UpdatedItem"}
# API呼び出し
response = requests.put(f"{BASE_URL}/items/1", json=payload)
# ステータスコードの確認
assert response.status_code == 200
# データベースのアイテムが更新されたか確認
updated_item = requests.get(f"{BASE_URL}/items/1")
assert updated_item.json()["name"] == "UpdatedItem"
# DELETE メソッドのテスト
def test_delete_method():
# API呼び出し
response = requests.delete(f"{BASE_URL}/items/1")
# ステータスコードの確認
assert response.status_code == 204
# データベースからアイテムが削除されたか確認
deleted_item = requests.get(f"{BASE_URL}/items/1")
assert deleted_item.status_code == 404
各テスト観点における詳細な実施観点は以下の項目が考えられます。
テスト実施ケース作成時に取り込みを行うかチームの基準を設ける必要があるでしょう。
プロジェクトの特性、リソース、スケジュールを加味してどの機能、網羅性についてテスト計画時にテスト要件として盛り込むと、いざテスト実施時に人によってばらばらにならず、統一された品質で作りこむことができます。
APIの単体テストの観点の詳細
ステータスコードのチェック
ステータスコードはHTTPプロトコルにおいて、サーバーがクライアントにレスポンスの状態や結果を通知するために使用される3桁の数字です。これらのコードは、特定の範囲ごとに分類され、それぞれ異なる意味を持ちます。
| ステータスコード | 概要 | 代表的な値と意味 |
|---|---|---|
| 1xx (情報) | リクエストは受け取られており処理中 | |
| 2xx (成功) | リクエストは正常に受け取られた状態 | 200 OK: リクエストが成功した状態201 Created: リクエストが成功し新しいリソースを作成した状態例)POSTリクエストを使ってデータベースに新しいレコードを作成したときなど |
| 3xx (リダイレクション) | さらなるアクションが必要で、通常、ブラウザはこれらのアクションを自動的に行う | 301 Moved Permanently: リソースが恒久的に新しいURLに移動しています。→WebページやAPIエンドポイントのURLが変更され、今後は新しいURLでアクセスされるべきであることをクライアントに通知するために使用されます。 例)ドメインの変更、URL構造の変更、 httpからhttpsにリダイレクト |
| 4xx (クライアントエラー) | クライアント側に何らかのエラーが存在してる状態 | 400 Bad Request: サーバーがリクエストの構文を理解できません。403 Forbidden: サーバーはリクエストを拒否しています。404 Not Found: サーバーがリクエストされたリソースを見つけられません。 |
| 5xx (サーバーエラー) | サーバー側に何らかの問題が発生した状態 | 500 Internal Server Error: サーバーに内部エラーが発生し、リクエストを処理できません。 |
APIやWebアプリケーションのテスト時には、適切なステータスコードが返されることを確認することが重要です。これは、システムが正しく動作しているか、または予期されたエラーを適切に処理しているかを確認するためです。
レスポンスの内容
レスポンスの内容の検証は、APIテストやWebアプリケーションのテストにおいて非常に重要です。
想定するリクエストから返却される内容が間違った状態で異常が検知できないと、不正なデータの状態でシステムが稼働してしまう状態となるためです。
以下はレスポンスの内容を検証する際の主な観点です。
| 観点 | 説明 |
|---|---|
| データの正確性 | レスポンスで返されるデータの整合性チェック。 例えば、ユーザー情報を要求した場合、名前、メールアドレスなどの情報が期待通りかどうかをチェックします。 |
| データ形式 | レスポンスデータの形式チェック。 JSON、XMLなどのデータ形式や、データの構造(キー名、階層など)に関連しています。 |
| データの完全性 | 必要なすべての情報がレスポンスに含まれているかチェック。 |
| 有効範囲の限界値 | 入力が特定の範囲内である必要がある場合、その範囲の上限および下限の値でチェック。 例えば、年齢が0から100の範囲であるとすると、0と100の値を使用してテストして正常性を確認します。 |
| 無効範囲の限界値 | 上記の範囲のすぐ外側の値(-1および101など)を使用して、システムがこれらの値を適切に拒否またはエラー処理するかどうかをテストします。 |
| 配列やリストの境界 | 配列やリストの要素数が上限に達しているか、または空である場合の挙動をテストします。 |
| テキストフィールドの長さ | テキスト入力フィールドに最小文字数や最大文字数の制限がある場合、これらの境界値でテストを行い、適切なエラーメッセージが表示されるかどうかを確認します。 |
| 日付と時間の境界 | 日付や時間の入力において、月の最初と最後の日、うるう年、時間の上限と下限(00:00, 23:59)など、特定の境界条件での動作をテストします。 |
エラーハンドリング
不正なパラメータやリクエストで適切なエラーメッセージが返されるか確認します。
エラーハンドリングは、ソフトウェアが予期しない状況や無効な入力に適切に対応する能力を確認するための重要なテスト要素となります。
以下は、エラーハンドリングを検証する際の観点です。
| 観点 | 説明 |
|---|---|
| 適切なエラーメッセージ | エラーが発生した場合、適切なエラーメッセージが表示されているかをチェック。 |
| エラーの種類に応じた処理 | 異なるエラーの種類(例:バリデーションエラー、システムエラー、ネットワークエラー)に対して、アプリケーションが適切な対応をするかどうかのチェック。 |
| 入力バリデーション | ユーザーからの入力が無効または不適切な場合に、アプリケーションが適切にバリデーションエラーを捕捉し、ユーザーにフィードバックを提供するかを検証します。 |
| ロギングと監視 | エラーが発生したときに、システムが適切にエラーログを記録し、必要に応じてアラートを生成するかどうかをチェック。 |
| リソースリークとクリーンアップ | エラーが発生したときに、アプリケーションがオープンされたリソース(ファイルハンドル、データベース接続など)を適切に解放し、システムが安定して動作し続けるかどうかをチェック。 |
セキュリティ
アプリケーションがセキュリティの脅威に対して適切に保護されていることを確認するために、セキュリティは非常に重要なテスト要素の一つです。
以下は、セキュリティを検証する際の主な観点です。
| 観点 | 説明 |
|---|---|
| 認証と認可 | ユーザーが適切に認証されているか(ログインしているか)、また認証されたユーザーが許可された操作のみを実行できるか(認可)を確認します。 |
| SQLインジェクションの防止 | アプリケーションがユーザー入力を適切にサニタイズし、SQLインジェクション攻撃に対して耐性を持っているかどうかを検証します。 |
| クロスサイトスクリプティング (XSS) の防止 | ユーザー入力が適切にエスケープされ、クライアントサイドのスクリプトが意図せず実行されないようにすることで、XSS攻撃に対する脆弱性を検証します。 |
| データ暗号化 | データが適切に暗号化されているか検証します。これには、トランスポート層のセキュリティ(たとえば、HTTPSを使用した通信)およびデータベース内の機密データの暗号化が含まれます。 |
| セキュリティヘッダーとCookieの設定 | セキュリティヘッダー(例:Content-Security-Policy)が適切に設定されているか、Cookieには適切なフラグ(HttpOnly、Secure)が設定されているかを検証します。 |
セキュリティは絶えず進化する分野であるため、常に最新のセキュリティプラクティスと脅威に注意を払うことも重要です。
サイドエフェクト
プログラムまたはシステム操作の結果として予期せずに発生する効果を指します。
これは特にAPIや複雑なシステムで重要です。
以下は、サイドエフェクトの検証観点の例です。
| 観点 | 説明 |
|---|---|
| データベースの整合性 | 操作後にデータベースの整合性が保たれていることを確認します。 たとえば、あるアクションがデータベースの異なる部分に予期せぬ変更をもたらしていないか、関連するエントリ間のリレーションシップが損なわれていないか確認します。 |
| 外部システムへの影響 | システムが外部サービスやAPIと連携している場合、操作がこれらの外部システムに予期せぬ影響を及ぼしていないか確認します。 |
| リソースの消費 | 操作が予期せぬリソース(メモリ、CPUなど)の消費を引き起こしていないか確認します。これには、リークや性能の低下が含まれる場合があります。 |
| 状態の変更 | 操作によってシステムの状態が予期せぬ方法で変更されていないか確認します。これには、グローバル変数、キャッシュ、または設定の変更が含まれる場合があります。 |

コメント