Catch2を使用したC++の単体テスト 🧪👨‍🔬

私の静的サイトジェネレーター - palpatineの単体テストを書くことで、Catch2という全く新しいテストフレームワークを学ぶことができました。今年のHacktoberfestのプルリクエストでは、他のリポジトリへの単体テストへの貢献を行いましたし、現在はクラウドコンピューティングの授業で必要とされる、JavaScriptの単体テスト用のJestを使ってマイクロサービスを構築中です。だから、単体テストに関してある程度の経験があり、そのスキルを伸ばし続けています。

しっかりとした単体テストスキルを身につけるには時間がかかります。なぜなら、より深くコードを考える必要があるからです。入力はどのようなものか?期待される出力は?エッジケースは? 普段の個人プロジェクトではテストにそれほど注意を払うことはありませんでしたが、実際の開発では重要だとわかっています。

コードを信頼する 🌱

名前が示す通り、単体テストはコードやアプリケーションのコンポーネントの小さな単位をテストし、意図したとおりに動作していることを確かめるものです。アプリケーションは互いに調和して機能する構成要素を持っています。これらの構成要素や単位をテストし、正しい動作をしていることを確認することで、コードを信頼することができます!

もしテストが難しいコードがあったら? それはおそらく分割する必要があるということです。これはまた、単体テストがモジュール性を促進していることを意味しています。

もしコードの単位が別の単位を依存している場合、依存している単位が適切に使われていることも確かめる必要があります。

なぜCatch2を選ぶのか?

C++にはGoogle Test、Boost.Test、Cute、CppUnitなど多くの確立されたテストフレームワークがありますが、Catch2を他と区別するのは、使いやすさと学習曲線の単純さです。それだけでなく、Catch2にはフォローしやすい素晴らしいドキュメントとチュートリアルがあります。また、夏にはCMakeに関するUdemyコースを受講し、そこでCatch2を使ったテストのテンプレートを見つけました。そのテンプレートは、始めるための設定をする際にとても役立ちました。

テストを書く初心者として、経験豊富なプログラマーが言うほどCatch2を学ぶのは簡単ではありませんでした。ドキュメントを数日間読み返し、YouTubeの動画を見たり、オンラインでたくさんの例を見た後でようやく最初のテストを書くことができました。でも、一度書き始めると、スムーズに進むようになりました。

Catch2のドキュメントによれば:

Catch2の主な利点は、使い方が単純で自然であることです。テスト名は有効な識別子である必要はなく、アサーションは通常のC++の論理表現のように見え、セクションはテスト内でセットアップとティアダウンコードを共有するための良いローカルな方法を提供します。

セットアップする 🛸

はじめにcatch2.hppファイルをダウンロードして、それをインクルードしました。

#include <catch2/catch_test_macros.hpp>

そして、これを最初に定義する必要がありました。

#define CATCH_CONFIG_MAIN // これはCatchにmain()を提供してもらうためのものです
                           // これはcppファイルの一つだけで行ってください

TEST_CASE マクロはテスト条件を導入するために使用されます。これは特定のコードの単位に対するテストケースをグループ化するために使います。

SECTIONは、彼らのドキュメントでうまく定義されています:

セクションを見るもう一つの方法は、テストを通じて木のようなパスを定義するものと考えることです。各セクションはノードを表し、最終的な木は深さ優先で歩かれ、各パスは一つの葉ノードのみを訪れます。

TEST_CASE("CLI Parser works perfectly", "[parser]") {
  std::vector<std::string> vct{"./palpatine"};

  SECTION("Without any args") {
    REQUIRE_THROWS_AS(get_options(vct), std::runtime_error); 
  }
}

私が受講したCMakeコースでは、私のテスト目的に合わせて変更し使用できるテンプレートを提供してくれました。CMakeLists.txtファイルを確認すると以下のようになっています。

if(ENABLE_TESTING)
  set(TEST_MAIN "unit_tests")
  set(TEST_SOURCES "main.cpp")

  SET(GCC_COVERAGE_COMPILE_FLAGS "-fprofile-arcs -ftest-coverage")
  SET(GCC_COVERAGE_LINK_FLAGS "--coverage")

  SET( CMAKE_CXX_FLAGS "${CMAKE_CXX_FLAGS} ${GCC_COVERAGE_COMPILE_FLAGS}" )
  SET( CMAKE_EXE_LINKER_FLAGS "${CMAKE_EXE_LINKER_FLAGS} ${GCC_COVERAGE_LINK_FLAGS}" )

  add_executable(${TEST_MAIN} ${TEST_SOURCES})
  target_link_libraries(${TEST_MAIN} PUBLIC
    ${PALPATINE_LIB}
    argparse
    Catch2::Catch2WithMain 
  )
  add_test(NAME ${TEST_MAIN} COMMAND ${TEST_MAIN})
endif(ENABLE_TESTING)

テストカバレッジ 💯

私のプロジェクトにはテストのカバレッジを示すプロバイダーがあり、テストでカバーされている行とカバレッジのパーセンテージを見ることができます。私のテストカバレッジプロバイダーに、gcovrを選びました。セットアップに関する明確なドキュメントがあり、トラブルなく行うことができました。

彼らのドキュメントより。

GcovrはGNU gcovユーティリティの使用を管理し、要約されたコードカバレッジ結果を生成するユーティリティを提供します。

テストカバレッジを実行するコマンドは以下の通りです:

gcovr

下の画像で見ることができるように、私のテストでカバーされている行を確認できます!

画像説明

カバレッジをカラフルに見たい場合は、lolcatを使ってパイプ処理すると良いです 😃

画像説明

最終考察 ☕︎

C++でテストを書くのは、私が取り組んできた他のプロジェクトよりも難しかったです。コードベースが進化するにつれて、テストも時間と共に更新される必要があります。

こちらの記事はdev.toの良い記事を日本人向けに翻訳しています。
https://dev.to/batunpc/c-unit-testing-with-catch2-20af