白黒羊飼いの庭

unityで作るゲームの製作メモでしたが、最近は機械学習とかデザインとか何でもやってます

おそらく一番簡単な「Google Test」導入記録

実際にテストコードを書くときに一から自分で書くのではなく、既存のフレームワークを使うという方法があります。
今回C++のコードのテストをするにあたって、「Google C++ Testing Framework」(Google Test)というフレームワークを使ったのでそのメモです。
使ってみたらまた追記するか別に記事を書くと思います。
数か月後の自分を信用していないせいで、前提知識がほぼ何もなくても使えるように余計なことまでたくさん書いてあるので適宜読み飛ばしてください。
github.com


日本語訳されたドキュメントもあります(Google Test — Google Test ドキュメント日本語訳)。

GoogleTestは比較的簡単に扱えて上記のような日本語ドキュメントがあったりQiitaにも情報があるので取っ付きやすいフレームワークみたいです。

ダウンロードとインストール

コードがGitHubにあるので以下のようにしてダウンロードし、ライブラリとして展開します。

$ git clone https://github.com/google/googletest.git
$ cd googletest
$ mkdir build
$ cd build
$ cmake -DCMAKE_INSTALL_PREFIX=$HOME/.local ..
$ make && make install

ライブラリについて

ライブラリには静的ライブラリ(スタティックライブラリ)と共有ライブラリ(シェアードライブラリ/ダイナミックリンクライブラリ)の2種類があります。
前者の静的ライブラリは拡張子「.a」で示されるコンパイル済みオブジェクトのことで、各々のプロジェクトフォルダに別々に存在します。
これはライブラリに修正があったときにそれぞれ直さなければならないのと、プロジェクトフォルダ分だけ複製されるので容量を圧迫するという問題があります。
この問題を解決したのが後者の、起動する実行ファイルと動的にリンクする共有ライブラリです。
共有ライブラリは拡張子「.so」で示されます。
「ldd "実行ファイル名"」でその実行ファイルと関連付けられている共有ライブラリを確認することができます。

共有ライブラリとして展開するときはcmakeのオプションに「-DBUILD_SHARED_LIBS=ON」を追加します。
上記で共有ライブラリの利点を述べましたが、リンクするための設定を省くために今回はひとまず簡易に静的ライブラリとして使うことを考えます。

この作業で「build」ディレクトリに「libgtest.a」「libgtest_main.a」ができました。
「libgtest.a」はGoogleTestのテスト関数が全て入っているライブラリで、「libgtest_main.a」は全てのテストケースを呼び出して実行するmain()関数のオブジェクトです。

静的ライブラリとして使う場合には、
「プロジェクトフォルダ」
  └「test」
    └「googletest」
となるようにフォルダをコピーしておきます。

.bashrcの設定

次いで

emacs ~/.bashrc

などのコマンドで、
「${HOME}/.bashrc」の末尾に

export GTEST_ROOT="「test/googletest」に至るまでのパス"
export CPLUS_INCLUDE_PATH="${GTEST_ROOT}/include:${CPLUS_INCLUDE_PATH}"
export LIBRARY_PATH="${GTEST_ROOT}/lib:${LIBRARY_PATH}"
export GTEST_INCLUDEDIR="${GTESTROOT}/googletest/include"
export GMOCK_INCLUDEDIR=${GTESTROOT}/googlemock/include
export GMOCK_LIBDIR=${GTESTROOT}/build/googlemock
export GTEST_LIBDIR=${GTESTROOT}/build/googlemock/gtest

を追記します。
「source .bashrc」でパスを通してから「echo $GTEST_ROOT」とかで設定した通りになっているかを確認します。
さらに「export LD_LIBRARY_PATH="${GTEST_DIR}/lib:${LD_LIBRARY_PATH}"」を追記すると共有ライブラリとしてコンパイラに認識してもらえます。

ここまでが基本的な準備です。

サンプルテストコード

それでは実際にテストを始めてみましょう。

MyCode.cpp

int tashizan(int i, int j)
{
    return i + j;
}

MyCode.hpp

#pragma once
int tashizan(int i, int j);

test/MyCodeTest.cpp

#include "gtest/gtest.h"
#include "../MyCode.hpp"
TEST(tashizan, base)
{
    EXPECT_EQ(3, tashizan(1,2));
}

「MyCode.cpp」のtashizan(i,j)という関数をテストするために「test」フォルダに「MyCodeTest.cpp」を作ってみました。

