Pythonパッケージのテストを書く#
パッケージのコードをテストするコード (テストスイートとも呼ばれる) を書くことは、メンテナであるあなた、ユーザー、そしてパッケージの貢献者にとって重要です。テストスイートは、コードの特定の部分が期待どおりに動作することを確認する目的で書かれる関数、メソッド、クラスの集まりで構成されます。
なぜパッケージのテストを書くのか?#
テストはコード変更のセーフティネットとして機能します。バグがユーザーに影響を与える前に発見して修正するのに役立ちます。また、貢献者によるコード変更が既存の機能を壊さないという自信を得られます。
Pythonパッケージのテストを書くことは重要です:
ミスの検出: テストはセーフティネットです。パッケージに変更を加えたり新機能を追加したりしたとき、以前は問題なく動作していたものを誤って壊していないかをテストがすぐに教えてくれます。
時間の節約: あなたのパッケージがまだ正常に動作しているかどうかを自動的にチェックできる魔法のボタンがあるとしましょう。テストはその魔法のボタンのようなものです!すべてのチェックを実行してくれるので、時間を節約できます。
より容易なコラボレーション: 他の人と一緒に仕事をしていたり、外部の協力者がいる場合、テストは全員が同じ認識を保つのに役立ちます。テストはパッケージがどのように動作するはずなのかを説明するので、他の人が理解しやすくなり、プロジェクトに貢献しやすくなります。
恐れずにリファクタリング: リファクタリングとは、動作を変えずにコード構造を改善することです。テストがあれば安心して変更でき、何かを壊した場合はテストの失敗が知らせてくれます。
ドキュメントとしての役割: テストはパッケージの使い方の技術的な例になります。これは、パッケージにコードを提供しようとする新しい技術的貢献者にとって役に立ちます。テストを見ることで、コードの機能がどのように組み合わされているかを理解できます。
長期的なメンテナンスの容易さ: パッケージが進化するにつれて、テストは、時間の経過とともに変更を加えても、コードが期待どおりに動作し続けることを保証します。テストを書くことで、将来の自分を助けることになります。
より簡単なプルリクエストのレビュー: GitHub Actionsのような CI フレームワークでテストを実行することで、あなたや貢献者がコードベースに変更を加えるたびに、コードベースの問題や変更点をキャッチすることができます。 これにより、あなたのソフトウェアが期待通りに動作することが保証されます。
ユーザーエッジケースのテスト#
エッジケースとは、あるユーザがあなたのパッケージを使用する際の、予期せぬ、あるいは "外れ値" の使い方を指します。 テストによって、パッケージの機能を損なう可能性のある様々なエッジケースに対処することができます。 例えば、ある関数がpandasの dataframe を期待したのに、ユーザがnumpyの array を提供した場合、何が起こるでしょうか? あなたのコードは、このような状況を丁寧に処理し、明確なフィードバックを提供していますか?それとも、原因不明の失敗でユーザーをイライラさせたままにしていますか?
注釈
テストの入門書としては、 このSoftware Carpentryのレッスン を参照してください。
テスト例
例えば、2つの数値を足し合わせる Python 関数があるとしましょう。
def add_numbers(a: float, b: float) -> float:
"""
Add two numbers together and return the result.
Parameters
----------
a : float
The first number to add.
b : float
The second number to add.
Returns
-------
float
The sum of the two numbers.
"""
return a + b
A test to ensure that function runs as you might expect when provided with
different numbers might look like this. Each set of inputs gets its own
readable ID, so a failing case is easy to spot in the pytest output
(for example, test_add_numbers_returns_sum[negative]):
import pytest
@pytest.mark.parametrize(
"first_number, second_number, expected_sum",
[
(2, 3, 5),
(-1, 4, 3),
(0, 0, 0),
],
ids=["positive", "negative", "zero"],
)
def test_add_numbers_returns_sum(first_number, second_number, expected_sum):
"""Test that add_numbers returns the sum of two numbers."""
actual_sum = add_numbers(first_number, second_number)
assert actual_sum == expected_sum, (
f"Expected {expected_sum}, but got {actual_sum}"
)
🧩🐍 どのような種類のテストを書けばよいでしょうか?#
パッケージのテストを書き始めるときには、次のことを考慮してください:
開発のガイドに役立つ 3 種類のテスト Python パッケージのテスト タイプ があります。
テストでは、ユーザーがパッケージをどのように使用 (そして悪用!) するかを考慮する必要があります。
注釈
このセクションは、Nick Murphy によるプレゼンテーションを参考にしています。
では、パッケージで何をテストすべきでしょうか?いくつか例を挙げます:
いくつかの典型的なケースをテスト: パッケージが、ユーザーが使用したときに期待通りに機能することをテストします。例えば、パッケージが2つの数値を足すことになっている場合、その2つの数値を足した結果が正しいかどうかをテストします。
特殊なケースのテスト: 特殊なケースや外れ値のケースが存在することもあります。たとえば、ある関数が特定の計算を実行し、その計算が 0 に近い値ほど問題になる可能性がある場合は、0 とその近傍の値を入力としてテストします。
期待される境界付近でのテスト: 関数が 1 以上の値を必要とする場合は、1 と 0.999、さらに 1.001 (制約に近い値) でも関数が正しく動作することを確認します。予期しない値が与えられたときに、有用なエラーメッセージを提供することで、関数がわかりやすく失敗し、ユーザーが失敗の理由を簡単に理解できるようにします。
Write tests that are easy to review#
Clear tests help reviewers and future contributors understand what behavior is being checked and why that behavior matters. As you write tests, try to make the main idea of each test visible without requiring readers to reverse-engineer the setup.
Use descriptive test names: Name each test after the behavior, condition, or edge case it checks. For example,
test_add_numbers_accepts_negative_valuestells readers more thantest_add_numbers_2.Keep one main behavior in focus: A test can contain several assertions, but they should support one clear idea. If a test starts checking several unrelated behaviors, split it into smaller tests.
Add comments only where they help: A short comment is useful when a fixture, regression, or unusual edge case is not obvious from the assertion. Avoid comments that repeat the code.
Keep setup, action, and assertion easy to scan: Arrange the test so readers can quickly see what input is prepared, what code is run, and what result is expected.
For example, a test based on the add_numbers function in the
pyOpenSci Python package template
can make its inputs, action, and expected result visible at a glance:
from my_package.example import add_numbers
def test_add_numbers_returns_sum():
first_number = 1
second_number = 2
expected_sum = 3
actual_sum = add_numbers(first_number, second_number)
assert actual_sum == expected_sum
For more guidance on structuring readable tests, see pytest's anatomy of a test, which explains the arrange, act, assert, and cleanup phases, and the package template's example unit test.
次のステップ#
何をテストするのか、なぜテストするのかが理解できたので、3 種類のテスト (単体、統合、エンドツーエンド) を調べて、パッケージに最適なテストのスタイルを決定します。次に、ローカルでテストを実行する および 継続的インテグレーションで する方法を学びます。最後に、コード カバレッジ メトリクスを使用して進捗状況を追跡します。