はじめに
「テスト駆動開発による組み込みプログラミング」で、C言語による組み込み向けのテスト駆動開発を勉強しています。この書籍の内容は非常によいのですが、出版日がやや古く、Makefileを使用して実行環境がgithubで提供されています。僕としては、CMakeを使用して開発を行いたいので、備忘録も込めて、CMakeを使用して開発環境を構築する方法を本記事で説明します。テストコードは書籍の「2.3 Unity:CppUTest: C++ユニットテストハーネス」のものです。
ここではCMakeを用いた、CppUTestの開発環境構築に重点をおいて説明します。テストコード自体の解説は、ぜひ「テスト駆動開発による組み込みプログラミング」を購入してご確認ください。良書です。適宜ファイル名やフォルダ名は変更しています。
本記事で使用するコードは、githubで公開済みです。
2.2 Unity:C専用テストハーネス
2.3 Unity:CppUTest: C++ユニットテストハーネス←本記事
CppUTestとは?
CppUTestは、C言語およびC++言語向けに設計されたユニットテストフレームワークです。主に組み込み向けソフトウェアを対象としており、シンプルに使用可能であることが特徴です。
前提条件
C++言語のコンパイル環境はもちろん、CMakeやNinjaはインストール済みであると仮定します。
フォルダ構成
tree -d -L 1
.
├── .clang-format
├── CMakeLists.txt
├── README.md
├── src
│ ├── CMakeLists.txt
│ └── main.cpp
└── test
├── AllTests.cpp
├── AllTests.h
├── CMakeLists.txt
└── SprintfTest.cpp
フォルダ構成は上記の通りとします。src
には、通常のコードを、test
にはテスト用のコードを、unity
にはテストフレームワークのUnityを配置します。src
には、main.c
だけ作成しています。各フォルダはまだ作成しなくて大丈夫です。
なお、2.2節のUnity使用時には、Unityをgitのサブモジュールとして追加しましたが、本プロジェクトではCmakeの関数により、CppUTestをライブラリとして使用可能にします。詳細は後述します。
テストの作成
書籍の内容、およびCppUTestのexampleに従って、以下のファイルを作成します。コードの内容はここでは割愛しますので、詳細は書籍でご確認ください。
#include "CppUTest/CommandLineTestRunner.h"
#include "CppUTest/TestHarness.h"
#include "CppUTest/TestRegistry.h"
#include <stdio.h>
#include <string.h>
static char output[100];
static const char* expected;
TEST_GROUP(Sprintf)
{
void setup()
{
memset(output, 0xaa, sizeof output);
expected = "";
}
void teardown() {}
static void expect(const char* s)
{
expected = s;
}
static void given(int charsWritten)
{
LONGS_EQUAL(strlen(expected), charsWritten);
STRCMP_EQUAL(expected, output);
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"));
}
IMPORT_TEST_GROUP(Sprintf);
#include "CppUTest/CommandLineTestRunner.h"
int main(int ac, char **av)
{
return CommandLineTestRunner::RunAllTests(ac, av);
}
#include "AllTests.h"
CMakeLists.txtの作成
上記でテストコードが作成できたので、このコードをコンパイルしてテストが実行できるように各ディレクトリのCMakeLists.txtを作成していきます。
トップディレクトリのCMakeLists.txt
まず、トップディレクトリ(gitリポジトリ内)のCMakeLists.txtを以下のように作成します。
cmake_minimum_required(VERSION 3.8)
project("Test Driven Development for cpputest(2.3)")
set(TARGET_GROUP production CACHE STRING "Group to build")
include(FetchContent) # once in the project to include the module
if(TARGET_GROUP STREQUAL production)
add_subdirectory(src)
elseif(TARGET_GROUP STREQUAL test)
FetchContent_Declare(CppUTest
GIT_REPOSITORY https://github.com/cpputest/cpputest.git
GIT_TAG master)
FetchContent_MakeAvailable(CppUTest)
add_subdirectory(test)
include(CTest)
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)
以降に着目して見てましょう。
FetchContent_Declare
とFetchContent_MakeAvailable
により、CppUTestをgithubからダウンロードし、ライブラリとして使用可能になります。
さらに、include(CTest)
により、CMakeにテストを指定することができます。別のディレクトリのCMakeLists.txtでこのあと出てきますが、add_test(AllTests AllTests)
などとすることで、テストスイート(テストのグループ)を実行させることができます。add_test
は複数回呼び出すことができるので、複数のテストスイートを実行が便利になります。ただし、本記事でのテストスイートを1つのみとし、その中に1つのテストを含めています。
testフォルダ内のCMakeLists.txtの作成
以下のようにtestフォルダ内にCMakeLists.txtを作成します。
add_executable(AllTests
AllTests.cpp
SprintfTest.cpp
)
target_include_directories(AllTests
PRIVATE
.
)
target_link_libraries(AllTests
PRIVATE
CppUTest
)
enable_testing()
add_test(AllTests AllTests)
まず、AllTests.cppとSprintfTest.cppをadd_executable
でソースファイルに含めます。
今回はAllTests.hを作成しているので、target_include_directories
でtestディレクトリをインクルードします。
その後、target_link_libraries
でCppUTestをライブラリとしてリンクします。
最後に、enable_testing
でtestディレクトリをテスト可能にし、add_test
でテストを追加します。
テストの実行
まず、以下のコマンドでビルドします。
cmake -GNinja -DTARGET_GROUP=test -S. -Bbuild_test
ninja -v -Cbuild_test
build_testフォルダにコンパイル済みのファイル等が作成されていると思います。
さらに、以下でテストを実行しましょう。
ctest --verbose --test-dir build_test
今回の場合、成功するテストしか書いていないため、以下のようにテストが100%パスします。
test 1
Start 1: AllTests
1: Test command: /Users/yutahirai/LocalDesktop/Git/tddec-cmake/2.3/build_test/test/AllTests
1: Working Directory: /Users/yutahirai/LocalDesktop/Git/tddec-cmake/2.3/build_test/test
1: Test timeout computed to be: 1500
1: ..
1: OK (2 tests, 2 ran, 6 checks, 0 ignored, 0 filtered out, 0 ms)
1:
1/1 Test #1: AllTests ......................... Passed 0.06 sec
100% tests passed, 0 tests failed out of 1
Total Test time (real) = 0.06 sec
最後に
間違い・不足点・分かりづらい点等あればコメント欄で教えていただけますと幸いです。