コードベースへの貢献#

コード標準#

優れたコードを作成することは、単に何を書くかということだけではありません。どのように書くかということも重要です。 継続的インテグレーション テスト中、いくつかのツールが実行され、コードのスタイル上のエラーがチェックされます。警告が生成されると、テストは失敗します。したがって、優れたスタイルは pandas にコードを提出するための要件です。

pandas には、コントリビューターがプロジェクトに貢献する前に変更を検証するのに役立ついくつかのツールがあります。

  • ./ci/code_checks.sh: スクリプトは、ドキュメントテスト、ドキュメント文字列のフォーマット、およびインポートされたモジュールを検証します。 docstringscode、および doctests のパラメータを使用して、チェックを個別に実行できます(例: ./ci/code_checks.sh doctests)。

  • pre-commit。これについては次のセクションで詳しく説明します。

さらに、多くの人が私たちのライブラリを使用しているため、多くのユーザーコードを壊す可能性のあるコードへの突然の変更を行わないことが重要です。つまり、大規模な破損を避けるために、できる限り *後方互換性* を維持する必要があります。

Pre-commit#

さらに、継続的インテグレーション では、pre-commit フックを使用して、blackruffisortclang-format などのコードフォーマットチェックが実行されます。これらのチェックからの警告は、継続的インテグレーション を失敗させます。したがって、コードを送信する前に自分でチェックを実行すると役立ちます。これは、pre-commit をインストールすることで実行できます(開発環境の設定 の指示に従った場合、すでに実行されているはずです)。次に、以下を実行します。

pre-commit install

pandas リポジトリのルートから。これで、変更をコミットするたびに、各チェックを手動で実行する必要なく、すべてのスタイルチェックが実行されます。さらに、pre-commit を使用すると、コードチェックが変更された場合でも、より簡単に最新の状態を維持できます。

必要に応じて、git commit --no-verify を使用してこれらのチェックをスキップできることに注意してください。

ワークフローの一部として pre-commit を使用したくない場合は、次のいずれかを使用してチェックを実行できます。

pre-commit run --files <files you have modified>
pre-commit run --from-ref=upstream/main --to-ref=HEAD --all-files

事前に pre-commit install を実行する必要はありません。

最後に、各コミットでは実行されませんが、継続的インテグレーション中に実行される遅い pre-commit チェックもいくつかあります。次のコマンドで手動でトリガーできます。

pre-commit run --hook-stage manual --all-files

使用されなくなったリポジトリをクリーンアップするために、pre-commit gc を定期的に実行する必要がある場合があります。

virtualenv のインストールが競合している場合は、エラーが発生する可能性があります。 こちらを参照してください。

また、virtualenv のバグにより、conda を使用している場合に問題が発生する可能性があります。これを解決するには、virtualenv をバージョン 20.0.33 にダウングレードできます。

最近、アップストリームブランチからメインをマージした場合、pre-commit で使用される依存関係の一部が変更されている可能性があります。開発環境を更新 してください。

オプションの依存関係#

オプションの依存関係(例:matplotlib)は、プライベートヘルパー pandas.compat._optional.import_optional_dependency を使用してインポートする必要があります。これにより、依存関係が満たされない場合に一貫したエラーメッセージが保証されます。

オプションの依存関係を使用するすべてのメソッドには、オプションの依存関係が見つからない場合に ImportError が発生することをアサートするテストを含める必要があります。ライブラリが存在する場合は、このテストをスキップする必要があります。

すべてのオプションの依存関係は、オプションの依存関係 でドキュメント化する必要があり、必要な最小バージョンは pandas.compat._optional.VERSIONS 辞書で設定する必要があります。

後方互換性#

後方互換性を維持するようにしてください。 pandas には、多くの既存のコードを持つ多くのユーザーがいるため、可能であれば壊さないでください。破損が必要と思われる場合は、プルリクエストの一部としてその理由を明確に述べてください。また、メソッドシグネチャを変更するときは注意し、必要に応じて非推奨の警告を追加してください。また、非推奨の関数またはメソッドに非推奨の sphinx ディレクティブを追加してください。

非推奨化されているものと同じ引数を持つ関数が存在する場合は、pandas.util._decorators.deprecate を使用できます。

from pandas.util._decorators import deprecate

deprecate('old_func', 'new_func', '1.1.0')

そうでない場合は、手動で行う必要があります。

