5
7

More than 1 year has passed since last update.

Unity+CMakeでC言語のユニットテスト(「テスト駆動開発による組み込みプログラミング」2.2節)

Last updated at Posted at 2023-02-12

はじめに

テスト駆動開発による組み込みプログラミング」で、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フォルダ以下もアップデート可能となります。

テストの作成

書籍の内容に従って、以下のファイルを作成します。コードの内容はここでは割愛しますので、詳細は書籍でご確認ください。

test/suite_stdio/TestSprintf.c
#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"));
}
test/suite_stdio/TestSprintf_Runner.c
#include "unity.h"
#include "unity_fixture.h"

TEST_GROUP_RUNNER(Sprintf)
{
	RUN_TEST_CASE(Sprintf, NoFormatOperations);
	RUN_TEST_CASE(Sprintf, InsertString);
}
test/suite_stdio/suite_stdio.c
#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を以下のように作成します。

./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.cunity_memory.cも含める必要がある点です。これらはUnityのadd-onで、CppUTestと同様の記法を可能にするためのものです。詳しくは、Unity Fixturesのドキュメントをご覧ください。書籍では、Unity Fixturesを使用してテストを記述しているので、これらもコンパイル対象として指定しておきます。

最後のadd_subdirectory(test)で、testフォルダを加えます。

testフォルダ内のCMakeLists.txt

テストフォルダ内の構成は以下のようになっています。

tree
.
├── 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は以下のようになります。

test/CMakeLists.txt
add_subdirectory(suite_stdio)

test/suite_stdio内のCMakeLists.txt

最後のCMakeLists.txtは以下のようになります。

test/suite_stdio
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

最後に

間違い・不足点・分かりづらい点等あればコメント欄で教えていただけますと幸いです。

参考文献

5
7
0

Register as a new user and use Qiita more conveniently

  1. You get articles that match your needs
  2. You can efficiently read back useful information
  3. You can use dark theme
What you can do with signing up
5
7