Pythonパッケージのテストを書く#

Writing code that tests your package code, also known as test suites, is important for you as a maintainer, your users, and package contributors. Test suites consist of sets of functions, methods, and classes that are written with the intention of making sure a specific part of your code works as you expected it to.

なぜパッケージのテストを書くのか?#

Tests act as a safety net for code changes. They help you identify and fix bugs before they affect users. Tests also instill confidence that code changes from contributors won't break existing functionality.

Pythonパッケージのテストを書くことは重要です:

  • Catch mistakes: Tests are a safety net. When you make changes or add new features to your package, tests can quickly tell you if you accidentally broke something that was working fine before.

  • Save time: Imagine you have a magic button that can automatically check if your package is still working properly. Tests are like that magic button! They can run all those checks for you, saving you time.

  • Easier collaboration: If you're working with others or have outside contributors, tests help everyone stay on the same page. Your tests explain how your package is supposed to work, making it easier for others to understand and contribute to your project.

  • Fearless refactoring: Refactoring means making improvements to your code structure without changing its behavior. Tests empower you to make these changes; if you break something, test failures will let you know.

  • Documentation: Tests serve as technical examples of how to use your package. This can be helpful for new technical contributors who want to contribute code to your package. They can look at your tests to understand how parts of your code functionality fits together.

  • Long-term ease of maintenance: As your package evolves, tests ensure that your code continues to behave as expected, even as you make changes over time. Thus you are helping your future self when writing tests.

  • より簡単なプルリクエストのレビュー: GitHub Actionsのような CI フレームワークでテストを実行することで、あなたや貢献者がコードベースに変更を加えるたびに、コードベースの問題や変更点をキャッチすることができます。 これにより、あなたのソフトウェアが期待通りに動作することが保証されます。

ユーザーエッジケースのテスト#

エッジケースとは、あるユーザがあなたのパッケージを使用する際の、予期せぬ、あるいは "外れ値" の使い方を指します。 テストによって、パッケージの機能を損なう可能性のある様々なエッジケースに対処することができます。 例えば、ある関数がpandasの dataframe を期待したのに、ユーザがnumpyの array を提供した場合、何が起こるでしょうか? あなたのコードは、このような状況を丁寧に処理し、明確なフィードバックを提供していますか?それとも、原因不明の失敗でユーザーをイライラさせたままにしていますか?

注釈

テストの入門書としては、 このSoftware Carpentryのレッスン を参照してください。

テスト例

Let's say you have a Python function that adds two numbers together.

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}"
    )

🧩🐍 How do you know what type of tests to write?#

As you begin to write tests for your package, you should consider:

  1. 開発のガイドに役立つ 3 種類のテスト Python パッケージのテスト タイプ があります。

  2. テストでは、ユーザーがパッケージをどのように使用 (そして悪用!) するかを考慮する必要があります。

注釈

This section has been adapted from a presentation by Nick Murphy.

But, what should you be testing in your package? Below are a few examples:

  • いくつかの典型的なケースをテスト: パッケージが、ユーザーが使用したときに期待通りに機能することをテストします。例えば、パッケージが2つの数値を足すことになっている場合、その2つの数値を足した結果が正しいかどうかをテストします。

  • Test special cases: Sometimes there are special or outlier cases. For instance, if a function performs a specific calculation that may become problematic closer to the value of 0, test it with the input of both 0 and nearby values.

  • Test at and near expected boundaries: If a function requires a value that is greater than or equal to 1, make sure that the function still works with the values 1 and 0.999, as well as 1.001 (values close to the constraint). Make sure that the function fails gracefully when given unexpected values and that the user can easily understand why it failed by providing a useful error message.

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_values tells readers more than test_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.

Next steps#

何をテストするのか、なぜテストするのかが理解できたので、3 種類のテスト (単体、統合、エンドツーエンド) を調べて、パッケージに最適なテストのスタイルを決定します。次に、ローカルでテストを実行する および 継続的インテグレーションで する方法を学びます。最後に、コード カバレッジ メトリクスを使用して進捗状況を追跡します。