import warnings
from pandas.util._exceptions import find_stack_level


def old_func():
    """Summary of the function.

    .. deprecated:: 1.1.0
       Use new_func instead.
    """
    warnings.warn(
        'Use new_func instead.',
        FutureWarning,
        stacklevel=find_stack_level(),
    )
    new_func()


def new_func():
    pass

次のことも必要になります。

  1. 非推奨の引数で呼び出すと警告が発行されることをアサートする新しいテストを作成します。

  2. pandas の既存のすべてのテストとコードを更新して、新しい引数を使用します。

詳細については、警告のテスト を参照してください。

型ヒント#

pandas は、PEP 484 スタイルの型ヒントの使用を強く推奨します。新しい開発には型ヒントを含める必要があり、既存のコードに注釈を付けるプルリクエストも受け付けています。

スタイルガイドライン#

型のインポートは、from typing import ... の規則に従う必要があります。コードは、pre-commit チェック によって、最新の構成(例: typing.List の代わりに組み込みの list を使用するなど)を使用するように自動的に書き換えられる場合があります。

コードベースの一部のケースでは、クラスが組み込みをシャドウイングするクラス変数を定義している場合があります。これにより、Mypy 1775 で説明されているように、問題が発生します。ここでの防御的な解決策は、組み込みの明確なエイリアスを作成し、注釈なしで使用することです。たとえば、次のような定義に遭遇した場合

class SomeClass1:
    str = None

これに注釈を付ける適切な方法は次のとおりです。

str_type = str

class SomeClass2:
    str: str_type = None

アナライザーよりもよく知っている場合は、typing モジュールから cast を使用したくなる場合があります。これは、特にカスタム推論関数を使用する場合に発生します。例

from typing import cast

from pandas.core.dtypes.common import is_number

def cannot_infer_bad(obj: Union[str, int, float]):

    if is_number(obj):
        ...
    else:  # Reasonably only str objects would reach this but...
        obj = cast(str, obj)  # Mypy complains without this!
        return obj.upper()

