0
3

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?

More than 3 years have passed since last update.

C言語によるアジャイル開発とテスト駆動開発(CI入門) ~ 3.CMake導入 ~

Last updated at Posted at 2021-02-08

概要

これまで、でアジャイル開発に対するテスト駆動の必要性、CUnitによるテスト実行の自動化を行ってきました。
今回は、CMakeを導入することでビルドプロセスを自動化していきます。

記事の全体像

環境

OS : Linux ubuntu 18.04.5 LTS
テストツール : CUnit 2.1-3

コンパイラ : gcc 7.5.0
ビルドツール:CMake 3.10.2 → 3.19.4(追記より)

ビルドツール

そもそもビルドツールとは

実機(PCであったり組み込みマイコンであったり)でプログラムを動かすためのこれまでの遍歴を書くと、
手動ビルド → ビルドスクリプト → ビルドツール という流れで進化してきています。

手動ビルド

ビルドに必要な処理を全て手動で行う。
ソースファイルが1つしか無い場合は良いが、多数存在する場合に正しい手順で作業するのは至難の業となる。
時間がかかる上に、ミスも起こりうる可能性が高いので色々と問題。

ビルドスクリプト

ビルドに必要な処理をスクリプト化して実行する。
手動ビルドの課題を色々と改善はしたが、人間は欲深き存在。
スクリプトは基本的に上から順に全て実行していくので、最後の方の工程でエラーが起きると最初からやり直しになる。
また、大きいシステムになるとコンパイルそのものに時間がかかる。

ビルドツール

ビルドスクリプトの課題を解決してくれるもの。
ビルドタスク(解析、コンパイル、リリース用ビルド、テスト用ビルド等)を定義して実行します。

ビルドツール自身が、ファイルの更新やファイルの依存関係を監視しタスクの最適化を行ってくれたりします(ツールによる)。
つまり、
ファイルが更新されていなければコンパイルを行わずに以前行ったコンパイル結果を使用してくれたり、
デプロイ用のビルドタスクではテスト用のファイルは不要なので、そこのコンパイルを省いてくれたりするというわけです。
これを__インクリメンタルビルド__といいます

代表的なツール

  • make (makefileに記述されたルールに沿って、ビルドを行う ※makefileを記述する)
  • autotools (アジャイル開発とテスト駆動 ~ 2.CUnit導入 ~の)
  • CMake (makefile等のファイルを自動で生成してくれる ※ CMakeLists.txtというテキストファイルを記述する)
  • Meson
  • Bazel
    など

make vs CMake

どちらも記載内容は下記のようになりますが、これらが楽してかけたほうが良いですよね。

記載内容
ターゲットの分離 ・リリース用
・デバッグ用
コンパイルの自動化 ・ソースコードの一覧
・コンパイルオプション
・インクルードパス
リンクの自動化 ・ライブラリパスの指定
・リンカオプションの設定

makeのデメリット

makefileは独特で暗黙のルールが多く、学習コストや保守面で大変なようです。また、コンパイラに依存します。
すなわち、開発環境が異なる場合makefileが使えないということです。

CMakeのメリット

一方CMakeは、コンパイラに依存しないツールです(マルチプラットフォーム対応のビルドツール)。
makefileとは若干位置づけが異なります。
CMakeはコンパイラに依存するビルド手順(makefile相当)を生成し、ビルドの実行はその生成した手順を元に他のツール(make相当)が行います。
この__相当__というのが肝で、他の環境ではmakefileではないビルド手順ファイルが生成され、makeでないツールがビルドを行います。
つまり、CMakeは各種ビルドツールのUIを担うと言って良いかもしれませんね。
ちなみに上で上げたmesonBazelもこれにあたります。

また、CMakemakefileと比べてわかりやすくかけます。

ですので、今回はCMakeを使用します。

CMake導入

CMakeのインストール

Bash
sudo apt install cmake -y

CMakeLists.txtの作成

インストール出来たので、早速CMakeLists.txtファイルを作成します。

CMakeLists.txt
# CMakeのバージョンを設定
cmake_minimum_required(VERSION 3.10.2)
# プロジェクト名と使用する言語を設定
project(unitTest_cmake C)

# unitTest.outという実行ファイルをadd.cとunitTest.cから作成
add_executable(unitTest.out add.c unitTest.c)
# unitTest.out作成時に libcunit.aをリンク
target_link_libraries(unitTest.out cunit)

