Pythonパッケージのテストタイプ#

3種類のテスト: 単体テスト、統合テスト、機能テスト#

テストスイートを作成する際に考慮したいテストには、さまざまなタイプがあります:

  1. 単体テスト

  2. 統合

  3. エンドツーエンド(機能テストとも呼ばれる)テスト

それぞれのテストには異なる目的があります。 ここでは、3種類のテストすべてについて学びます。

単体テスト#

単体テストは、個々のコンポーネントやコードの単位を分離してテストし、それらが正しく動作することを確認するものです。単体テストの目的は、ソフトウェアの各部分、通常は関数やメソッドのレベルが、意図したタスクを正しく実行することを検証することです。

単体テストは、パズルの各パーツが壊れていないかどうかを調べることに例えることができます。パズルのピースがすべて合わなければ、完成することはありません。同様に、コードを扱う場合、テストは各関数、プロパティ、クラス、メソッドが分離されたときに正しく機能することを保証します。

ユニットテスト例: 温度値を摂氏から華氏に変換する関数があるとします。その関数のテストは、摂氏の値が提供されたとき、その関数が正しい華氏の値を返すことを保証するかもしれません。この関数はユニットテストです。これは、コード内の1つのユニット(関数)をチェックします。

# Example package function
def celsius_to_fahrenheit(celsius):
    """
    Convert temperature from Celsius to Fahrenheit.

    Parameters:
        celsius (float): Temperature in Celsius.

    Returns:
        float: Temperature in Fahrenheit.
    """
    fahrenheit = (celsius * 9/5) + 32
    return fahrenheit

上記の関数のユニットテスト例。このテストは、 tests/ ディレクトリの pytest コマンドを使って実行します。

import pytest
from temperature_converter import celsius_to_fahrenheit

def test_celsius_to_fahrenheit():
    """
    Test the celsius_to_fahrenheit function.
    """
    # Test with freezing point of water
    assert pytest.approx(celsius_to_fahrenheit(0), abs=0.01) == 32.0

    # Test with boiling point of water
    assert pytest.approx(celsius_to_fahrenheit(100), abs=0.01) == 212.0

    # Test with a negative temperature
    assert pytest.approx(celsius_to_fahrenheit(-40), abs=0.01) == -40.0

パズルのピースがうまく組み合わさったような画像です。パズルのピースは紫色、緑色、鴨の羽色とカラフルです。

ユニットテストでは、コードの各部分がそれ自体で期待通りに動作することを確認する必要があります。#

統合テスト#

統合テストでは、パッケージの各パーツがどのように連動するか、あるいは統合されるかをテストします。統合テストは、パズルのピースをつなげて全体像を作るようなものです。統合テストは、コードのさまざまな部分がどのように適合し、連携して動作するかに焦点を当てます。

例えば、スプレッドシートに温度データを収集し、摂氏から華氏に変換し、特定の期間の平均温度を提供する一連のステップがあったとします。統合テストは、そのワークフローのすべての部分が期待通りに動作することを保証します。


def fahr_to_celsius(fahrenheit):
    """
    Convert temperature from Fahrenheit to Celsius.

    Parameters:
        fahrenheit (float): Temperature in Fahrenheit.

    Returns:
        float: Temperature in Celsius.
    """
    celsius = (fahrenheit - 32) * 5/9
    return celsius

# Function to calculate the mean temperature for each year and the final mean
def calc_annual_mean(df):
    # TODO: make this a bit more robust so we can write integration test examples??
    # Calculate the mean temperature for each year
    yearly_means = df.groupby('Year').mean()

    # Calculate the final mean temperature across all years
    final_mean = yearly_means.mean()

    # Return a converted value
    return fahr_to_celsius(yearly_means), fahr_to_celsius(final_mean)

パズルのピースが2つあり、一部欠けている画像です。パズルのピースは紫・鴨の羽色・黄色・青色。 それぞれのピースの形が合いません。

パズルのピースの両端が欠けていると、パズルの他の要素と連動することができません。ソフトウェアの個々の関数、メソッド、クラスについても同様です。コードは、ある一連のタスクを実行するために、個々に、そして一緒に働く必要があります。#

パズルのピースがうまく組み合わさったような画像です。パズルのピースは紫色、緑色、鴨の羽色とカラフルです。

統合テストは、コードの一部が連携して動作することが期待されており、期待通りに動作することを保証するものでなければなりません。#

エンドツーエンド(機能)テスト#

Python におけるエンドツーエンドのテスト(機能テストとも呼ばれる)は、ソフトウェアの包括的なチェックリストのようなものです。実際のユーザーのエンドツーエンドのワークフローをシミュレートし、コードベースが実際のアプリケーションやユースケースを最初から最後までサポートすることを確認します。これらのテストは、小規模なテストでは表示されないかもしれない問題をキャッチし、アプリケーションやプログラム全体が正しく動作することを確認するのに役立ちます。ソフトウェアを実用化する前に最終チェックを行い、ユーザーにスムーズなエクスペリエンスを提供する準備ができていることを確認する方法だと考えてください。

デイジーを描いたパズルの完成イメージ

エンドツーエンドテストまたは機能テストは、パッケージがサポートすることを期待するワークフロー全体を表します。#

エンドツーエンドのテストも、プログラムが最初から最後までどのように実行されるかをテストします。ドキュメントに追加するチュートリアルは、分離された環境のCIで実行され、エンドツーエンドのテストのもう一つの例です。

注釈

科学的なパッケージの場合、パッケージがサポートする中核的なワークフローを強調する短いチュートリアルを作成し、ドキュメントのビルド時に実行することで、エンドツーエンドのテストを兼ねることができます。

ユニットテスト、統合テスト、エンドツーエンドテストの比較#

単体テスト、統合テスト、エンドツーエンドテストには、それぞれ相補的な長所と短所があります。 単体テストは、そのきめ細かな性質から、どこでエラーが発生しているかを切り分けるのに適しています。 しかし、ユニットテストは、コードの異なるセクションが連携して動作することを検証するのには役に立ちません。

統合テストとエンドツーエンドテストは、プログラムのさまざまな部分が連携して動作することを検証しますが、どこでエラーが発生しているかを切り分けるにはあまり適していません。例えば、コードをリファクタリングすると、エンドツーエンドのテストが壊れる可能性があります。しかし、リファクタリングによって既存のコードに新しい動作が導入されなかったのであれば、コードの元の機能をテストするユニットテストがパスし続けることに頼ることができます。

重要なのは、さまざまな種類のテストにまつわる詳細について心配することにエネルギーを費やす必要はないということです。テストスイートの作成に取りかかるときには、パッケージが何をするのか、そしてパッケージの一部をどのようにテストする必要があるのかを考えてください。さまざまな種類のテストに慣れ親しむことは、テストの書き方や、さまざまな種類のテストが互いにどのように補完し合えるかを考える際のフレームワークとなります。