ここでの制約は、人間であれば is_numberint 型と float 型を捕捉することを合理的に理解できますが、mypy はまだ同じ推論を行うことができないということです ( mypy #5206 を参照)。上記の方法は機能しますが、cast の使用は強く推奨されません。可能な限り、静的解析に対応するようにコードをリファクタリングすることが望ましいです。

def cannot_infer_good(obj: Union[str, int, float]):

    if isinstance(obj, str):
        return obj.upper()
    else:
        ...

カスタム型と推論を使用すると、常に可能とは限らないため例外が設けられますが、そのような道に進む前に cast を避けるためのあらゆる努力を払う必要があります。

pandas 固有の型#

pandas に固有の一般的に使用される型は pandas._typing に含まれており、該当する場合はこれらを使用する必要があります。このモジュールは今のところプライベートですが、最終的には pandas に対する型チェックを実装したいサードパーティライブラリに公開されるべきです。

たとえば、pandas の非常に多くの関数が dtype 引数を受け入れます。これは "object" のような文字列、np.int64 のような numpy.dtype 、または pd.CategoricalDtype のような pandas の ExtensionDtype として表現できます。ユーザーにこれらのすべてのオプションを常にアノテーションする必要性を負担させるのではなく、pandas._typing モジュールからインポートして再利用することができます。

from pandas._typing import Dtype

def as_type(dtype: Dtype) -> ...:
    ...

このモジュールは最終的に、「path-like」、「array-like」、「numeric」などの繰り返し使用される概念の型を格納し、axis のように頻繁に現れるパラメータのエイリアスも保持できます。このモジュールの開発は活発に行われているため、利用可能な型の最新リストについては必ずソースを参照してください。

型ヒントの検証#

pandas は mypypyright を使用して、コードベースと型ヒントを静的に解析します。変更を加えたら、次のコマンドを実行することで型ヒントの一貫性を確保できます。

pre-commit run --hook-stage manual --all-files mypy
pre-commit run --hook-stage manual --all-files pyright
pre-commit run --hook-stage manual --all-files pyright_reportGeneralTypeIssues
# the following might fail if the installed pandas version does not correspond to your local git version
pre-commit run --hook-stage manual --all-files stubtest

python 環境で。

警告

  • 上記のコマンドは現在の python 環境を使用することに注意してください。python パッケージが pandas CI によってインストールされたものよりも古い/新しい場合、上記のコマンドが失敗する可能性があります。これは、mypy または numpy のバージョンが一致しない場合に頻繁に発生します。pandas CI がインストールするバージョンを確認するには、python 環境のセットアップ方法 を参照するか、最近成功したワークフロー を選択し、「Docstring validation, typing, and other manual pre-commit hooks」ジョブを選択して、「Set up Conda」と「Environment info」をクリックしてください。

pandas を使用したコードでの型ヒントのテスト#

警告

  • pandas はまだ py.typed ライブラリではありません (PEP 561)! pandas をローカルで py.typed ライブラリとして宣言する主な目的は、pandas に組み込まれた型アノテーションをテストおよび改善することです。

pandas が py.typed ライブラリになるまでは、pandas インストールフォルダに「py.typed」という空のファイルを作成することで、pandas に同梱されている型アノテーションを簡単に試すことができます。

python -c "import pandas; import pathlib; (pathlib.Path(pandas.__path__[0]) / 'py.typed').touch()"

py.typed ファイルが存在すると、型チェッカーに pandas がすでに py.typed ライブラリであることを通知します。これにより、型チェッカーは pandas に同梱されている型アノテーションを認識します。

継続的インテグレーションを使用したテスト#

pandas テストスイートは、プルリクエストが送信されると、GitHub Actions 継続的インテグレーションサービスで自動的に実行されます。ただし、プルリクエストを送信する前にブランチでテストスイートを実行する場合は、継続的インテグレーションサービスを GitHub リポジトリに接続する必要があります。手順は GitHub Actions にあります。

すべて「緑色」のビルドの場合、プルリクエストはマージの対象と見なされます。テストが失敗した場合、赤い「X」が表示され、それをクリックすると個々の失敗したテストを確認できます。これは、成功したビルドの例です。

../_images/ci.png

テスト駆動開発#

pandas はテストを重視しており、貢献者が テスト駆動開発 (TDD) を採用することを強く推奨しています。この開発プロセスは、「非常に短い開発サイクルの繰り返しに依存します。最初に、開発者は (最初は失敗する) 自動テストケースを作成して、望ましい改善または新しい機能を定義し、次にそのテストに合格するための最小限のコードを作成します。」したがって、実際にコードを記述する前に、テストを記述する必要があります。多くの場合、テストは元の GitHub issue から取得できます。ただし、追加のユースケースを検討し、対応するテストを作成する価値は常にあります。

テストの追加は、コードが pandas にプッシュされた後によくある要求の 1 つです。したがって、これが問題にならないように、事前にテストを記述する習慣を身につける価値があります。

テストの記述#

すべてのテストは、特定のパッケージの tests サブディレクトリに配置する必要があります。このフォルダには、現在のテストの多くの例が含まれており、これらを見てインスピレーションを得ることをお勧めします。

一般的なヒントとして、統合開発環境 (IDE) の検索機能、またはターミナルの git grep コマンドを使用して、メソッドが呼び出されるテストファイルを見つけることができます。テストを配置する最適な場所がわからない場合は、最善の推測をしてください。ただし、レビューアがテストを別の場所に移動するように要求する可能性があることに注意してください。

git grep を使用するには、ターミナルで次のコマンドを実行できます。

git grep "function_name("

これにより、リポジトリ内のすべてのファイルでテキスト function_name( が検索されます。これは、コードベース内の関数をすばやく特定し、その関数のテストを追加する最適な場所を判断するのに便利な方法です。

理想的には、テストが存在すべき明確な場所が 1 つだけである必要があります。その理想に到達するまでは、テストを配置する場所に関するいくつかの経験則があります。

  1. テストが pd._libs.tslibs のコードのみに依存している場合、このテストは次のいずれかに属する可能性が高いです。

    • tests.tslibs

      tests.tslibs のファイルは、pd._libs.tslibs 以外の pandas モジュールからインポートしてはなりません。

    • tests.scalar

    • tests.tseries.offsets

  2. テストが pd._libs のコードのみに依存している場合、このテストは次のいずれかに属する可能性が高いです。

    • tests.libs

    • tests.groupby.test_libgroupby

  3. テストが算術演算または比較メソッドのテストである場合、このテストは次のいずれかに属する可能性が高いです。

    • tests.arithmetic

      これらは、box_with_array フィクスチャを使用して DataFrame/Series/Index/ExtensionArray の動作をテストするために共有できるテストを対象としています。

    • tests.frame.test_arithmetic

    • tests.series.test_arithmetic

  4. テストが縮約メソッド (min、max、sum、prod など) のテストである場合、このテストは次のいずれかに属する可能性が高いです。

    • tests.reductions

      これらは、DataFrame/Series/Index/ExtensionArray の動作をテストするために共有できるテストを対象としています。

    • tests.frame.test_reductions

    • tests.series.test_reductions

    • tests.test_nanops

  5. テストがインデックスメソッドのテストである場合、これはテストを配置する場所を決定する上で最も難しいケースです。これらのテストは多く、多くのテストが複数のメソッド (たとえば、Series.__getitem__Series.loc.__getitem__ の両方) をテストするためです。

    1. テストが Index メソッド (たとえば、Index.get_locIndex.get_indexer) を具体的にテストしている場合、このテストは次のいずれかに属する可能性が高いです。

      • tests.indexes.test_indexing

      • tests.indexes.fooindex.test_indexing

      これらのファイル内には、メソッド固有のテストクラス (例: TestGetLoc) があるはずです。

      ほとんどの場合、これらのテストでは Series オブジェクトも DataFrame オブジェクトも必要ありません。

    2. テストが __getitem__ または __setitem__ 以外 の Series または DataFrame インデックスメソッド (例: xswheretakemasklookup、または insert) のテストである場合、このテストは次のいずれかに属する可能性が高いです。

      • tests.frame.indexing.test_methodname

      • tests.series.indexing.test_methodname

    3. テスト対象が loc, iloc, at, または iat のいずれかですか?このテストは、以下のいずれかに該当する可能性が高いです。

      • tests.indexing.test_loc

      • tests.indexing.test_iloc

      • tests.indexing.test_at

      • tests.indexing.test_iat

      適切なファイル内では、テストクラスはインデクサのタイプ(例:TestLocBooleanMask)または主要なユースケース(例:TestLocSetitemWithExpansion)のいずれかに対応します。

      複数のインデックス作成メソッドをテストするテストについては、セクションD)の注記を参照してください。

    4. テスト対象が Series.__getitem__, Series.__setitem__, DataFrame.__getitem__, または DataFrame.__setitem__ のいずれかですか?このテストは、以下のいずれかに該当する可能性が高いです。

      • tests.series.test_getitem

      • tests.series.test_setitem

      • tests.frame.test_getitem

      • tests.frame.test_setitem

      このようなテストの多くは、複数の類似メソッドをテストする場合があります。例:

      import pandas as pd
      import pandas._testing as tm
      
      def test_getitem_listlike_of_ints():
          ser = pd.Series(range(5))
      
          result = ser[[3, 4]]
          expected = pd.Series([2, 3])
          tm.assert_series_equal(result, expected)
      
          result = ser.loc[[3, 4]]
          tm.assert_series_equal(result, expected)
      

    このような場合、テストの場所は、テスト対象の基盤となるメソッドに基づいて決定する必要があります。あるいは、バグ修正のテストの場合は、実際のバグの場所に基づいて決定します。したがって、この例では、Series.__getitem__Series.loc.__getitem__ を呼び出していることがわかっているため、これは実際には loc.__getitem__ のテストです。したがって、このテストは tests.indexing.test_loc に該当します。

  6. テスト対象は DataFrame または Series のメソッドですか?

    1. そのメソッドはプロットメソッドですか?このテストは、以下のいずれかに該当する可能性が高いです。

      • tests.plotting

    2. そのメソッドは IO メソッドですか?このテストは、以下のいずれかに該当する可能性が高いです。

      • tests.io

        これには to_string が含まれますが、__repr__ は除外されます。これは tests.frame.test_reprtests.series.test_repr でテストされます。他のクラスには、多くの場合、test_formats ファイルがあります。

    3. それ以外の場合、このテストは以下のいずれかに該当する可能性が高いです。

      • tests.series.methods.test_mymethod

      • tests.frame.methods.test_mymethod

        テストが frame_or_series フィクスチャを使用して DataFrame/Series 間で共有できる場合、慣例により tests.frame ファイルに配置されます。

  7. テスト対象が、Series/DataFrame に依存しない Index メソッドですか?このテストは、以下のいずれかに該当する可能性が高いです。

    • tests.indexes

  1. テスト対象が、pandas が提供する ExtensionArray(Categorical, DatetimeArray, TimedeltaArray, PeriodArray, IntervalArray, NumpyExtensionArray, FloatArray, BoolArray, StringArray)のいずれかですか?このテストは、以下のいずれかに該当する可能性が高いです。

    • tests.arrays

  2. テスト対象がすべての ExtensionArray サブクラス(「EA インターフェース」)ですか?このテストは、以下のいずれかに該当する可能性が高いです。

    • tests.extension

pytest の使用#

テスト構造#

pandas の既存のテスト構造はほとんどクラスベースです。つまり、通常はクラス内にテストがラップされていることがわかります。

class TestReallyCoolFeature:
    def test_cool_feature_aspect(self):
        pass

私たちは、pytest フレームワークを使用した、より関数型のスタイルを好みます。これは、テストと開発を容易にする、よりリッチなテストフレームワークを提供します。したがって、テストクラスを作成する代わりに、次のようなテスト関数を記述します。

def test_really_cool_feature():
    pass

推奨される pytest イディオム#

  • 関数型テストは def test_* という名前で、フィクスチャまたはパラメータのいずれかである引数のみを受け取ります。

  • スカラーと真理値テストには、ベアな assert を使用します。

  • SeriesDataFrame の結果を比較するには、tm.assert_series_equal(result, expected)tm.assert_frame_equal(result, expected) をそれぞれ使用します。

  • 複数のケースをテストする場合は、@pytest.mark.parameterize を使用します。

  • テストケースが失敗することが予想される場合は、pytest.mark.xfail を使用します。

  • テストケースが絶対にパスしないことが予想される場合は、pytest.mark.skip を使用します。

  • テストケースに特定のマークが必要な場合は、pytest.param を使用します。

  • 複数のテストがセットアップオブジェクトを共有できる場合は、@pytest.fixture を使用します。

警告

テストをすぐに停止し、テストが失敗するかどうかをチェックしないため、pytest.xfailpytest.mark.xfail とは異なります)は使用しないでください。この動作が必要な場合は、代わりに pytest.skip を使用してください。

