Xcodeにはユニットテストを実行する機能があります。Xcodeから実行できるユニットテスト機能はXCTestフレームワークのチェック関数を使って、クラスやメソッドの戻り値が期待値通りになっているかをチェックします。チェック結果はXcodeの中でリスト表示されます。
XCTestはAppleプラットフォーム専用です。クロスプラットフォーム対応のライブラリを作っているときなどには、XCTestを使うよりも、クロスプラットフォーム対応のテストフレームワークを使用して、各プラットフォーム上で直接ユニットテストを実行できることが重要です。
クロスプラットフォーム対応のテストフレームワークの中にGoogle製のGoogle Test (gtest) があります。C++のテストフレームワークでmacOSやWindowsなど複数のプラットフォームに対応しています。
この記事では、 gtest をmacOS上でXcodeと組み合わせて使えるようにし、XCTestを使ったユニットテストと一緒にテストできるようにする方法を解説します。
準備
この記事では、gtest をソースからビルドして使う方法を解説します。gtest のビルドには CMake が必要です。まず、 CMake をインストールしてください。インストール方法については、次の記事で解説しています。
[clink url=”https://www.rk-k.com/archives/3907″]
gtest をビルドする
gtest のコードはGitHubにありますので、GitHubからクローンするかダウンロードしてください。
CMakeの設定変更
CMakeはv3.19.2でApple Silicon、Xcode 12の新ビルドシステムに対応しました。古いバージョンを使っている方はアップデートしましょう。
gtestはCMakeで、Xcodeのプロジェクトを生成して、Xcodeを使ってビルドします。しかし、この記事を執筆している時点では、生成されるプロジェクトの設定に問題があり、ビルドが通りません。
生成されるプロジェクトでは、C11が有効になっていません。gtestをビルドするにはC11を有効化する必要があります。
CMakeLists.txt
を開き、以下を追加します。Appleプラットフォーム上のときは、C++11を有効化するという設定です。
if (APPLE)
SET(CMAKE_CXX_STANDARD 11)
SET(CMAKE_CXX_STANDARD_REQUIRED on)
endif()
追加する場所は、enable_testing()
という行の上くらいが良いと思います。編集した後のファイルは次のようになります。
# Note: CMake support is community-based. The maintainers do not use CMake
# internally.
cmake_minimum_required(VERSION 2.8.8)
if (POLICY CMP0048)
cmake_policy(SET CMP0048 NEW)
endif (POLICY CMP0048)
project(googletest-distribution)
set(GOOGLETEST_VERSION 1.10.0)
if (CMAKE_VERSION VERSION_GREATER "3.0.2")
if(NOT CYGWIN AND NOT MSYS)
set(CMAKE_CXX_EXTENSIONS OFF)
endif()
endif()
if (APPLE)
SET(CMAKE_CXX_STANDARD 17)
SET(CMAKE_CXX_STANDARD_REQUIRED on)
endif()
enable_testing()
include(CMakeDependentOption)
include(GNUInstallDirs)
#Note that googlemock target already builds googletest
option(BUILD_GMOCK "Builds the googlemock subproject" ON)
option(INSTALL_GTEST "Enable installation of googletest. (Projects embedding googletest may want to turn this OFF.)" ON)
if(BUILD_GMOCK)
add_subdirectory( googlemock )
else()
add_subdirectory( googletest )
endif()
プロジェクトの生成
ビルドするために必要なプロジェクトの生成などを行います。次のようにクローンしたフォルダの直下にmybuild
というフォルダを作ります。
GoogleTest
├── BUILD.bazel
├── CMakeLists.txt
├── CONTRIBUTING.md
├── LICENSE
├── README.md
├── WORKSPACE
├── appveyor.yml
├── ci
├── googlemock
├── googletest
├── library.json
├── mybuild
└── platformio.ini
ターミナルでmybuild
に移動します。
$ cd GoogleTest/mybuild
次のようにcmakeを使って、プロジェクトを生成します。指定しているオプションは、以下の通りです。適時変更してください。
- pthreadを有効化
- インストール先を
usr/local/gtest
に設定 - Xcodeのプロジェクトを生成する
$ cmake -DGTEST_HAS_PTHREAD=1 -G Xcode -DCMAKE_INSTALL_PREFIX=/usr/local/gtest ../
Universal Binaryの設定
M1 Macにも対応できるようにUniversal Binaryを出力するための設定を行います。
生成された googletest-distribution.xcodeproj を開きます。プロジェクトのオプションを開き、以下のように設定します。全ターゲットの設定を変更するか、ターゲット単位の設定を削除して、プロジェクト設定を使うかはどちらでも構いません。
Architectures : Standard Architectures (Apple Silicon, Intel) – $(ARCHS_STANDARD)
Base SDK : macOS
C++11が有効化されているか確認する
プロジェクトの設定でC++11が有効化されていることを確認します。生成された`googletest-distribution.xcodeproj`を開きます。
プロジェクトのオプションを開き、gmockなどのターゲットのオプションを表示します。Build SettingsタブのOther C++ Flagsに`’-std=c++11’`があるかを確認します。
ビルドとインストール
xcodebuildを使ってビルドします。XcodeでGUIでビルドしても良いのですが、ここではターミナルで行います。また、インストール先を/usr/local/gtest
に指定したので、書き込むためには管理者権限が必要です。そのため、sudo
付きで実行します。
$ sudo xcodebuild -target install build
しばらくして、ビルドが成功すると、/usr/local/gtest
にインストールされます。
/usr/local/gtest
├── include
│ ├── gmock
│ └── gtest
└── lib
├── cmake
├── libgmock_maind.a
├── libgmockd.a
├── libgtest_maind.a
├── libgtestd.a
└── pkgconfig
Xcodeとgtestを連携させる
Xcodeのテストナビゲータに表示させるには、XCTestを使う必要があります。gtestもXCTestのテストケースと同じように表示させるには一工夫必要です。その一工夫を実装してくれたコードがOSSで公開されています。
mattstevens/xcode-googletest: Google Test integration with Xcode
プロジェクトに必要ファイルを追加する
使用するのは、Bundle/GoogleTests.m
です。このファイルをユニットテストバンドルのプロジェクトに追加します。
次に、ユニットテストバンドルのターゲットの設定で、「Build Settings」を開き、「Header Search Paths」と「Library Search Paths」に、インストールしたgtestのディレクトリを追加します。
以下の設定を書きます。
- Header Search Paths :
/usr/local/gtest/include
- Library Search Paths :
/usr/local/gtest/lib
ライブラリをリンクさせます。ユニットテストバンドルのターゲットに以下のライブラリを追加します。
- libgtestd.a
プロジェクトに登録するのでも良いのですが、/usr/local/gtest/lib
以下なので、少し面倒なら、次のようにOther Linker Flags
にオプション追加でも構いません。
- Other Linker Flags :
-lgtestd
テストを書く
確認するために簡単なテストコードを書いてみます。まずは、テスト対象のクラスを実装します。
// Counter.hpp
#ifndef COUNTER_HPP
#define COUNTER_HPP
class Counter {
private:
int value_;
public:
Counter();
virtual ~Counter();
void reset();
void increment(int step = 1);
void decrement(int step = 1);
int value() const;
};
#endif /* COUNTER_HPP */
// Counter.cpp
#include "Counter.hpp"
Counter::Counter() : value_(0)
{
}
Counter::~Counter()
{
}
void Counter::reset()
{
value_ = 0;
}
void Counter::increment(int step)
{
value_ += step;
}
void Counter::decrement(int step)
{
value_ -= step;
}
int Counter::value() const
{
return value_;
}
テストケースを実装します。例えば、次のようなコードです。
// CounterTests.cpp
#include <gtest/gtest.h>
#include "Counter.hpp"
TEST(CounterTests, Initialize)
{
Counter c;
ASSERT_EQ(0, c.value());
}
TEST(CounterTests, Increment)
{
Counter c;
c.increment();
ASSERT_EQ(1, c.value());
c.increment(10);
ASSERT_EQ(11, c.value());
}
TEST(CounterTests, Decrement)
{
Counter c;
c.decrement();
ASSERT_EQ(-1, c.value());
c.decrement(10);
ASSERT_EQ(-11, c.value());
}
TEST(CounterTests, Reset)
{
Counter c;
c.increment();
ASSERT_EQ(1, c.value());
c.reset();
ASSERT_EQ(0, c.value());
}
Xcodeでユニットテストを実行すると、テストナビゲータにXCTestで書いたテストと同様に項目が追加されて、テスト結果も表示されます。