0
1

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?

【C言語×GitHub Actions CI#2】組込みC言語プロジェクトにCIを導入する:ビルド・単体テスト編

0
Posted at

はじめに

本記事は全5部構成のシリーズです。

  • #1:概要・構成編
  • #2(本記事):ビルド・単体テスト編 ← いまここ
  • #3:静的解析・カバレッジ編
  • #4:ハマりどころ編
  • #5:GitLab経験者向けワークフロー構文解説

#1ではCI環境の全体像とワークフロー構成を紹介しました。
本記事ではその中から「ビルド」と「単体テスト」のステップを掘り下げて解説します。

サンプルコードはこちらです。
https://github.com/YusukeHarada/c-sample


ビルド

makefileの構成

メインプログラムのビルド部分です。

CC = gcc
CXX = g++
CFLAGS = -Wall -Wextra
CXXFLAGS = -Wall -Wextra
TARGET = main
SRC = main.c
OUT_DIR = out
OBJ = $(OUT_DIR)/main.o

$(TARGET): $(OBJ)
    $(CC) $(CFLAGS) -o $(TARGET) $(OBJ)

$(OBJ): $(SRC)
    @mkdir -p $(OUT_DIR)
    $(CC) $(CFLAGS) -c $(SRC) -o $(OBJ)

コンパイラはC言語用に gcc、C++(テスト)用に g++ を明示的に分けて定義しています。これはC/C++混在環境でのリンクエラーを防ぐ上で重要なポイントです(詳しくは第4部で解説)。

ビルド成果物は out/ ディレクトリにまとめて出力するようにしています。

GitHub Actionsでのビルドステップ

- name: Build with make
  run: make

単体テスト(GoogleTest)

C言語コードをC++でテストする工夫

テストコードはC++(GoogleTest)で書いていますが、テスト対象はC言語のコードです。
C/C++混在環境では、extern "C" を使わないとリンクエラーになります。

main.h にガードを追加しています。

#ifndef MAIN_H
#define MAIN_H

#ifdef __cplusplus
extern "C" {
#endif

int add(int a, int b);
int multiply(int a, int b);
void run_program();

#ifdef __cplusplus
}
#endif

#endif // MAIN_H

#ifdef __cplusplus で囲むことで、C言語からインクルードした場合とC++からインクルードした場合の両方に対応できます。

テストコード側では次のようにインクルードします。

#include <gtest/gtest.h>
#include <cstdio>
#include <cstdlib>

extern "C" {
    #include "main.h"
}

main.cの修正

GoogleTestと組み合わせる場合、main.c に main() をそのままにしておくと問題が発生します。リンカは.oファイルをライブラリより先に処理するため、 -lgtest_main が提供する main() よりmain_obj.omain()が優先され、GoogleTestのランナーが起動しません。

これを避けるため、-DTESTINGマクロでテストビルド時のみmain()を除外します。

/* main.c の main() をテストビルド時に除外する */
#ifndef TESTING
int main()
{
    run_program();
    return 0;
}
#endif // TESTING

テストコードの実装

// add関数のテスト
TEST(AddTest, PositiveNumbers)
{
    EXPECT_EQ(add(2, 3), 5);
}

TEST(AddTest, NegativeNumbers)
{
    EXPECT_EQ(add(-2, -3), -5);
}

TEST(AddTest, MixedNumbers)
{
    EXPECT_EQ(add(5, -3), 2);
}

TEST(AddTest, Zero)
{
    EXPECT_EQ(add(0, 0), 0);
}

// multiply関数のテスト
TEST(MultiplyTest, PositiveNumbers)
{
    EXPECT_EQ(multiply(2, 3), 6);
}

TEST(MultiplyTest, Zero)
{
    EXPECT_EQ(multiply(5, 0), 0);
}

TEST(MultiplyTest, NegativeNumbers)
{
    EXPECT_EQ(multiply(-2, 3), -6);
}

TEST(MultiplyTest, One)
{
    EXPECT_EQ(multiply(5, 1), 5);
}