テストが失敗することがわかっていても、その失敗方法をキャプチャするべきでない場合は、pytest.mark.xfail を使用します。このメソッドは、バグのある動作を示すテストや、実装されていない機能に対して使用するのが一般的です。失敗するテストに不安定な動作がある場合は、引数 strict=False を使用します。これにより、テストが偶然にパスした場合に pytest が失敗しなくなります。strict=False の使用は非常に望ましくありません。最後の手段としてのみ使用してください。

テストが pytest の収集フェーズ中に適切にマークされるように、テスト内での使用よりも、デコレータ @pytest.mark.xfail と引数 pytest.param を優先します。複数のパラメータ、フィクスチャ、またはこれらの組み合わせを含むテストを xfail するには、テストフェーズ中に xfail するしかありません。そのためには、request フィクスチャを使用します。

def test_xfail(request):
    mark = pytest.mark.xfail(raises=TypeError, reason="Indicate why here")
    request.applymarker(mark)

xfail は、無効なユーザー引数による失敗を含むテストには使用しないでください。これらのテストでは、pytest.raises を使用して、正しい例外タイプとエラーメッセージが発生していることを確認する必要があります。

警告のテスト#

コードブロックが警告を発生させることを確認するには、tm.assert_produces_warning をコンテキストマネージャーとして使用します。