これまで作成したファイルを整理しておきます。
また、ビルド時に中間ファイルが色々と生成されるので、build用のフォルダを追加しています。

Bash
$ mkdir build
$ tree
.
├── CMakeLists.txt
├── add.c // テスト対象ソースコード
├── add.h
├── build // build用のフォルダ
└── unitTest.c // テストソースコード

これまでに作成してきたファイル
add.c
#include "add.h"

// バグのあるadd関数(テスト対象)
int add(int x, int y) {
		return 0;
}
add.h
// #ifndef ADD_H_
// #define ADD_H_

int add(int x, int y);

//  #endif /* ADD_ */
unitTest.c
#include <CUnit/CUnit.h>
// #include <CUnit/Console.h>
#include <CUnit/Automated.h>

#include "add.h"

/**
 * addテストスイート
 **/
// テスト関数001
void test_add_001(void) {
	CU_ASSERT_EQUAL(add(1, 2), 3);
}

// テスト関数002
void test_add_002(void) {
	CU_ASSERT_EQUAL(add(-1, -2), -3);
}

// main関数
int main() {
CU_pSuite add_suite;

    CU_initialize_registry(); // 2.テスト構造の初期化
    add_suite = CU_add_suite("add", NULL, NULL); // 3.テストスイートの追加
    CU_add_test(add_suite, "test_001", test_add_001); // 4.テスト関数の追加
    CU_add_test(add_suite, "test_002", test_add_002); // 4.テスト関数の追加

	CU_automated_run_tests();
    // CU_console_run_tests(); // 5.適切なインターフェースを使用してテストを実行
    CU_cleanup_registry(); // 6.テスト構造のクリーン
}

ビルドと実行

bulidフォルダに入ってcmakeコマンドを実行します。
buildフォルダ内にmakefileが生成されたのが確認できるはずです。

Bash
$ cd build/
$ cmake ..
-- The C compiler identification is GNU 7.5.0
-- Check for working C compiler: /usr/bin/cc
-- Check for working C compiler: /usr/bin/cc -- works
-- Detecting C compiler ABI info
-- Detecting C compiler ABI info - done
-- Detecting C compile features
-- Detecting C compile features - done
-- Configuring done
-- Generating done
-- Build files have been written to: /path/to/projectDir/build
$ tree -L 1
.
├── CMakeCache.txt
├── CMakeFiles
├── Makefile // これ
└── cmake_install.cmake

ビルドします。 次のコマンドでCMakeがビルドツールを選んで実行してくれます。
目的ファイルであるunitTest.outが作成されているので、
実行すると、レポートファイルが吐き出されていることが確認できるはずです。

Bash
$ cmake --build .
Scanning dependencies of target unitTest.out
[ 33%] Building C object CMakeFiles/unitTest.out.dir/add.c.o
[ 66%] Building C object CMakeFiles/unitTest.out.dir/unitTest.c.o
[100%] Linking C executable unitTest.out
[100%] Built target unitTest.out

$ tree -L 1
.
├── CMakeCache.txt
├── CMakeFiles
├── Makefile
├── cmake_install.cmake
└── unitTest.out // これ

$ ./unitTest.out 
$ tree -L 1
.
├── CMakeCache.txt
├── CMakeFiles
├── CUnitAutomated-Results.xml // これ
├── Makefile
├── cmake_install.cmake
└── unitTest.out

以上で、CMakeを使用して、ビルドプロセスを自動化することが出来ました。

おまけ

フォルダを分ける

フォルダを分けたりして、本番環境に少しだけ寄せておこうと思います。

Bash
$ tree
.
├── CMakeLists.txt
├── add             // フォルダを追加
│   ├── include     // フォルダを追加
│   │   └── add.h
│   └── src         // フォルダを追加
│       └── add.c
├── main.c          // ファイルを新規作成 (アプリケーション側のメイン処理入る)
└── test            // フォルダを追加 (テスト側のメイン処理が入る)
    └── unitTest.c

別に大したことはやっていません。気になる方は見てください。

各種ファイルの中身

CMakeLists.txtの最初の4行は同じです。
主に下記3点を変更しています。

  1. ./add/includeをインクルードフォルダとして登録
  2. 実際のアプリケーションの作成指示
  3. フォルダが変更に伴うパスの変更
