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

## はじめに

「[テスト駆動開発による組み込みプログラミング](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.3 Unity:CppUTest: C++ユニットテストハーネス」のものです。

ここではCMakeを用いた、CppUTestの開発環境構築に重点をおいて説明します。テストコード自体の解説は、ぜひ「[テスト駆動開発による組み込みプログラミング](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.3)です。

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

## CppUTestとは？

[CppUTest](https://cpputest.github.io/)は、C言語およびC++言語向けに設計されたユニットテストフレームワークです。主に組み込み向けソフトウェアを対象としており、シンプルに使用可能であることが特徴です。

## 前提条件

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

## フォルダ構成

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

```shell
.
├── .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使用時](https://qiita.com/reirev2913/items/9cf598e86b9897313a6c)には、Unityをgitのサブモジュールとして追加しましたが、本プロジェクトではCmakeの関数により、CppUTestをライブラリとして使用可能にします。詳細は後述します。

## テストの作成

書籍の内容、および[CppUTestのexample](https://github.com/cpputest/cpputest/tree/master/examples)に従って、以下のファイルを作成します。コードの内容はここでは割愛しますので、詳細は書籍でご確認ください。

```cpp:test/SprintfTest.cpp
#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"));
}
```

```cpp:test/AllTest.h
IMPORT_TEST_GROUP(Sprintf);
```

```test/AllTests.cpp
#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: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を作成します。

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

## テストの実行

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

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

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

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

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

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

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

## 最後に

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

## 参考文献

- [テスト駆動開発による組み込みプログラミング](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/)