with tm.assert_produces_warning(DeprecationWarning):
    pd.deprecated_function()

コードブロック内で特定の警告が発生しないようにする必要がある場合は、コンテキストマネージャーに False を渡します。

with tm.assert_produces_warning(False):
    pd.no_warning_function()

警告を発生させるテストがあるが、実際に警告自体をテストしていない場合(将来削除される予定である場合や、サードパーティライブラリの動作に合わせている場合など)、pytest.mark.filterwarnings を使用してエラーを無視します。

@pytest.mark.filterwarnings("ignore:msg:category")
def test_thing(self):
    pass

例外のテスト#

pytest.raises を、特定の例外サブクラス(つまり、Exception を使用しないでください)と、match 内の例外メッセージを指定したコンテキストマネージャーとして使用します。

with pytest.raises(ValueError, match="an error"):
    raise ValueError("an error")

ファイルを含むテスト#

tm.ensure_clean コンテキストマネージャーは、テスト用の一時ファイルを生成されたファイル名(または指定したファイル名)で作成し、コンテキストブロックが終了すると自動的に削除します。

with tm.ensure_clean('my_file_path') as path:
    # do something with the path

ネットワーク接続を含むテスト#

ユニットテストは、ネットワーク接続の不安定さや、接続先のサーバーの所有権がないため、インターネット経由でパブリックデータセットにアクセスすべきではありません。この相互作用をモックするには、pytest-localserver プラグインhttpserver フィクスチャを合成データと共に使用します。