CMakeLists.txt
# CMakeのバージョンを設定
cmake_minimum_required(VERSION 3.10.2)
# プロジェクト名と使用する言語を設定
project(unitTest_cmake C)

# ./add/include をincludeフォルダに指定
include_directories(./add/include) // 1.ココ

# Main という実行ファイルを main.cと add/src/add.c から作成
add_executable(Main main.c add/src/add.c) // 2.ココ

# UnitTest という実行ファイルを test/unitTest.c と add/src/add.c から作成
add_executable(UnitTest test/unitTest.c add/src/add.c) // 3.ココ
# unitTest.out作成時に libcunit.aをリンク
target_link_libraries(UnitTest cunit)

main.c のみ今回新規に追加しました。
実行するアプリケーションを想定したファイルです。

main.c
#include <stdio.h>

#include <add.h>

int main() {
	int a = 1;
	int b = 2;

	int c = add(a,b);

	printf("1 + 2 = %d\n", c);
}

その他の変更点ですが、
#include "add.h"
#include <add.h> に変更しています。
これはCMakeLists.txtでインクルードディレクトリを指定したためです。

add.c
#include <add.h>

// バグのあるadd関数(テスト対象)
int add(int x, int y) {
		return 0;
}
add.h
// #ifndef ADD_H_
// #define ADD_H_

int add(int x, int y);

//  #endif /* ADD_ */
unitTest.c
#include <CUnit/CUnit.h>
#include <CUnit/Automated.h>

#include <add.h>

/**
 * addテストスイート
 **/
// テスト関数001
void test_add_001(void) {
	CU_ASSERT_EQUAL(add(1, 2), 3);
}

// テスト関数002
void test_add_002(void) {
	CU_ASSERT_EQUAL(add(-1, -2), -3);
}

// main関数
int main() {
CU_pSuite add_suite;

    CU_initialize_registry(); // 2.テスト構造の初期化
    add_suite = CU_add_suite("add", NULL, NULL); // 3.テストスイートの追加
    CU_add_test(add_suite, "test_001", test_add_001); // 4.テスト関数の追加
    CU_add_test(add_suite, "test_002", test_add_002); // 4.テスト関数の追加

	CU_automated_run_tests(); // 5.適切なインターフェースを使用してテストを実行
    CU_cleanup_registry(); // 6.テスト構造のクリーン
}

再度ビルドと実行

再度ビルドを行います。
Mainと、UnitTestの2つの実行ファイルが作成されているのが確認できました。

Bash
$ cd build
$ cmake ..
$ cmake --build .
$ tree -L 1
.
├── CMakeCache.txt
├── CMakeFiles
├── Main
├── Makefile
├── UnitTest
└── cmake_install.cmake

それぞれ実行してみます。

Bash
$ ./Main 
1 + 2 = 0

$ ./UnitTest
$ tree -L 1
.
├── CMakeCache.txt
├── CMakeFiles
├── CUnitAutomated-Results.xml // ココ
├── Main
├── Makefile
├── UnitTest
└── cmake_install.cmake

ちゃんと出来てますね。
よかった。

参考資料

今回の記事の趣旨から若干ずれてしまいますが、今回の勉強をするのに役立った参考資料のリンクです。
特にCMakeの使い方に関しては、本記事の趣旨から外れるので省略しますので、参考資料を参照していただければと思います。

そもそもビルドって??

C/C++のビルドの仕組みとライブラリ

make関連の全体像

makeとconfigureと、もっとナウいやつ
CMakeについて悪く書かれてますが、とりあえず自分で使ってみて判断しようかなと。

CMake関連の参考資料
その他参考資料

ありきたりなCMakeのプロジェクト作成 for C++
make (UNIX)

追記

カバレッジレポートを出力させる

最新版のCMakeをインストールする

CMakeのバージョンが古く途中で引っかかったので、最新版をインストールします。
すでにインストールしてしまった方、申し訳ないですが、アンインストールしてください。

Bash
sudo apt remove cmake -y

こちらの記事を参考に最新版をインストールします (執筆時の CMake -version = 3.19.4)
Ubuntu 18.04 に Cmake の Latest Release をインストールする

./bootstrapでエラーが出た場合

私の環境ではOpenSSLが見当たらないといったエラーが発生しました。

