はじめに
本記事は全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.oのmain()が優先され、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.
まとめ
本記事では以下を解説しました。
- makefileでのC/C++混在ビルドの構成
-
extern "C"ガードを使ったC言語コードのC++テスト対応 - GoogleTestによるテストケースの実装
次回は静的解析とカバレッジ計測の実装を解説します。