@pytest.mark.network
@pytest.mark.single_cpu
def test_network(httpserver):
    httpserver.serve_content(content="content")
    result = pd.read_html(httpserver.url)

#

以下は、私たちが使用したい複数の機能を示す、ファイル pandas/tests/test_cool_feature.py 内の自己完結型のテストセットの例です。新しいテストには、GitHub の Issue 番号をコメントとして追加することを忘れないでください。

import pytest
import numpy as np
import pandas as pd


@pytest.mark.parametrize('dtype', ['int8', 'int16', 'int32', 'int64'])
def test_dtypes(dtype):
    assert str(np.dtype(dtype)) == dtype


@pytest.mark.parametrize(
    'dtype', ['float32', pytest.param('int16', marks=pytest.mark.skip),
              pytest.param('int32', marks=pytest.mark.xfail(
                  reason='to show how it works'))])
def test_mark(dtype):
    assert str(np.dtype(dtype)) == 'float32'


@pytest.fixture
def series():
    return pd.Series([1, 2, 3])


@pytest.fixture(params=['int8', 'int16', 'int32', 'int64'])
def dtype(request):
    return request.param


def test_series(series, dtype):
    # GH <issue_number>
    result = series.astype(dtype)
    assert result.dtype == dtype

    expected = pd.Series([1, 2, 3], dtype=dtype)
    tm.assert_series_equal(result, expected)

このテストを実行すると、次のようになります。

