はじめに
「テスト駆動開発による組み込みプログラミング」で、C言語による組み込み向けのテスト駆動開発を勉強しています。この書籍の内容は非常によいのですが、出版日がやや古く、Makefileを使用して実行環境がgithubで提供されています。僕としては、CMakeを使用して開発を行いたいので、備忘録も込めて、CMakeを使用して開発環境を構築する方法を本記事で説明します。テストコードは書籍の「2.2 Unity:C専用テストハーネス」のものです。
ここでは、Unityの開発環境構築に重点をおいて説明します。テストコード自体の解説は、ぜひ「テスト駆動開発による組み込みプログラミング」を購入してご確認ください。良書です。適宜ファイル名やフォルダ名は変更しています。
本記事で使用するコードは、githubで公開済みです。Unityをサブモジュールとして含むため、クローンする際には、--recursive
をつけてください。
2.2 Unity:C専用テストハーネス ←本記事
2.3 Unity:CppUTest: C++ユニットテストハーネス
Unityとは?
ここで指すUnityはゲームエンジンのことではなく、C言語のユニットテストフレームワークのことです。軽量・シンプルで、マイコンなどの組み込み機器のテストをターゲットにしています。MITライセンスでgithubに公開されています。
前提条件
C言語のコンパイル環境はもちろん、CMakeやNinjaはインストール済みであると仮定します。
フォルダ構成
tree -d -L 1
.
├── src
├── test
└── Unity
フォルダ構成は上記の通りとします。src
には、通常のコードを、test
にはテスト用のコードを、unity
にはテストフレームワークのUnityを配置します。src
には、main.c
だけ作成しています。各フォルダはまだ作成しなくて大丈夫です。
次節でUnityをgitサブモジュールとして追加するために、gitリポジトリを作成してください。方法は自由ですが、githubにリポジトリを作成して、クローンしてくるのが手早いかと思います。
Unityをサブモジュールとして追加
作成したgitリポジトリ内に移動し、以下のコマンドでUnityをサブモジュールとして追加します。
git submodule add https://github.com/ThrowTheSwitch/Unity.git
サブモジュールとして追加することで、Unityが更新された際に、Unityフォルダ以下もアップデート可能となります。
テストの作成
書籍の内容に従って、以下のファイルを作成します。コードの内容はここでは割愛しますので、詳細は書籍でご確認ください。
#include <unity.h>
#include <unity_fixture.h>
#include <stdio.h>
#include <string.h>
TEST_GROUP(Sprintf);
static char output[100];
static const char *expected;
TEST_SETUP(Sprintf)
{
memset(output, 0xaa, sizeof output);
expected = "";
}
TEST_TEAR_DOWN(Sprintf)
{
}
static void expect(const char *s)
{
expected = s;
}
static void given(int charsWritten)
{
TEST_ASSERT_EQUAL(strlen(expected), charsWritten);
TEST_ASSERT_EQUAL_STRING(expected, output);
TEST_ASSERT_BYTES_EQUAL(0xaa, output[strlen(expected) + 1]);
}
TEST(Sprintf, NoFormatOperations)
{
expect("hey");
given(sprintf(output, "hey"));
}
TEST(Sprintf, InsertString)
{
expect("Hello World\n");
given(sprintf(output, "Hello %s\n", "World"));
}
#include "unity.h"
#include "unity_fixture.h"
TEST_GROUP_RUNNER(Sprintf)
{
RUN_TEST_CASE(Sprintf, NoFormatOperations);
RUN_TEST_CASE(Sprintf, InsertString);
}
#include "unity_fixture.h"
static void RunAllTests(void)
{
RUN_TEST_GROUP(Sprintf);
}
int main(int argc, const char *argv[])
{
return UnityMain(argc, argv, RunAllTests);
}
CMakeLists.txtの作成
上記でテストコードが作成できたので、このコードをコンパイルしてテストが実行できるように各ディレクトリのCMakeLists.txtを作成していきます。
トップディレクトリのCMakeLists.txt
まず、トップディレクトリ(gitリポジトリ内)のCMakeLists.txtを以下のように作成します。
cmake_minimum_required(VERSION 3.12)
project("Test Driven Development for Embbed C(Unity ver)" C)
set(TARGET_GROUP production CACHE STRING "Group to build")
if(TARGET_GROUP STREQUAL production)
add_subdirectory(src)
elseif(TARGET_GROUP STREQUAL test)
include(CTest)
add_library(Unity STATIC
Unity/src/unity.c
Unity/extras/fixture/src/unity_fixture.c
Unity/extras/memory/src/unity_memory.c
)
target_include_directories(Unity PUBLIC
Unity/src
Unity/extras/fixture/src
Unity/extras/memory/src
)
add_subdirectory(test)
else()
message(FATAL_ERROR "Given TARGET_GROUP unknown")
endif()
まず、set(TARGET_GROUP production CACHE STRING "Group to build")
を設定し、コンパイル時に-DTARGET_GROUP=test
または-DTARGET_GROUP=production
を指定することで、テストビルドなのかプロダクションビルドなのかを分けられるようにします。その後、testなのかproductionなのかに応じて、CMakeの挙動を変更しています。
elseif(TARGET_GROUP STREQUAL test)
以降に着目して見てましょう。
include(CTest)
により、CMakeにテストを指定することができます。別のディレクトリのCMakeLists.txtでこのあと出てきますが、add_test(suite_stdio_test suite_stdio_app)
などとすることで、テストスイート(テストのグループ)を実行させることができます。add_test
は複数回呼び出すことができるので、複数のテストスイートを実行が便利になります。ただし、本記事でのテストスイートは1つのみです。
その後のadd_library
以降では、Unityのライブラリの設定を行なっています。1点注意が必要なのは、unity.c
だけではなく、unity_fixture.c
やunity_memory.c
も含める必要がある点です。これらはUnityのadd-onで、CppUTestと同様の記法を可能にするためのものです。詳しくは、Unity Fixturesのドキュメントをご覧ください。書籍では、Unity Fixturesを使用してテストを記述しているので、これらもコンパイル対象として指定しておきます。
最後のadd_subdirectory(test)
で、testフォルダを加えます。
testフォルダ内のCMakeLists.txt
テストフォルダ内の構成は以下のようになっています。
.
├── CMakeLists.txt
└── suite_stdio
├── CMakeLists.txt
├── TestSprintf.c
├── TestSprintf_Runner.c
└── suite_stdio.c
1 directory, 5 files
testフォルダ内に1つ、CMakeLists.txtを作成し、各テストスイートのフォルダをインクルードします。ここでは、インクルードするフォルダはsuite_stdioのみなので、CMakeLists.txtは以下のようになります。
add_subdirectory(suite_stdio)
test/suite_stdio内のCMakeLists.txt
最後のCMakeLists.txtは以下のようになります。
add_executable(suite_stdio_app
suite_stdio.c
)
target_sources(suite_stdio_app PRIVATE
TestSprintf_Runner.c
TestSprintf.c
)
target_link_libraries(suite_stdio_app
Unity
)
add_test(suite_stdio_test suite_stdio_app)
まず、実行ファイル(main関数のあるファイル)をadd_executable
で指定します。その後、実行ファイルに依存するファイルをtarget_sources
で加えます。そして、Unityをライブラリとして使用しているので、target_link_libraries
で実行ファイルとUnityをリンクします。最後に、add_test
でテストスイートをテストに追加します。
テストの実行
まず、以下のコマンドでビルドします。
cmake -GNinja -DTARGET_GROUP=test -S. -Bbuild_test
ninja -v -Cbuild_test
build_testフォルダにコンパイル済みのファイル等が作成されていると思います。
さらに、以下でテストを実行しましょう。
ctest --verbose --test-dir build_test
今回の場合、成功するテストしか書いていないため、以下のようにテストが100%パスします。
1: Test timeout computed to be: 1500
1: Unity test run 1 of 1
1: ..
1:
1: -----------------------
1: 2 Tests 0 Failures 0 Ignored
1: OK
1/1 Test #1: suite_stdio_test ................. Passed 0.00 sec
100% tests passed, 0 tests failed out of 1
Total Test time (real) = 0.01 sec
最後に
間違い・不足点・分かりづらい点等あればコメント欄で教えていただけますと幸いです。