サブロウ丸

Sabrou-mal サブロウ丸

主にプログラミングと数学

master mind by c++; part 4 googletest

今回やること

単体テストgoogletestの導入

googletestとは

Googleが提供するC++用の単体テストフレームワーク
参考: Google Test の使い方 - ゼロから学ぶ C++

python版のpytestと提供される機能はほとんど同じですね.

googletestの導入

現状のディレクトリ構成

GitHub - nariaki3551/master_mind_cpp at feature/action_cmake

master_mind_cpp $ tree ./
./
├── README.md
├── CMakeLists.txt
└── src
    ├── CMakeLists.txt
    ├── def.h
    ├── main.cpp
    ├── ...
    └── utils.h

googletestのインストール

githubからcloneします.

$ git clone https://github.com/google/googletest
$ cd googletest
$ mkdir build; cd build; cmake ..; make; make install

ubuntuの場合は

$ apt install libgtest-dev

でもok.

cmakeを用いる場合, 事前にインストールしていなくても

include(FetchContent)
FetchContent_Declare(
  googletest
  URL https://github.com/google/googletest/archive/refs/tags/release-1.11.0.zip
)
set(gtest_force_shared_crt ON CACHE BOOL "" FORCE)
FetchContent_MakeAvailable(googletest)

にてコンパイル時に自動でインストールしてくれる. 便利. ( Quickstart: Building with CMake | GoogleTest )

テストプログラム

utils.hの中のcountHitBlow関数のテストを作成したいと思います. 2つのコードが与えられたときに, そのコードの組みのヒント(hit, blow)を返す関数です.

test/test.cppを作成し, そこに下記のようにテストコードを書きます. ASSERT_EQ で, 関数の返り値と期待する値が等しいかどうかを3通りの入力で確かめています.

#include <gtest/gtest.h>
#include "utils.h"

TEST ( countHitBlowTest, countHitBlowTest1 )
{
   Code codeA{ 0, 0 };
   Code codeB{ 0, 0 };
   ASSERT_EQ( countHitBlow(codeA, codeB), HitBlow(2, 0) );
}

TEST ( countHitBlowTest, countHitBlowTest2 )
{
   Code codeA{ 0, 0 };
   Code codeB{ 0, 1 };
   ASSERT_EQ( countHitBlow(codeA, codeB), HitBlow(1, 0) );
}

TEST ( countHitBlowTest, countHitBlowTest3 )
{
   Code codeA{ 0, 0 };
   Code codeB{ 1, 1 };
   ASSERT_EQ( countHitBlow(codeA, codeB), HitBlow(0, 0) );
}

ここ 入門ガイド — Google Test ドキュメント日本語訳 を見れば, 他にどのようなテスト用の比較関数があるか確認できます.

ビルド

googletestをインストールしている場合

コマンドラインでビルドする際は, -lgtest, -lgtest_main をリンクさせます.

$ g++ ./test/test.cpp -std=c++17 -I./src -lgtest -lgtest_main

cmakeであれば

add_executable(
  utest
  test.cpp
  )

target_include_directories(
  utest PRIVATE
  ${PROJECT_SOURCE_DIR}/src
  )

target_compile_features(
  utest PRIVATE
  cxx_std_17
  )

target_link_libraries(
  utest PRIVATE
  gtest  # 追加!!
  gtest_main  # 追加!!
  pthread  # 追加!!
  )

と, 編集してルートディレクトリのCmakeLists.txtを

cmake_minimum_required(VERSION 3.1)
project(MasterMind CXX)

# setting of CMAKE_RUNTIME_OUTPUT_DIRECTORY
if(NOT CMAKE_RUNTIME_OUTPUT_DIRECTORY)
  set(CMAKE_RUNTIME_OUTPUT_DIRECTORY ${PROJECT_SOURCE_DIR}/bin)
endif()

add_subdirectory(src)
add_subdirectory(utest)  # 追加!!

としてやれば, あとは

$ mkdir build; cd build; cmake ..; make

コンパイルされます. バイナリは ./bin/test に作成されてあります.

作業後のディレクトリ構成

.
├── CMakeLists.txt
├── README.md
├── src
│   ├── CMakeLists.txt
│   ├── def.h
│   ├── main.cpp
│   ├── ...
│   └── utils.h
└── test  # 追加!!
    ├── CMakeLists.txt   # 追加!!
    └── test.cpp   # 追加!!

