ユニットテスト

|Gromacs|におけるユニットテストの主な目的は、開発者がコードを開発する際に役立つことです。 ユニットテストは、特定のモジュールまたは関連モジュルのグループの機能をテストすることに焦点を当てています。 ユニットテストは、変更後すぐに簡単に実行できるように設計されており、これにより、何も壊れていないことを確認できます。

検索、構築、実行

ソースコードの構造 で説明されているように、src/gromacs/ ディレクトリは、各サブディレクトリに対応するモジュールに分割されています。 利用可能な場合は、そのモジュールのユニットテストは、上位レベルのモジュールディレクトリにある tests/ サブディレクトリにあります。 通常、モジュール内の file.h に記述されたコードのテストは、対応する tests/file.cpp にあります。 すべてのファイルにテストが用意されているわけではなく、個々のファイルを独立してテストすることは必ずしも意味がない場合があります。 テストの主な目的は、モジュール外で公開されている機能の検証です。 特に、より上位のモジュール向けのテストは、統合テストに近いもので、複数のモジュールの機能をテストします。 テストの実装に使用される共有コードは、src/external/googletest/ および src/testutils/ (下記参照) にあります。

テストは、CMakeで BUILD_TESTING=ON (デフォルト) および GMX_BUILD_UNITTESTS=ON (デフォルト) が設定されている場合にビルドされます。 各モジュールは、 bin/ 以下の少なくとも1つの個別のユニットテストバイナリ(module-test)を生成し、それを使用してそのモジュールのテストを実行できます。

テストは、いくつかの異なる方法で実行できます:

  • 「test」ターゲットをビルドします(例:make test):これは、CTestを使用してすべてのテストを実行します。 CMakeにテストの場所が指定されている場合、リグレッションテストも実行されます(このページではリグレッションテストの詳細については説明していません)。テストが失敗した場合、基本的な概要情報のみが表示されます(各テストバイナリまたはリグレッションテストクラスのパス/失敗ステータスのみ)。個々の失敗したテストバイナリを実行して、詳細な情報を取得できます。`make test`は、ソースコードを変更した場合にテストバイナリを再ビルドしないため、個別に`make`または`make tests`を実行する必要があります。後者はテストバイナリとその依存関係のみをビルドします。

  • 「check」ターゲットをビルドします(例:make check):これは「test」ターゲットと同じ動作ですが、いくつかの拡張機能があります。

    1. テスト実行前に、古いバージョンのテストバイナリが検出された場合、それらは再構築されます。

    2. テストが失敗した場合、テストバイナリの出力が表示されます。

    3. ユニットテストおよび/または回帰テストが利用できない場合、メッセージが表示されます。

  • make check の実行は、ctest 実行可能ファイルを通じて CTest を呼び出し、すべての個別のテストバイナリを実行します。 これにより、テスト名やラベルによるフィルタリング、または詳細レベルの調整など、より詳細な制御が可能になります。

  • 直接テストバイナリを実行する。これにより、障害の原因を特定するための最も有益な情報が得られ、テストの失敗をデバッグできます。出力は、失敗した個々のテスト(またはテスト)を特定し、すべての失敗したアサーションの結果を表示します。一部のテストでは、失敗したアサーションにさらに情報を追加することで、原因を特定しやすくしています。一部のテストは、検出されたMPIのプロセス数またはGPUデバイスの数で実行できないため、スキップされます。このようなケースに関する詳細な情報は、「-echo-reasons」フラグをテストバイナリに渡すことで取得できます。コマンドラインオプションを使用して、実行するテストを制御できます。詳細については、「--help」オプションでバイナリを実行してください。

CTest を実行すると、テストは Testing/Temporary/ に XML 形式の出力を生成し、各テストの結果とエラーメッセージを含めます。 この XML は、GitLab CI によって個々のテストのステータスを報告するために使用されます。 ただし、テストがアサーションや gmx_fatal() の呼び出しによってクラッシュまたは失敗した場合、バイナリに対して XML は生成されず、CI はテストバイナリのステータスを報告しません。 実際のエラーは、コンソール出力にのみ表示されます。

ユニットテストフレームワーク

これらのテストは、Google Test を使用して作成されており、これはユニットテストの作成と、それらをテストバイナリにコンパイルするためのフレームワークを提供します。 テストバイナリで提供されるほとんどのコマンドラインオプションは、Google Test によって実装されています。 Google Test Primer を参照してください。 Google Test は、ソースツリーの src/external/googletest/ に含まれており、ユニットテストのビルドの一部としてコンパイルされます。