ここまでできたら後はテストのビルドと実行です。

Makefile作成

毎回コマンドを打ち込むのは面倒なのでこれまで使用していたのとは別に、「test」フォルダの中に「Makefile」を作っておきます。

Makefile

#!/bin/make

CC       =g++
CFLAGS   =-Wall -ansi -pedantic
TARGET   =MyCodeTest
TAR_OBJ	 =MyCodeTest.o
SRC      =MyCodeTest.cpp
HEADERS  =
INCLUDES =-Igoogletest/googletest/include
STD  =-std=gnu++11
LIB_DIR = -Lgoogletest/build/lib
LIBS     =$(LIB_DIR) -lgtest -lgtest_main -pthread
OBJS = MyCode.o $(TAR_OBJ)
.PHONY: all clean

all: $(TARGET)

$(TARGET): $(OBJS)
	$(CC) -g -o $(TARGET) $(OBJS) $(STD) $(LIBS)

MyCodeTest.o: ../MyCodeTest.cpp ../MyCodeTest.hpp
	$(CC) $(CFLAGS) -c ../MyCodeTest.cpp $(STD)

$(TAR_OBJ): $(SRC) $(HEADERS) Makefile
	$(CC) -c -o $@ $(SRC) $(INCLUDES) $(STD) $(LIBS)

clean:
	rm -f $(TARGET) $(OBJS)

上記をコピペして「Makefile:@: *** missing separator. Stop.」というエラーコードが出た場合空白部分が「タブ」ではなく「スペース」になっていることが考えられますのでMakefileを見直してください。

使用時のエラー

ここまで準備してmakeコマンドを叩いて、「fatal error: gtest/gtest.h: No such file or directory
#include "gtest/gtest.h"」のエラーメッセージが出るときはインクルードパスが通っていないのでその設定をしましょう。
私が今回対応したのは、Makefile

LIB_DIR = -Lgoogletest/build/lib
LIBS     =$(LIB_DIR) -lgtest -lgtest_main -pthread

の部分です。-Lオプションでここのライブラリも見てねとしました。

参考:シンプルで応用の効くmakefileとその解説 - URIN HACK


その他躓いたところ。
Makefile」に「-lpthread」オプションを付けたら、

gtest-all.cc:(.text._ZNK7testing8internal11ThreadLocalIPNS_31TestPartResultReporterInterfaceEE16GetOrCreateValueEv[_ZNK7testing8internal11ThreadLocalIPNS_31TestPartResultReporterInterfaceEE16GetOrCreateValueEv]+0x25): undefined reference to `pthread_getspecific'
gtest-all.cc:(.text._ZNK7testing8internal11ThreadLocalIPNS_31TestPartResultReporterInterfaceEE16GetOrCreateValueEv[_ZNK7testing8internal11ThreadLocalIPNS_31TestPartResultReporterInterfaceEE16GetOrCreateValueEv]+0x88): undefined reference to `pthread_setspecific'
collect2: error: ld returned 1 exit status

のエラーが出てライブラリ読んでくれなかったので、「-pthread」オプションにしたら解決しました。「-std=c++11」のときはそうしないとダメっぽい。
参考:multithreading - Difference between -pthread and -lpthread while compiling - Stack Overflow


あと宣言がないよエラー(GoogleTestには関係ないかもだけど)

MyCodeTest.cpp: In member function ‘virtual void Velocity_base_Test::TestBody()’:
MyCodeTest.cpp:38:20: error: ‘tashizan’ was not declared in this scope
         EXPECT_EQ(3, tashizan(1,2));

ヘッダに書いておけば良いのでは? と思ってたのですが、「MyCodeTest.cpp」内に

int tashizan(int i, int j);

を追記したら解決しました。
この部分はまた考えてみます。
これ、元々のヘッダに不要なインクルードガードが書いてあったことが問題だったみたいです。

#ifndef MY_CODE_HPP
#define MY_CODE_HPP

......

#endif // MY_CODE_HPP

こういうの。
これのおかげで必要な時にガードされてしまっていて読み込まれなかったようなので「#pragma once」に修正しました。
そもそも多重インクルードがダメなのは定義であって、宣言は何度インクルードされても良いんですよね……。
勉強になりました。
参考:インクルードガードとpragma once - にゃははー

最後に

こんな感じでお手軽に使うこともできるらしい。
blog.emattsan.org

コンパイル周りが全然分かってなくてそこそこ苦労しましたがこれでやっと使えるようになりました。
間違っているところあれば教えてください。