googletestをインストールしていない場合

find_package(GTest)  # googletest packageを検索
if(NOT GTest_FOUND)  # 無ければFetchContent_Declearを用いてダウンロード
  include(FetchContent)
  FetchContent_Declare(
    googletest
    URL https://github.com/google/googletest/archive/refs/tags/release-1.11.0.zip
  )
  set(gtest_force_shared_crt ON CACHE BOOL "" FORCE)
  FetchContent_MakeAvailable(googletest)
endif()

add_executable(
  utest
  test.cpp
  )

 ..(以下略)..

でも入ります. 便利. ( Quickstart: Building with CMake | GoogleTest )

実行

Running main() from /xxx/master_mind_cpp_dev/source/googletest/googletest/src/gtest_main.cc
[==========] Running 3 tests from 1 test suite.
[----------] Global test environment set-up.
[----------] 3 tests from countHitBlowTest
[ RUN      ] countHitBlowTest.countHitBlowTest1
[       OK ] countHitBlowTest.countHitBlowTest1 (0 ms)
[ RUN      ] countHitBlowTest.countHitBlowTest2
[       OK ] countHitBlowTest.countHitBlowTest2 (0 ms)
[ RUN      ] countHitBlowTest.countHitBlowTest3
[       OK ] countHitBlowTest.countHitBlowTest3 (0 ms)
[----------] 3 tests from countHitBlowTest (0 ms total)

[----------] Global test environment tear-down
[==========] 3 tests from 1 test suite ran. (0 ms total)
[  PASSED  ] 3 tests.

動いていますね.

ctest

ついでにctestも設定します. CMakeLists.txtにenable_testing()以下を追加します.

cmake_minimum_required(VERSION 3.1)
project(MasterMind CXX)

# ( 中略 )

add_subdirectory(src)
add_subdirectory(utest)

# test   # 追加!!
enable_testing()
add_test(
  NAME utest
  COMMAND utest
  )

これにより,

$ mkdir build; cd build; cmake ..; make

bash後に

$ ctest

でgoogletestが動きます.

Test project /xxx/master_mind_cpp/build
    Start 1: ugtest
1/1 Test #1: ugtest ...........................   Passed    0.00 sec

100% tests passed, 0 tests failed out of 1

Total Test time (real) =   0.01 sec

GitHub action の修正

最後に github actionを修正します. gtestがないとエラーが起きています.

Run cmake --build /home/runner/work/master_mind_cpp/master_mind_cpp/build --config Release
( 中略 )
10
/home/runner/work/master_mind_cpp/master_mind_cpp/test/test.cpp:1:10: fatal error: gtest/gtest.h: No such file or directory
11
    1 | #include <gtest/gtest.h>
12
      |          ^~~~~~~~~~~~~~~
13
compilation terminated.
( 中略 )
17
Error: Process completed with exit code 2.

修正.. introduce gtest by nariaki3551 · Pull Request #6 · nariaki3551/master_mind_cpp · GitHub

次はpthreadに関するエラー.

( 中略 )
10
[100%] Linking CXX executable ../../bin/utest
11
/usr/bin/ld: /usr/lib/gcc/x86_64-linux-gnu/9/../../../x86_64-linux-gnu/libgtest.a(gtest-all.cc.o): in function `testing::internal::ThreadLocal<std::vector<testing::internal::TraceInfo, std::allocator<testing::internal::TraceInfo> > >::~ThreadLocal()':
12
( 中略 )
44
./obj-x86_64-linux-gnu/googletest/./googletest/include/gtest/internal/gtest-port.h:1787: undefined reference to `pthread_getspecific'
45
/usr/bin/ld: ./obj-x86_64-linux-gnu/googletest/./googletest/include/gtest/internal/gtest-port.h:1794: undefined reference to `pthread_setspecific'
46
collect2: error: ld returned 1 exit status
( 中略 )
50
Error: Process completed with exit code 2.

修正.. introduce gtest by nariaki3551 · Pull Request #6 · nariaki3551/master_mind_cpp · GitHub

これでclearになりました.

まとめ

googletestを追加して, cmakeが動くように, それに合わせてCMakeLists.txtを変更しました.

コード

参考

他の記事