Bash
Could NOT find OpenSSL, try to set the path to OpenSSL root folder in the system variable OPENSSL_ROOT_DIR (missing: OPENSSL_CRYPTO_LIBRARY OPENSSL_INCLUDE_DIR)

libssl-devをインストールして再度./bootstrapを実行します。

Bash
sudo apt-get install libssl-dev

gcovによるカバレッジの計測

GCCに付属しているgcovというツールを使用し、ステートメントカバレッジ(C0)を計測します。

そのために、CMakeLists.txtを修正します。
先程 CMakeのバージョンを上げたのは、target_link_optionsが未対応のバージョンだったためです。

CMakeLists.txt
# CMakeのバージョンを設定
cmake_minimum_required(VERSION 3.10.2) // ココの記載はあまり気にする必要は無いらしい
# プロジェクト名と使用する言語を設定
project(unitTest_cmake C)

# ./add/include をincludeフォルダに指定
include_directories(./add/include)

# Main という実行ファイルを main.cと add/src/add.c から作成
add_executable(Main main.c add/src/add.c)

# UnitTest という実行ファイルを test/unitTest.c と add/src/add.c から作成
add_executable(UnitTest test/unitTest.c add/src/add.c)
# unitTest.out作成時に libcunit.aをリンク
target_link_libraries(UnitTest cunit)

# コンパイルとリンクオプションに -coverageを追加 // ココ
target_compile_options(UnitTest PUBLIC -coverage) // ココ
target_link_options(UnitTest PUBLIC -coverage) // ココ

./buildフォルダを一旦消し、再度CMakeを使用してビルドします。

Bash
rm -rf build
mkdir build
cd build
cmake ..
cmake --build .

すると、CMakeFiles/UnitTest.diradd/srctestディレクトリの中に *.gcnoファイルが出来ていると思います。

Bash
$ tree ./CMakeFiles/UnitTest.dir/
/
./CMakeFiles/UnitTest.dir/
├── C.includecache
├── DependInfo.cmake
├── add
│   └── src
│       ├── add.c.gcno
│       └── add.c.o
├── build.make
├── cmake_clean.cmake
├── depend.internal
├── depend.make
├── flags.make
├── link.txt
├── progress.make
└── test
    ├── unitTest.c.gcno
    └── unitTest.c.o

ここで、./UnitTestを実行すると、先程と同じディレクトリに*.gcdaファイルが出力されます。
このファイルを後ほど使用します。

Bash
$ ./UnitTest
$ tree ./CMakeFiles/UnitTest.dir/
./CMakeFiles/UnitTest.dir/
├── C.includecache
├── DependInfo.cmake
├── add
│   └── src
│       ├── add.c.gcda
│       ├── add.c.gcno
│       └── add.c.o
├── build.make
├── cmake_clean.cmake
├── depend.internal
├── depend.make
├── flags.make
├── link.txt
├── progress.make
└── test
    ├── unitTest.c.gcda
    ├── unitTest.c.gcno
    └── unitTest.c.o

出力結果の確認

出力結果を確認します。
gcovで簡易的な出力結果を確認後、add.c.gcovが吐き出されるので中身を確認しています。
add.c.gcovにはadd.cのファイルの内容が記載されており、各行の左側の数字が、テストで何回実行されたかが記載されています。

Bash
$ gcov CMakeFiles/UnitTest.dir/add/src/add.c.gcda
File '/home/yusuke/Desktop/cunitSample/add/src/add.c'
Lines executed:100.00% of 2
Creating 'add.c.gcov'

$ cat add.c.gcov 
        -:    0:Source:/home/yusuke/Desktop/cunitSample/add/src/add.c
        -:    0:Graph:CMakeFiles/UnitTest.dir/add/src/add.c.gcno
        -:    0:Data:CMakeFiles/UnitTest.dir/add/src/add.c.gcda
        -:    0:Runs:1
        -:    0:Programs:1
        -:    1:
        -:    2:#include <add.h>
        -:    3:
        -:    4:// バグのあるadd関数(テスト対象)
        2:    5:int add(int x, int y) {
        2:    6:		return 0;
        -:    7:}
        -:    8:

おわりに

次回はいよいよ Jenkinsを用いて、テストの自動化を行います。
またJenkinsで今回までに出力してきた、テスト結果とテストカバレッジの結果も表示できるようにします。

もし、間違いや指摘事項等あれば、コメントしていただけると大変助かります。

0
3
2

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
0
3

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?