// run_program関数のテスト
TEST(ProgramTest, RunProgram)
{
    run_program();
    SUCCEED();
}

テストケース一覧

テストスイート テストケース 検証内容
AddTest PositiveNumbers 正の数の加算(2 + 3 = 5)
AddTest NegativeNumbers 負の数の加算(-2 + -3 = -5)
AddTest MixedNumbers 混合加算(5 + -3 = 2)
AddTest Zero ゼロの加算(0 + 0 = 0)
MultiplyTest PositiveNumbers 正の数の乗算(2 × 3 = 6)
MultiplyTest Zero ゼロを含む乗算(5 × 0 = 0)
MultiplyTest NegativeNumbers 負の数の乗算(-2 × 3 = -6)
MultiplyTest One 1を含む乗算(5 × 1 = 5)
ProgramTest RunProgram run_program()の正常実行確認

makefileのテストターゲット

GTEST_FLAGS = -lgtest -lgtest_main -pthread
TEST_TARGET = test_main
TEST_SRC = test_main.cc

test: $(OUT_DIR)/$(TEST_TARGET)
    ./$(OUT_DIR)/$(TEST_TARGET)

$(OUT_DIR)/$(TEST_TARGET): $(OUT_DIR)/main_obj.o $(OUT_DIR)/main_test.o
    @mkdir -p $(OUT_DIR)
    $(CXX) $(CXXFLAGS) -o $(OUT_DIR)/$(TEST_TARGET) \
        $(OUT_DIR)/main_obj.o $(OUT_DIR)/main_test.o $(GTEST_FLAGS)

$(OUT_DIR)/main_obj.o: $(SRC)
    @mkdir -p $(OUT_DIR)
    $(CC) $(CFLAGS) -DTESTING -c $(SRC) -o $(OUT_DIR)/main_obj.o

$(OUT_DIR)/main_test.o: $(TEST_SRC)
    @mkdir -p $(OUT_DIR)
    $(CXX) $(CXXFLAGS) -c $(TEST_SRC) -o $(OUT_DIR)/main_test.o

main.c$(CC)(gcc)で、テストコードは $(CXX)(g++)でそれぞれコンパイルし、最終的に g++ でリンクしている点がポイントです。

GitHub Actionsでのテストステップ


- name: Run unit tests
  run: make test

テスト実行結果

[==========] Running 9 tests from 3 test suites.
[----------] Global test environment set-up.
[----------] 4 tests from AddTest
[ RUN      ] AddTest.PositiveNumbers
[       OK ] AddTest.PositiveNumbers (0 ms)
[ RUN      ] AddTest.NegativeNumbers
[       OK ] AddTest.NegativeNumbers (0 ms)
[ RUN      ] AddTest.MixedNumbers
[       OK ] AddTest.MixedNumbers (0 ms)
[ RUN      ] AddTest.Zero
[       OK ] AddTest.Zero (0 ms)
[----------] 4 tests from MultiplyTest
[ RUN      ] MultiplyTest.PositiveNumbers
[       OK ] MultiplyTest.PositiveNumbers (0 ms)
[ RUN      ] MultiplyTest.Zero
[       OK ] MultiplyTest.Zero (0 ms)
[ RUN      ] MultiplyTest.NegativeNumbers
[       OK ] MultiplyTest.NegativeNumbers (0 ms)
[ RUN      ] MultiplyTest.One
[       OK ] MultiplyTest.One (0 ms)
[----------] 1 test from ProgramTest
[ RUN      ] ProgramTest.RunProgram
[       OK ] ProgramTest.RunProgram (0 ms)
[==========] 9 tests from 3 test suites ran.

image.png


まとめ

本記事では以下を解説しました。

  • makefileでのC/C++混在ビルドの構成
  • extern "C" ガードを使ったC言語コードのC++テスト対応
  • GoogleTestによるテストケースの実装

次回は静的解析とカバレッジ計測の実装を解説します。

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

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?