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


## はじめに

「[テスト駆動開発による組み込みプログラミング](https://www.amazon.co.jp/%E3%83%86%E3%82%B9%E3%83%88%E9%A7%86%E5%8B%95%E9%96%8B%E7%99%BA%E3%81%AB%E3%82%88%E3%82%8B%E7%B5%84%E3%81%BF%E8%BE%BC%E3%81%BF%E3%83%97%E3%83%AD%E3%82%B0%E3%83%A9%E3%83%9F%E3%83%B3%E3%82%B0-%E2%80%95C%E8%A8%80%E8%AA%9E%E3%81%A8%E3%82%AA%E3%83%96%E3%82%B8%E3%82%A7%E3%82%AF%E3%83%88%E6%8C%87%E5%90%91%E3%81%A7%E5%AD%A6%E3%81%B6%E3%82%A2%E3%82%B8%E3%83%A3%E3%82%A4%E3%83%AB%E3%81%AA%E8%A8%AD%E8%A8%88-James-W-Grenning/dp/4873116147?__mk_ja_JP=%E3%82%AB%E3%82%BF%E3%82%AB%E3%83%8A&crid=2IARFTQ6ZHNDA&keywords=%E3%83%86%E3%82%B9%E3%83%88%E9%A7%86%E5%8B%95%E9%96%8B%E7%99%BA%E3%81%AB%E3%82%88%E3%82%8B%E7%B5%84%E3%81%BF%E8%BE%BC%E3%81%BF%E3%83%97%E3%83%AD%E3%82%B0%E3%83%A9%E3%83%9F%E3%83%B3%E3%82%B0&qid=1677923749&sprefix=%E3%83%86%E3%82%B9%E3%83%88%E9%A7%86%E5%8B%95%E9%96%8B%E7%99%BA%E3%81%AB%E3%82%88%E3%82%8B%E7%B5%84%E3%81%BF%E8%BE%BC%E3%81%BF%E3%83%97%E3%83%AD%E3%82%B0%E3%83%A9%E3%83%9F%E3%83%B3%E3%82%B0%2Caps%2C163&sr=8-1&linkCode=ll1&tag=reirev0e-22&linkId=8c42803fabc95ee3849fbe5b93606392&language=ja_JP&ref_=as_li_ss_tl)」で、C言語による組み込み向けのテスト駆動開発を勉強しています。この書籍の内容は非常によいのですが、出版日がやや古く、Makefileを使用して[実行環境がgithubで提供されています](https://github.com/jwgrenning/tddec-code)。僕としては、CMakeを使用して開発を行いたいので、備忘録も込めて、CMakeを使用して開発環境を構築する方法を本記事で説明します。テストコードは書籍の「2.2 Unity:C専用テストハーネス」のものです。

ここでは、Unityの開発環境構築に重点をおいて説明します。テストコード自体の解説は、ぜひ「[テスト駆動開発による組み込みプログラミング](https://www.amazon.co.jp/%E3%83%86%E3%82%B9%E3%83%88%E9%A7%86%E5%8B%95%E9%96%8B%E7%99%BA%E3%81%AB%E3%82%88%E3%82%8B%E7%B5%84%E3%81%BF%E8%BE%BC%E3%81%BF%E3%83%97%E3%83%AD%E3%82%B0%E3%83%A9%E3%83%9F%E3%83%B3%E3%82%B0-%E2%80%95C%E8%A8%80%E8%AA%9E%E3%81%A8%E3%82%AA%E3%83%96%E3%82%B8%E3%82%A7%E3%82%AF%E3%83%88%E6%8C%87%E5%90%91%E3%81%A7%E5%AD%A6%E3%81%B6%E3%82%A2%E3%82%B8%E3%83%A3%E3%82%A4%E3%83%AB%E3%81%AA%E8%A8%AD%E8%A8%88-James-W-Grenning/dp/4873116147?__mk_ja_JP=%E3%82%AB%E3%82%BF%E3%82%AB%E3%83%8A&crid=2IARFTQ6ZHNDA&keywords=%E3%83%86%E3%82%B9%E3%83%88%E9%A7%86%E5%8B%95%E9%96%8B%E7%99%BA%E3%81%AB%E3%82%88%E3%82%8B%E7%B5%84%E3%81%BF%E8%BE%BC%E3%81%BF%E3%83%97%E3%83%AD%E3%82%B0%E3%83%A9%E3%83%9F%E3%83%B3%E3%82%B0&qid=1677923749&sprefix=%E3%83%86%E3%82%B9%E3%83%88%E9%A7%86%E5%8B%95%E9%96%8B%E7%99%BA%E3%81%AB%E3%82%88%E3%82%8B%E7%B5%84%E3%81%BF%E8%BE%BC%E3%81%BF%E3%83%97%E3%83%AD%E3%82%B0%E3%83%A9%E3%83%9F%E3%83%B3%E3%82%B0%2Caps%2C163&sr=8-1&linkCode=ll1&tag=reirev0e-22&linkId=8c42803fabc95ee3849fbe5b93606392&language=ja_JP&ref_=as_li_ss_tl)」を購入してご確認ください。良書です。適宜ファイル名やフォルダ名は変更しています。

本記事で使用するコードは、[githubで公開済み](https://github.com/ReiRev/tddec-cmake/tree/main/2.2)です。Unityをサブモジュールとして含むため、クローンする際には、`--recursive`をつけてください。

[2.2 Unity:C専用テストハーネス](https://qiita.com/reirev2913/items/9cf598e86b9897313a6c) ←本記事
[2.3 Unity:CppUTest: C++ユニットテストハーネス](https://qiita.com/reirev2913/items/c8baa3b885fdad8722b0)

## Unityとは？

ここで指すUnityはゲームエンジンのことではなく、[C言語のユニットテストフレームワーク](http://www.throwtheswitch.org/unity)のことです。軽量・シンプルで、マイコンなどの組み込み機器のテストをターゲットにしています。MITライセンスで[githubに公開されています](https://github.com/ThrowTheSwitch/Unity)。

## 前提条件

C言語のコンパイル環境はもちろん、CMakeやNinjaはインストール済みであると仮定します。

## フォルダ構成

```shell
tree -d -L 1
```

```shell
.
├── src
├── test
└── Unity
```

フォルダ構成は上記の通りとします。`src`には、通常のコードを、`test`にはテスト用のコードを、`unity`にはテストフレームワークのUnityを配置します。`src`には、`main.c`だけ作成しています。各フォルダはまだ作成しなくて大丈夫です。

次節でUnityをgitサブモジュールとして追加するために、gitリポジトリを作成してください。方法は自由ですが、githubにリポジトリを作成して、クローンしてくるのが手早いかと思います。

## Unityをサブモジュールとして追加

作成したgitリポジトリ内に移動し、以下のコマンドでUnityをサブモジュールとして追加します。

```shell
git submodule add https://github.com/ThrowTheSwitch/Unity.git
```

サブモジュールとして追加することで、Unityが更新された際に、Unityフォルダ以下もアップデート可能となります。

## テストの作成

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

```c: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を以下のように作成します。

```cmake:./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のドキュメント](https://github.com/ThrowTheSwitch/Unity/tree/master/extras/fixture)をご覧ください。書籍では、Unity Fixturesを使用してテストを記述しているので、これらもコンパイル対象として指定しておきます。

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

### testフォルダ内のCMakeLists.txt

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

```shell: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は以下のようになります。

```cmake: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`でテストスイートをテストに追加します。

## テストの実行

まず、以下のコマンドでビルドします。

```shell
cmake -GNinja -DTARGET_GROUP=test -S. -Bbuild_test
ninja -v -Cbuild_test
```

build_testフォルダにコンパイル済みのファイル等が作成されていると思います。

さらに、以下でテストを実行しましょう。

```shell
ctest --verbose --test-dir build_test
```

今回の場合、成功するテストしか書いていないため、以下のようにテストが100%パスします。

```shell
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
```

## 最後に

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

## 参考文献

- [テスト駆動開発による組み込みプログラミング](https://www.amazon.co.jp/%E3%83%86%E3%82%B9%E3%83%88%E9%A7%86%E5%8B%95%E9%96%8B%E7%99%BA%E3%81%AB%E3%82%88%E3%82%8B%E7%B5%84%E3%81%BF%E8%BE%BC%E3%81%BF%E3%83%97%E3%83%AD%E3%82%B0%E3%83%A9%E3%83%9F%E3%83%B3%E3%82%B0-%E2%80%95C%E8%A8%80%E8%AA%9E%E3%81%A8%E3%82%AA%E3%83%96%E3%82%B8%E3%82%A7%E3%82%AF%E3%83%88%E6%8C%87%E5%90%91%E3%81%A7%E5%AD%A6%E3%81%B6%E3%82%A2%E3%82%B8%E3%83%A3%E3%82%A4%E3%83%AB%E3%81%AA%E8%A8%AD%E8%A8%88-James-W-Grenning/dp/4873116147?__mk_ja_JP=%E3%82%AB%E3%82%BF%E3%82%AB%E3%83%8A&crid=2IARFTQ6ZHNDA&keywords=%E3%83%86%E3%82%B9%E3%83%88%E9%A7%86%E5%8B%95%E9%96%8B%E7%99%BA%E3%81%AB%E3%82%88%E3%82%8B%E7%B5%84%E3%81%BF%E8%BE%BC%E3%81%BF%E3%83%97%E3%83%AD%E3%82%B0%E3%83%A9%E3%83%9F%E3%83%B3%E3%82%B0&qid=1677923749&sprefix=%E3%83%86%E3%82%B9%E3%83%88%E9%A7%86%E5%8B%95%E9%96%8B%E7%99%BA%E3%81%AB%E3%82%88%E3%82%8B%E7%B5%84%E3%81%BF%E8%BE%BC%E3%81%BF%E3%83%97%E3%83%AD%E3%82%B0%E3%83%A9%E3%83%9F%E3%83%B3%E3%82%B0%2Caps%2C163&sr=8-1&linkCode=ll1&tag=reirev0e-22&linkId=8c42803fabc95ee3849fbe5b93606392&language=ja_JP&ref_=as_li_ss_tl)
- [CMake Unity Integration](https://www.poisel.info/posts/2019-07-15-cmake-unity-integration/)