最近趣味でもC++を本格的に始めたのでテスト環境としてGoogle Testを導入したのでメモ書き。
Google Testとは
Google's C++ test frameworkが正式名称なのかな?
Googleが開発、公開しているC++用単体テストフレームワーク。
ASSERTを組み込んだテストコードを自動で走らせてくれるスグレモノ。
そもそも単体テストとは?
最小に近い単位の機能の動作を検証するもの。
いわゆるV字モデルの詳細設計に対応するテスト工程になる。
https://ja.wikipedia.org/wiki/V%E3%83%A2%E3%83%87%E3%83%AB
やれドメイン駆動開発だのユースケース駆動開発だの、それらを持ち込んだクリーンアーキテクチャだの言われますが、つまるところはオブジェクト指向の根本となるカプセル化をちゃんとやれということで、カプセル化が正しくなされていれば関数やクラスは機能単位で分解できるはず。そうすると単体テストもしやすくなってコードの質も上がっていく、という考え。
逆から見て行うべきテストを先に書いて、それにあわせて実装していくというテスト駆動開発というのもありますね。
私は苦手ですが。
使用方法
Win10Home+cygwin+g++(gcc)での導入。
ダウンロード & ビルド
Githubのリポジトリからソースを引っ張る。
ブラウザでダウンロードしてきて解凍してもかまわない。
2019/10/15現在は1.10.0が最新。以降Verは適当に置き換えること。
wget https://github.com/google/googletest/archive/release-1.10.0.tar.gz
tar -xvf release-1.10.0.tar.gz
googletest-release-1.10.0のディレクトリで解凍されるので内部へ移動しビルドを行う。
ビルドされたいファイルが多数生成されてごちゃごちゃしてしまうのでビルド用ディレクトリを作成してその中でcmakeを走らせる。
cd googletest-release-1.10.0
mkdir build-tmp
cd build-tmp
cmake ..
make
この時点で以下のようなディレクトリになっているはず。
(細かいのは省略)
googletest-release-1.10.0/
├── build-tmp
│ ├── bin
│ ├── CMakeFiles
│ ├── googlemock
│ ├── googletest
│ ├── lib
│ │ ├── libgmock.a
│ │ ├── libgmock_main.a
│ │ ├── libgtest.a
│ │ └── libgtest_main.a
│ ├── cmake_install.cmake
│ └── Makefile
├── ci
├── googlemock
├── googletest
│ ├── cmake
│ ├── include
│ │ ├── gtest
│ ├── src
│ └── LICENSE
├── CMakeList.txt
├── LICENSE
この中から使用するのは./build-tmp/lib/
内の2つのgtestライブラリファイルと./googletest/include/gtest
ディレクトリまるっと。
テストの走らせ方
ここでは以下のようなプロジェクトディレクトリ構成を扱う。
プロジェクトルート直下にgoogletest
をディレクトリを作成し、ライブラリファイル2つとgoogletest-release-1.10.0/googletest/include/gtest
を持ってくる。
./
├── bin
├── googletest
│ ├── include
│ │ └── gtest
│ │ ├── gtest.h
│ │ └── ...
│ ├── libgtest.a
│ └── libgtest_main.a
├── src
│ └── my_func.cpp
├── test
│ └── test_my_func.cpp
└── makefile
とりあえずテスト対象の関数。
# include "my_func.hpp"
int MyFunc() {
return 10;
}
# ifndef MYFUNC_HPP
# define MYFUNC_HPP
int MyFunc();
# endif
テストコードはtest
ディレクトリに追加していく。
# include "gtest/gtest.h"
# include "my_func.hpp"
TEST(MyTest, MyFunctionTest) {
int response = MyFunc();
EXPECT_EQ(response, 10);
}
テストコードではgtest/gtest.h
をインクルードする。
テストケースはTESTマクロで記述する。TESTマクロの第1引数はテストケース名、第2引数にテスト名を指定する。もちろんテストケース内でテスト名が被ると動作しない(はず)なので注意。
実際のテスト判定にはアサーションマクロを定義する。
アサーションにはASSERT
系とEXPECT
系が用意されている。
ASSERT
系ではひっかかったときに、その時点でテストをそこで終了させる。これに対しEXPECT
系ではテストは全て実行される。ひっかかるとその後がすべて無為になるような場面ではASSERT
を使い、基本的にはEXPECT
系を使えばいいと思う。
ASSERT
/EXPECT
どちらでもいくつかの種類が用意されている。
ASSERT | EXPECT | |
---|---|---|
TrueでOK | ASSERT_TRUE | EXPECT_TRUE |
FalseでOK | ASSERT_FALSE | EXPECT_FALSE |
== でOK | ASEERT_EQ | EXPECT_EQ |
>= でOK | ASSERT_GE | EXPECT_GE |
その他のアサーションマクロは公式ドキュメントや日本語訳を参照すべし。
これらをコンパイルする必要があるがいくつかハマった点があったので、それを含め注意点をメモ。
-
C++ Verの指定オプションではc++ではなくgnu++を用いる
googletest内部で用いられる関数などがANSI C++標準ではなくGNU拡張が含まれたものになっている。
したがってコンパイルオプションで-std=c++11
とするところを-std=gnu++11
とする必要がある。
もしかするとgoogletestのmake時にオプションを定義すれば不要になるかもしれない。 -
gtestまわりをリンクさせる
.aライブラリファイル2つと同時にいくつかコンパイル時にリンクさせる必要がある。また-pthread
も必要。
これらを踏まえたmakefileが以下。
汎用的ではなくこのテストに限定したものなので、実際にはdebugビルドやreleaseビルドなどのルールを追加したmakefileにする。
CXX = g++
CXXFLAGS = -std=gnu++11
TARGET = Sample.exe
TARGETDIR = ./bin
INCLUDES = -I. -Isrc -Igoogletest/include
SRCDIR = ./src
SRCS = $(SRCDIR)/my_func.cpp
OBJS := $(SRCS:.cpp=.o)
TESTDIR = ./test
TESTSRCS = $(TESTDIR)/test_my_func.cpp
TESTOBJS := $(TESTSRCS:.cpp=.o)
GTESTLIB += ./googletest/libgtest.a ./googletest/libgtest_main.a
GTESTFLAGS = -Lgoogletest -lgtest -lgtest_main -lpthread
.PHONY: all clean
.SUFFIXES: .c .cpp .o
all: $(TARGETDIR)/$(TARGET)
$(TARGETDIR)/$(TARGET): $(OBJS) $(TESTOBJS)
$(CXX) $(GTESTFLAGS) -o $@ $^ $(GTESTLIB)
.cpp.o:
$(CXX) $(INCLUDES) $(CXXFLAGS) -o $@ -c $^
.cpp:
$(CXX) $(INCLUDES) $(CXXFLAGS) -o $@ $^
clean:
rm $(OBJS) $(TESTOBJS)
ビルドを走らせれば./bin/Sample.exe
が生成されるので実行すれば
$ ./bin/Sample.exe
Running main() from /home/usr/googletest-release-1.10.0/googletest/src/gtest_main.cc
[==========] Running 1 test from 1 test suite.
[----------] Global test environment set-up.
[----------] 1 test from MyTest
[ RUN ] MyTest.MyFunctionTest
[ OK ] MyTest.MyFunctionTest (0 ms)
[----------] 1 test from MyTest (0 ms total)
[----------] Global test environment tear-down
[==========] 1 test from 1 test suite ran. (0 ms total)
[ PASSED ] 1 test.
のような形でリンクされているテストコードで定義されているTESTマクロが全て実行される。
gtest_mainの参照先がgoogletestのビルドディレクトリになっているみたいだが…
とりあえず導入完了。