ユニットテストの高度な書き方
By JoeVu, at: 2023年2月22日19:39
Estimated Reading Time: __READING_TIME__ minutes


これはPythonによるユニットテストの書き方 - 初心者向けの次の章です
この章では、いくつかの重要なトピックについて説明します
- テスト検出
- セットアップとティアダウン
- モッキング
- カバレッジとレポート
- ヒント、ベストプラクティス、ガイドライン
- ユニットテストの原則と主要な概念
- 参考文献
一つずつ見ていきましょう
1. テスト検出
Pythonのunittest discoverは、ディレクトリとそのサブディレクトリ内のすべてのファイルをスキャンし、テストパターンに一致するファイルを探して動作します。テストパターンはユーザーによって定義され、プロジェクトに固有のテストを見つけるためにカスタマイズできます。テストパターンが識別されると、ツールはパターンに一致するすべてのテストを実行します。
Python unittest discoverを使用する利点は何ですか?
Python unittest discoverを使用すると、テストの検索と実行のプロセスを簡素化することで、開発者とテスターの時間と労力を節約できます
テストディレクトリが次のようになっていると仮定して、ここのサンプルコードを確認できます
garage/
cars/
honda.py
test_honda.py
libs/
datetimes.py
test_datetimes.py
コードをプルしてルートディレクトリに移動してテストを実行できます
cd garage
python -m unittest discover
デフォルトでは、現在のディレクトリ(garage
)内のすべてのtest*.py
ファイルを自動的に検出します。これは、次のようにして同じ効果を得ることができます。
python -m unittest # garageディレクトリ下のすべてのテストを実行
python -m unittest discovery libs # garage/libsディレクトリ下のすべてのテストを実行
python -m unittest discovery cars # garage/hondaディレクトリ下のすべてのテストを実行
python -m unittest cars/test_honda.py # cars/test_honda.pyテストファイルを実行
python -m unittest cars.test_honda # cars/test_honda.pyテストファイルを実行
2. セットアップとティアダウン
セットアップとティアダウンは、Python unittestの重要な機能であり、4つのメソッドがあります。
- setUp
- setUpClass
- tearDown
- tearDownClass
setUp()
とtearDown()
メソッドは、各テストメソッドの前後に実行されるコードを定義するために使用されます。一方、setUpClass()
とtearDownClass()
メソッドは、テストスイート全体の前後に実行されるコードを定義するために使用されます。
たとえば、ファイルを作成するメソッドの動作を検証するためのテストのセットを含むクラスがあるとします。setUp()
とtearDown()
を使用して、各テストの前後にファイルを作成および削除できます。コードは次のようになります。
import os
class TestFileUpload(unittest.TestCase):
def setUp(self):
self.file_name = "temporary.txt"
with open(self.file_name, "w") as fp:
pass
def tearDown(self):
os.remove(self.file_name)
この例では、setUp()
メソッドは各テストごとに新しいファイルを作成し、tearDown()
メソッドはテストの実行後にそれを削除します。
場合によっては、個々のテストごとにではなく、テストスイート全体に対してセットアップとティアダウン操作を実行する必要がある場合があります。たとえば、データベースに対してテストを実行する場合は、すべてのテストの実行前にデータベース接続を1回セットアップし、すべてのテストの完了後にティアダウンする必要があります。これは、次のようにsetUpClass()
とtearDownClass()
メソッドを使用して実行できます。
class TestDatabase(unittest.TestCase):
@classmethod
def setUpClass(cls):
cls.db_connection = DatabaseConnection("localhost", "test_db")
@classmethod
def tearDownClass(cls):
cls.db_connection.close()
この例では、setUpClass()
メソッドは新しいデータベース接続を作成し、tearDownClass()
メソッドはすべてのテストの完了後にそれを閉じます。
これは、Djangoアプリケーションを使用する場合に非常に役立ちます。setUpとsetUpClassはテストレコードの投入に使用され、tearDownとtearDownClassは各テストケース、各テストクラスのテストレコードのクリーンアップに使用されます。
3. モッキング
モックオブジェクトは、制御された方法で実際のオブジェクトの動作を模倣するシミュレートされたオブジェクトです。これらは、ユニットテストでテストが実行される環境を分離および制御し、実際のオブジェクトを作成する必要性を排除することでテストを簡素化するために使用されます。
unittestライブラリは、patch
、patch.object
、side_effect
メソッドを提供してモックオブジェクトを作成します。patch
メソッドは関数またはクラスをパッチするために使用され、patch.object
メソッドはクラスのインスタンスをパッチするために使用されます。side_effect
メソッドを使用すると、呼び出されたときの戻り値など、モックオブジェクトの動作を指定できます。
たとえば、facebook apiライブラリがあり、facebook apiを呼び出す関数があるとします。
ファイルfacebook_api.py
import requests
class FacebookAPI:
def get_userinfo(self, username):
response = requests.get("https://facebook.com/")
if response.status_code == 200:
return response.json()
return {}
ファイルuser.py
from libs.facebook_api import FacebookAPI
def is_registered_user(username):
user_info = FacebookAPI().get_userinfo(username)
return user_info != {}
ファイルtest_user.py
import unittest
from unittest import mock
from libs.facebook_api import FacebookAPI
from libs.user import is_registered_user
class TestDatetimes(unittest.TestCase):
@mock.patch.object(FacebookAPI, "get_userinfo")
def test_is_registered_user_return_true(self, mock_get_userinfo):
mock_get_userinfo.return_value = {"userid": 1}
result = is_registered_user("joe")
self.assertTrue(result)
@mock.patch.object(FacebookAPI, "get_userinfo")
def test_is_registered_user_return_false(self, mock_get_userinfo):
mock_get_userinfo.return_value = {}
result = is_registered_user("joe")
self.assertFalse(result)
FacebookAPIはFacebookのURLにリクエストを送信しますが、unittestでは実行したくないため、mockを使用します。
unittestでモックオブジェクトを使用すると、開発者はコードの実際の実装と対話することなく、迅速かつ簡単にユニットテストを作成できます。モックオブジェクトは、テストが実行される環境を分離および制御し、実際のオブジェクトを作成する必要性を排除することでテストを簡素化する方法を提供します。
4. テストカバレッジとレポート
Python unittestを使用すると、開発者はソースコードをカバーするテストを作成できますが、カバレッジレポート(ソースコードの何パーセントがテストでカバーされているか)は表示されません。コードカバレッジは、ユニットテストの有効性を測定するための重要な指標です。コードカバレッジは、テストによって実行されるコードの量を測定します。カバレッジが高いほど、テストは優れています。
Coverageパッケージは、Pythonでのコードカバレッジを測定するための一般的なツールです。Python unittestモジュールを使用して記述されたテストのカバレッジを測定するために使用できます。Coverageパッケージは、pipコマンドを使用してインストールできます:pip install coverage
インストールしたら、Coverageを使用してテストのコードカバレッジのレポートを生成できます。レポートを生成するには、適切なパラメーターを使用してCoverageコマンドを実行する必要があります。たとえば、test_module.pyファイル内のすべてのテストのコードカバレッジのレポートを生成するには、次のコマンドを実行する必要があります。
coverage run -m unittest discover
コマンドを実行すると、Coverageはテストのコードカバレッジのレポートを生成します。このレポートはターミナルで表示するか、HTMLまたはXML形式のファイルに書き込むことができます。
coverage report
Name Stmts Miss Cover
--------------------------------------------
cars/__init__.py 0 0 100%
cars/honda.py 29 0 100%
cars/test_honda.py 41 0 100%
libs/__init__.py 0 0 100%
libs/datetimes.py 7 0 100%
libs/facebook_api.py 7 4 43%
libs/test_datetimes.py 16 0 100%
libs/test_user.py 15 0 100%
libs/user.py 4 0 100%
--------------------------------------------
TOTAL 119 4 97%
または、次を使用できます。
これは、htmlcov/index.htmlにHTMLレポートを書き込みます。
これにより、新しいブラウザウィンドウが開き、まだテストでカバーされていないコードの部分を分析できます。
カバレッジレポートは、開発者にとって非常に貴重なツールです。テストのコードカバレッジの詳細なビューを提供します。レポートを分析することで、開発者はテストされていないコードの領域を特定できます。
5. ヒント、ベストプラクティス、ガイドライン
- テスト駆動開発を使用する:テスト駆動開発(TDD)は、堅牢なユニットテストを開発するための強力なツールです。これは、実際にコードを書く前に、コードの各機能のテストを書くことを伴います。これは、コードが開発チームによって設定されたすべての仕様と要件を満たしていることを保証するのに役立ちます。
- 独立したテストを作成する:互いに独立したテストを作成することが重要です。つまり、各テストは、コードのすべての機能をまとめてテストするのではなく、コードの単一の機能または側面に焦点を当てる必要があります。これにより、コードの一部のバグや問題がコードの他の部分に影響を与えないことが保証されます。
- 詳細なテストケースを作成する:詳細なテストケースを作成することは、ユニットテストプロセスの重要なステップです。各テストは、テスト対象とその期待される結果を明確に記述する必要があります
- テストケースとテストファイルを整理する:ソースコードの各ファイル(facebook_api.py)に対して、「test_」プレフィックス(test_facebook_api.py)が付いたそのファイルのテストファイルがあります。各関数/メソッドには、すべてのロジックをカバーするテストケースのリストがあります
- 出力テスト:値と形式
- 副作用テスト:例外処理テスト
- 各if/else文、try/exceptに対して、テストが必要です
- 規律を保ち、コミットする:優れたユニットテストを作成するには、時間と献身が必要です。コードに変更を加えるときにテストを変更する必要があるだけでなく、テストコードのバグを発見することもあります。努力を要するにもかかわらず、ユニットテストの利点は、小さなプロジェクトであっても非常に価値があります。ただし、これらの報酬にはコストがかかることを忘れないでください。規律が必要です。一貫性を持たなければなりません。テストが常にパスすることを確認してください。「コードが大丈夫」だとわかっているからといって、テストを壊さないようにしてください。
- 自動化する:規律を維持するために、自動化されたユニットテストを設定する必要があります。これらのテストは、プリコミットまたはプリデプロイメントなど、重要な瞬間に実行する必要があります。さらに、ソースコード管理システムが、すべてのテストに合格しないコードを拒否するように設定すると有益です。pre-commit https://pre-commit.com/を使用するのが最適な選択肢です。
6. ユニットテストの原則と主要な概念
6.1 テスト容易性のための設計
テスト容易性のための設計は、Python unittestの重要な概念です。これは、テストが容易で、テストに適した構造を持つコードを設計することを含みます。
- コードはモジュール化されており、関数は単体テスト可能です
- 適切なログとエラー処理。
- 意味のある、必要なすべてのコードパスをカバーし、保守が容易なテストを作成する。
- テストランナーや継続的インテグレーションなどのツールを使用して、テストが定期的に実行され、すべての必要なシナリオがカバーされるようにする。
- テスト、保守、および改善が容易なコードを作成する。
6.2 コスト/ベネフィット
Python unittestを使用すると、開発者は、次の点を考慮することで、個々のテストのコスト/ベネフィットを迅速かつ簡単に評価できます。
- コード行数
- 実行速度
プロジェクトに最適なアプローチを得るために、最小限のリソースで最高の成果を得るための最良の方法を確保します。
6.3 テストマインドセット
テストマインドセットでは、次の点が強調されます。
- 各テストが特定のものであり、他のテストとは独立していることを確認する
- 期待される動作を確認するためのアサーションを作成する
- テストを作成するための計画を立てる
- テスト駆動開発の方法論を使用する
- テスト結果を文書化する
6.4 純粋関数
Python unittestにおける純粋関数は、同じ出力を生成するために外部要因に依存しない関数です。これらは決定論的関数とも呼ばれます。ユニットテストでこのような関数を使用することの利点は次のとおりです。
- 同じ入力が常に同じ出力を生成するため、テストを書きやすい
- 外部要因がテスト結果に影響を与えることを心配する必要がない
- テストをより信頼性が高く、再現性のあるものにする
- コードの一貫性を維持し、長期的にはバグを減らすのに役立つ
6.5 エラー処理のテスト
Python unittestでのエラー処理のテストは、コードでエラーが適切に処理されていることを確認するプロセスです。エラー処理はプログラミングの重要な側面であり、コードが期待どおりに実行されるようにするために必要です。Unittestは、Pythonによって提供される強力なテストフレームワークであり、開発者は簡単にコードをテストできます。Python unittestでエラー処理をテストする方法は次のとおりです。
- 例外をスローするテストケースを作成し、期待される例外がスローされることを確認します。
- 期待される例外を入力として受け取るテストケースを作成し、期待される例外がスローされることを確認します。
- 予期しない例外を入力として受け取るテストケースを作成し、期待される例外がスローされないことを確認します。
- 例外がスローされたときに正しいエラーメッセージを確認するテストケースを作成します。
- エラーの正しい処理を確認するテストケースを作成します。
6.6 プライベートメソッドのテスト
Python unittestでプライベートメソッドをテストすることは、プログラムのすべての部分が正しく機能していることを確認するのに役立つ方法です。プライベートメソッドをテストすることの利点はいくつかあります。
- プログラム全体を実行することなくプライベートメソッドをテストできるため、エラーを迅速に検出して修正できます。
- 特定のプライベートメソッドに検索を絞り込むことができるため、問題の原因をより迅速に特定できます。
- プライベートメソッドが正しく記述されていることを確認するためにテストできるため、より優れたコード設計を促進します。
- テストを使用して、リファクタリング後もプライベートメソッドが正しく機能することを確認できるため、コードをより安全にリファクタリングできます。
6.7 ユニットテストの整理方法
Python unittestでユニットテストを整理することは、コードが徹底的にテストされ、適切に機能していることを確認するための優れた方法です。ユニットテストを整理する方法に関するヒントをいくつか紹介します。
- テストを論理的なテストスイートにグループ化する - テストを、コードの特定のセクションに関連するスイートにグループ化します。これにより、テストを整理しやすく、読みやすくすることができます。
- 記述的なテスト名を書く - テストが何をしているかを説明するのに役立つ記述的な名前をテストに付けます。これにより、コードの特定の部分に関連するテストを迅速に特定できます。
- setUpとtearDownを使用する - setUpとtearDownメソッドを使用して、テストに必要なリソースをセットアップおよびクリーンアップします。これにより、重複コードを削減し、テストを読みやすくすることができます。
- データ駆動テストを作成する - データ駆動テストを作成して、複数のデータセットに対してコードをテストします。これにより、入力されるデータに関係なく、コードが適切に機能していることを確認するのに役立ちます。
- アサーションを利用する - アサート文を使用して、コードが期待される結果を返していることを確認します。これにより、コードの問題を迅速に特定できます。
7. 参考文献
書籍
Test-Driven Development with Python
リンク
https://docs.python.org/3/library/unittest.html