src/testutils/ には、GROMACS-固有の共有テストコードが含まれています。これには、以下のようなものが含まれます:

  • CMake マクロを使用してテストバイナリを宣言します。これらのマクロは、テスト実行可能ファイルに main() メソッドを提供し、およびフレームワークの他の部分を初期化することで、モジュールのテストコードが実際のテストに集中できるようにします。テストを記述するために必要なフレームワークの唯一の部分は、gmx_add_unit_test() を CMake で使用してテストバイナリを作成し、すぐに実際のテストを記述できることです。使用方法の例については、src/testutils/TestMacros.cmake および既存の CMake コードを参照してください。

  • 汎用的なテスト用設定とヘルパークラス。C++ APIに関するドキュメントは、`テストユーティリティのDoxygenページ`__にあります。この機能には、ソースディレクトリからテスト入力ファイルを検索し、一時ファイルを構築すること、テストバイナリにカスタムのコマンドラインオプションを追加すること、例外と浮動小数点数の処理を改善するためのカスタムテストアサーション、コマンドライン引数の配列を構築するためのユーティリティ、および、長文字列の正確性をテストしたり、`stdin`の読み取りなどのレガシーコードを実行する必要があるテスト用のテスト設定が含まれます。

  • 上記の機能をサポートするための、いくつかのクラスと関数。このコードは、CMakeの内部で使用され、テストバイナリの作成と設定、およびGoogle Testを当社の環境に合わせてカスタマイズするために使用されます。

  • シンプルなフレームワークで、同じテストコードによって生成された参照データと結果を比較するテストを構築できます。C/C++コードのみでコードの結果を検証することが難しい場合に、手動での結果の確認が可能な場合に役立ちます。一般的なアプローチは、「参照データの使用に関するDoxygenページ」__に記載されています。

「src/testutils/」に加えて、一部のモジュールのテストディレクトリには、より上位のテストで再利用されるテストコードが含まれている場合があります。たとえば、「src/gromacs/analysisdata/tests/」には、gmx::IAnalysisDataModule のモック実装と、src/gromacs/trajectoryanalysis/tests/ でも使用されているヘルパークラスを含むテスト用設定が含まれています。これらのケースは、必要なすべてのテストバイナリにリンクされる CMake オブジェクトライブラリを使用して処理されます。

新しいテストの開始

新しいテストを開始するには、まず Google Test のドキュメントを読み、テストフレームワークの基本的な理解を得る必要があります。また、上記の記述を読み、GROMACS でテストがどのように構成されているかを理解する必要があります。すべての詳細を理解する必要はありませんが、全体的な理解を得ることで、スムーズに開始できます。

基本的なテストの作成は簡単で、既存のテストを参考にすることもできます。既存のテストにはさまざまなレベルの複雑さがあるため、特定の機能を使用するテストを見つけるためのヒントを以下に示します。

  • src/gromacs/utility/tests/stringutil.cpp には、関数のための非常にシンプルなテストが含まれています。これらのテストは、Google Test の基本的なアサーションのみを使用し、高度な機能は一切使用しません。これらのテストを実行するには、 TEST() マクロとそれに続くブロック、およびコンパイルに必要なヘッダーファイルが必要です。

  • 同じファイルには、参照フレームを使用して行の折り返しをチェックするための簡単なテストも含まれています(gmx::TextLineWrapper のテスト)。これらのテスト用のテスト環境は、src/testutils/include/testutils/stringtest.h/.cpp にあります。この文字列テスト環境は、テスト実行に影響を与えるように、テストバイナリにカスタムコマンドラインオプションを追加する方法も示しています。

  • ``src/gromacs/selection/tests/」には、参照フレームのより複雑な使用例が含まれています。これは、参照フレームが最初に作成されたコードです。「src/gromacs/selection/tests/selectioncollection.cpp」が主要なファイルです。

  • より複雑なテストで、参照フレームを使用せず、代わりにコード内でより複雑な検証を行う場合は、「src/gromacs/selection/tests/nbsearch.cpp」を参照してください。

  • 複雑なテスト(モッククラスと参照フレームを使用)の場合、「src/gromacs/analysisdata/tests/」ディレクトリを参照してください。

ユニットテストを使用する際に考慮すべき点:

  • テストの実行時間をできるだけ短く保ちつつ、テスト対象のコードにおける最も重要なパスを網羅するように努めてください。一般的に、テストは数秒で実行されるべきで、変更を行った後にテストを実行する際に、誰も躊躇しないようにします。長時間実行されるテストは、ユニットテストセットとは別の場所に配置してください。CIは、複数のビルド構成でテストを実行するため、長時間実行されるテストは、パイプラインを大幅に遅らせ、タイムアウトを引き起こす可能性があります。

  • テストの検証が失敗した場合に、役立つメッセージを生成するように努めてください。検証メッセージは、何が間違っているかを明確に示し、テスト自体をデバッガ(例えば、検証がループ内にある場合で、ループインデックスが検証が失敗する理由を理解するのに役立つ場合)で実行する必要はありません。さらに、ユーザーが問題を理解できるようにすることも理想的ですが、主な対象者はテストが失敗した開発者です。

MPI テスト

もし、テストが特定のMPIのランキング数や、実装の一部として通信子を必要とする場合、通常のGoogleTestのように動作するように、GROMACS-固有の拡張機能を使用できます。 GMX_TEST_MPI(RankRequirement) を使用してテストを定義し、gmx_add_mpi_unit_test を使用して、マルチスレッドとMPIの両方でビルドした場合でも、CTestがテストを実行できるように指示します。詳細は src/testutils/include/mpitest.h を参照してください。