((pandas) bash-3.2$ pytest  test_cool_feature.py  -v
=========================== test session starts ===========================
platform darwin -- Python 3.6.2, pytest-3.6.0, py-1.4.31, pluggy-0.4.0
collected 11 items

tester.py::test_dtypes[int8] PASSED
tester.py::test_dtypes[int16] PASSED
tester.py::test_dtypes[int32] PASSED
tester.py::test_dtypes[int64] PASSED
tester.py::test_mark[float32] PASSED
tester.py::test_mark[int16] SKIPPED
tester.py::test_mark[int32] xfail
tester.py::test_series[int8] PASSED
tester.py::test_series[int16] PASSED
tester.py::test_series[int32] PASSED
tester.py::test_series[int64] PASSED

parametrize したテストには、テスト名でアクセスできるようになりました。たとえば、-k int8 を使用して、int8 に一致するテストのみをサブ選択して実行できます。

((pandas) bash-3.2$ pytest  test_cool_feature.py  -v -k int8
=========================== test session starts ===========================
platform darwin -- Python 3.6.2, pytest-3.6.0, py-1.4.31, pluggy-0.4.0
collected 11 items

test_cool_feature.py::test_dtypes[int8] PASSED
test_cool_feature.py::test_series[int8] PASSED

hypothesis の使用#

Hypothesis は、プロパティベースのテスト用のライブラリです。テストを明示的にパラメータ化する代わりに、すべての有効な入力を記述し、Hypothesis に失敗する入力を見つけさせることができます。さらに良いことに、Hypothesis は、試行するランダムな例の数に関係なく、アサーションに対する単一の最小反例を常に報告します。これは多くの場合、テストすることを思いつかなかった例です。

詳しい入門については、Hypothesis の使用開始を参照し、詳細については、Hypothesis のドキュメントを参照してください

import json
from hypothesis import given, strategies as st

any_json_value = st.deferred(lambda: st.one_of(
    st.none(), st.booleans(), st.floats(allow_nan=False), st.text(),
    st.lists(any_json_value), st.dictionaries(st.text(), any_json_value)
))


@given(value=any_json_value)
def test_json_roundtrip(value):
    result = json.loads(json.dumps(value))
    assert value == result

このテストは、Hypothesis のいくつかの便利な機能と、適切なユースケース(大規模または複雑な入力ドメインで保持されるはずのプロパティのチェック)を示しています。

pandas テストスイートを高速に実行し続けるには、入力またはロジックが単純な場合はパラメータ化されたテストが推奨され、Hypothesis テストは、ロジックが複雑な場合や、オプションの組み合わせが多すぎる場合、またはテスト(または考える!)するのに微妙な相互作用が多すぎる場合に予約されます。

テストスイートの実行#

pandasをインストールしなくても、Gitクローン内で直接テストを実行できます。以下のコマンドを入力してください。

pytest pandas

いくつかのテストがパスしない場合でも、pandasのインストールに問題があるとは限りません。一部のテスト(例えば、SQLAlchemy関連のテスト)は追加の設定が必要です。また、ピン留めされていないライブラリが新しいバージョンをリリースしたために失敗する可能性や、並列実行時に不安定になるテストもあります。ローカルでビルドしたバージョンからpandasをインポートできる限り、インストールは問題ないと考えられ、貢献を始めることができます。

多くの場合、テストスイート全体を実行する前に、変更箇所周辺のテストのサブセットのみを実行する価値があります(ヒント:pandas-coverageアプリを使用すると、変更したコード行をヒットするテストを特定し、それらのテストのみを実行できます)。

これを行う最も簡単な方法は、以下のコマンドを使うことです。

pytest pandas/path/to/test.py -k regex_matching_test_name

または、以下のいずれかの構文を使用します。

pytest pandas/tests/[test-module].py
pytest pandas/tests/[test-module].py::[TestClass]
pytest pandas/tests/[test-module].py::[TestClass]::[test_method]

‘pandas-dev’環境に含まれているpytest-xdistを使用すると、マルチコアマシンでのローカルテストを高速化できます。pytestを実行する際に-nフラグにコア数を指定するか、autoを指定すると、マシン上のすべての利用可能なコアを活用してテストを並列化できます。

# Utilize 4 cores
pytest -n 4 pandas

# Utilizes all available cores
pytest -n auto pandas

さらにテストを高速化したい場合は、次のようなより高度なコマンドを使用できます。

pytest pandas -n 4 -m "not slow and not network and not db and not single_cpu" -r sxX

マルチスレッドによるパフォーマンス向上に加えて、-mマークフラグを使用して一部のテストをスキップすることで、テスト速度を向上させることができます。

  • slow:実行に時間がかかるテスト(ミリ秒ではなく秒単位)

  • network:ネットワーク接続が必要なテスト

  • db:データベース(MySQLまたはPostgreSQL)が必要なテスト

  • single_cpu:シングルCPUでのみ実行する必要があるテスト

関連性がある場合は、以下のオプションを有効にすることができます。

  • arm_slow:arm64アーキテクチャで実行に時間がかかるテスト

これらのマーカーは、このtomlファイル[tool.pytest.ini_options]の下にあるmarkersというリストで定義されています。興味のある新しいマーカーが作成されていないか確認したい場合は、このファイルを参照してください。

-rレポートフラグは、簡単なサマリー情報を表示します(詳細はpytestドキュメントを参照)。ここでは、以下の数を表示します。

  • s:スキップされたテスト

  • x:xfailしたテスト

  • X:xpassしたテスト

サマリーはオプションであり、追加の情報が必要ない場合は削除できます。並列化オプションを使用すると、プルリクエストを送信する前にローカルでテストを実行する時間を大幅に短縮できます。

過去に発生したように、結果についてサポートが必要な場合は、コマンドを実行する前にシードを設定してバグレポートを送信してください。そうすることで、再現することができます。以下は、Windowsでシードを設定する例です。

set PYTHONHASHSEED=314159265
pytest pandas -n 4 -m "not slow and not network and not db and not single_cpu" -r sxX

Unixでは、以下を使用します。

export PYTHONHASHSEED=314159265
pytest pandas -n 4 -m "not slow and not network and not db and not single_cpu" -r sxX

詳細は、pytestのドキュメントを参照してください。

さらに、以下のコマンドを実行できます。

pd.test()

インポートされたpandasを使用して同様にテストを実行します。

パフォーマンステストスイートの実行#

パフォーマンスは重要であり、コードがパフォーマンスの低下を引き起こしていないか検討する価値があります。pandasは、重要なpandas操作のパフォーマンスを簡単に監視できるように、asvベンチマークへの移行を進めています。これらのベンチマークはすべてpandas/asv_benchディレクトリにあり、テスト結果はこちらで確認できます。

asvのすべての機能を使用するには、condaまたはvirtualenvのいずれかが必要です。詳細については、asvのインストールに関するWebページを確認してください。

asvをインストールするには、以下を実行します。

pip install git+https://github.com/airspeed-velocity/asv

ベンチマークを実行する必要がある場合は、ディレクトリをasv_bench/に変更して、以下を実行します。

asv continuous -f 1.1 upstream/main HEAD

HEADを作業中のブランチの名前に置き換え、10%以上変化したベンチマークを報告できます。このコマンドは、ベンチマーク環境を作成するためにデフォルトでcondaを使用します。代わりにvirtualenvを使用する場合は、以下のように記述します。

asv continuous -f 1.1 -E virtualenv upstream/main HEAD

-E virtualenvオプションは、ベンチマークを実行するすべてのasvコマンドに追加する必要があります。デフォルト値はasv.conf.jsonで定義されています。

フルベンチマークスイートの実行は、ハードウェアとそのリソース使用率によっては、一日がかりのプロセスになる可能性があります。ただし、通常は、コミットされた変更が予期しないパフォーマンスの低下を引き起こさないことを示すために、結果のサブセットのみをプルリクエストに貼り付けるだけで十分です。-bフラグ(正規表現を取る)を使用して、特定のベンチマークを実行できます。例えば、これはpandas/asv_bench/benchmarks/groupby.pyファイルからのみベンチマークを実行します。

asv continuous -f 1.1 upstream/main HEAD -b ^groupby

ファイルから特定のベンチマークグループのみを実行したい場合は、区切り文字として.を使用できます。例えば

asv continuous -f 1.1 upstream/main HEAD -b groupby.GroupByMethods

は、groupby.pyで定義されたGroupByMethodsベンチマークのみを実行します。

現在のPython環境にすでにインストールされているpandasのバージョンを使用して、ベンチマークスイートを実行することもできます。これは、virtualenvまたはcondaがない場合、または上記で説明したsetup.py developアプローチを使用している場合に役立ちます。インプレースビルドでは、PYTHONPATHを設定する必要があります。例えば、PYTHONPATH="$PWD/.." asv [残りの引数]のようにします。既存のPython環境を使用してベンチマークを実行するには、以下のようにします。

asv run -e -E existing

または、特定のPythonインタープリターを使用するには、以下のようにします。

asv run -e -E existing:python3.6

これにより、ベンチマークからのstderrが表示され、$PATHから取得したローカルのpythonが使用されます。

ベンチマークの書き方とasvの使用方法に関する情報は、asvドキュメントにあります。

コードのドキュメント化#

変更は、doc/source/whatsnew/vx.y.z.rstにあるリリースノートに反映させる必要があります。このファイルには、各リリースの継続的な変更ログが含まれています。修正、機能強化、または(避けられない)破壊的な変更を記録するために、このファイルにエントリを追加してください。エントリを追加する際には、GitHubのissue番号を必ず含めてください(:issue:`1234`を使用します。1234はissue/プルリクエスト番号です)。エントリは完全な文章と正しい文法を使用して記述する必要があります。

APIの一部に言及する場合は、必要に応じてSphinxの:func::meth:、または:class:ディレクティブを使用してください。すべてのパブリックAPI関数とメソッドにドキュメントページがあるわけではありません。理想的には、リンクが解決する場合にのみリンクを追加する必要があります。通常、以前のバージョンのいずれかのリリースノートを確認することで、同様の例を見つけることができます。

コードがバグ修正である場合は、関連するバグ修正セクションにエントリを追加してください。Otherセクションへの追加は避けてください。まれにしかエントリはそこに入れるべきではありません。できるだけ簡潔に、バグの説明には、ユーザーがどのように遭遇する可能性があるか、およびバグ自体の兆候(例えば、「誤った結果を生成する」または「誤って例外を発生させる」)を含める必要があります。新しい動作を示す必要もあるかもしれません。

コードが機能強化である場合は、既存のドキュメントに使用例を追加する必要がある可能性が最も高いです。これは、ドキュメントに関するセクションに従って行うことができます。さらに、この機能が追加された時期をユーザーに知らせるために、versionaddedディレクティブが使用されます。そのsphinxの構文は次のとおりです。

.. versionadded:: 2.1.0

これにより、Sphinxディレクティブを配置した場所に「バージョン2.1.0で新規」というテキストが表示されます。これは、新しい関数またはメソッド()または新しいキーワード引数()を追加するときにも、docstringに含める必要があります。