UbuntuでGoogleTestをレガシーなCコードに対して部分的に実行する

目的

業務で10年以上前から利用されてきているレガシーなCコードに対して修正を加えた.修正は,グローバルだけでなくstaticな変数および関数がない関数の追加だけである. その関数が,想定した動きになるかどうか確かめておきたかったため,その関数に対してのみGoogleTestを実行できたのでその結果を記載する.

通常は(?),グローバルだけでなくstaticな変数および関数が存在することが多いので,実施は複雑になると思われる.

ユニットテスト(関数単体テスト)の実施が初めてなので,慣習と大きくズレていたり,より効率のよい方法をご存じの場合はコメント下さい.

環境は以下のとおり*1

名称 規格 製造会社 備考
ホストOS Windows 10 Pro,バージョン20H2,OSビルド19042.1348 Microsoft
仮想マシン VirtualBox 6.1.30 r148432 Oracle Guest Additions設定済
ゲストOS Ubuntu 16.04.7 LTS Canonical Ltd. cat /etc/os-releaseで確認
Linuxカーネル 4.15.0-142-generic Linus Torvalds and Community contributors uname -rで確認
CMake cmake version 3.5.1 kitware cmake --versionで確認
C++コンパイラ g++ (Ubuntu 5.4.0-6ubuntu1~16.04.12) 5.4.0 Free Software Foundation, Inc. g++ --versionで確認
GoogleTest 1.10.0 Google 1.11.0が2021/12/13時点では最新

結果

GoogleTestのインストール

1.11.0が今日2021/12/13時点での最新だがmake installで失敗するため,1.10.0を使用する. バージョンの確認はこちら☞https://github.com/google/googletest/releases

$ wget https://github.com/google/googletest/archive/refs/tags/release-1.10.0.zip
$ unzip release-1.10.0.zip
$ cd googletest-release-1.10.0/
$ mkdir build
$ cd build
$ cmake ..
$ sudo make install

ファイル構成とGoogleTest実行まで

以下,実際のコードを書くわけには行かないので,やんわり"ぼかして"記載する.

関数を追加したファイルと追加した関数

ファイル構成を描くと以下で,commonproc/commonの配下が共通で使用するもの,appl/src配下を主として使用してひとつのバイナリーを作成している. また,ApInc.hにはappl/src配下のヘッダーファイルがすべてインクルードされている:

├── common
│   ├── Cmn.h
│   ├── Pk.c
│   └── Pk.h
└── proc
    ├── common
    │   ├── PCmn.h
    │   └── PCmd.h
    └── appl
        └── src
            ├── Makefile
            ├── ApInc.h
            ├── ApMain.c
            ├── ApMain.h
            ├── ApFnc.c
            └── ApFnc.h

ソースコードとしてはApFnc.cに以下のような関数をファイル末尾に追加した:

uint16_t usGetTimerVal(uint8_t ucTime)
{
    uint16_t usTimer = SEC_10;     /* SEC_10はdefine定義値10 */
    switch(ucTime)
    {
        case SET_TIME_10M:        /* SET_TIME_10Mはdefine定義値1 */
            usTimer = SEC_600;  /* SEC_600はdefine定義値600 */
            break;
        
        case SET_TIME_10S:        /* SET_TIME_10Sはdefine定義値0 */
        default:
            usTimer = SEC_10;       /* SEC_10はdefine定義値10 */
            break;
    }

    return usTimer ;
}

一方,ApFnc.hには上記の宣言extern uint16_t usGetTimerVal(uint8_t);を追記したのみである.

GoogleTest実行のためのテストファイル

世の中,同一の階層にtestディレクトリーを作成してテストコードを書くのが主流なようなので(?),以下のようにtestディレクトリーとApFnc.cに対応するApFncTest.ccを作成した*2

├── common
│   ├── Cmn.h
│   ├── Pk.c
│   └── Pk.h
└── proc
    ├── common
    │   ├── PCmn.h
    │   └── PCmd.h
    └── appl
        ├── src
        │   ├── Makefile
        │   ├── ApInc.h
        │   ├── ApMain.c
        │   ├── ApMain.h
        │   ├── ApFnc.c
        │   └── ApFnc.h
        └── test
            ├── Makefile
            └── ApFncTest.cc

ApFncTest.ccの中身は以下で,大まかには,使用する関数の読み込みとテストケースを記載するだけである:

#include <gtest/gtest.h>
/* ヘッダーファイルのインクルードはMakefileで対応する */

/* 以下のような書き方をするとstaticな変数や関数も使用できるとのこと(参考文献参照) */
namespace unit_test
{
    #include "ApFnc.c"   /* usGetTimerVal関数の実態の読み込み */

    TEST(usGetTimerValTest, usGetTimerVal)
    {
        /* 正常系 */
        /* case SET_TIME_10Mのパスを通る */
        EXPECT_EQ(SEC_600,   usGetTimerVal(SET_TIME_10M));
        /* case SET_TIME_10Sのパスを通る */
        EXPECT_EQ(SEC_10,    usGetTimerVal(SET_TIME_10S));

        /* 異常系 */
        /* defaultのパスを通る */
        EXPECT_EQ(SEC_10,    usGetTimerVal(2));
        /* defaultのパスを通る */
        EXPECT_EQ(SEC_10,    usGetTimerVal(-1));
    }
}

また,上記をビルドするMakefileは以下である:

TARGET_TEST=appl_test
BUILD_ROOT_PATH=../../..
# GoogleTestにはC++コンパイラーを使用
CC=g++
# GoogleTestでのコンパイル時にエラーが出るのでC++11またはgnu++11を使用(後述)
CXXFLAGS=-std=c++11
# テストコードが追加の場合はここにソースを追加
SRCS=ApFncTest.cc
# GoogleTestを実行するコード範囲を狭める(後述)
DEFS=-DEXEC_GTEST
# GoogleTest独自のものだけでなくpthreadライブラリーも必要
LIBS=-lgtest_main -lgtest -lpthread
# 共通のヘッダーとapplビルド時に参照するヘッダーを指定
INCDIR= -I$(BUILD_ROOT_PATH)/common -I$(BUILD_ROOT_PATH)/proc/common -I../src

all: clean $(TARGET_TEST)
$(TARGET_TEST):
  $(CC) $(SRCS) -o $@ $(DEFS) $(CXXFLAGS) $(INCDIR) $(LIBS)
clean:
   -rm -f $(TARGET_TEST)

上記が準備できたら,make allを実行してTARGET_TESTであるappl_testが同一階層に生成される.それを実行することによりテスト結果が得られる:

$ ./appl_test 
Running main() from /home/so2akt/gtest/googletest-release-1.10.0/googletest/src/gtest_main.cc
[==========] Running 1 test from 1 test suite.
[----------] Global test environment set-up.
[----------] 1 test from usGetTimerValTest
[ RUN      ] usGetTimerValTest.usGetTimerVal
[       OK ] usGetTimerValTest.usGetTimerVal (0 ms)
[----------] 1 tests from usGetTimerValTest (0 ms total)

[----------] Global test environment tear-down
[==========] 1 test from 1 test suite ran. (0 ms total)
[  PASSED  ] 1 test.

その他

GoogleTestでのコンパイル時にエラー

g++でのコンパイル時に-std=c++11または-std=gnu++11のオプションが存在しないと以下のエラーが出た*3

In file included from /usr/include/c++/5/type_traits:35:0,
                 from /usr/local/include/gtest/gtest.h:59,
                 from ApFncTest.cc:1:
/usr/include/c++/5/bits/c++0x_warning.h:32:2: error: #error This file requires compiler and library support for the ISO C++ 2011 standard. This support must be enabled with the -std=c++11 or -std=gnu++11 compiler options.
 #error This file requires compiler and library support \
  ^

実行するコード範囲を狭める

ApFnc.cに追加したコードはusGetTimerVal関数のみだが,もちろんそれ以外の多量な関数も同ファイルに存在している.その影響で,それらの関数をビルド対象にするとエラーが大量に出るため,EXEC_GTESTを定義してusGetTimerVal関数のみを実行するようにした.この関数はたまたまグローバルだけでなくstaticな関数や変数を参照しないため簡単にGoogleTestを実施することができた.

実際にはもちろん複雑だが以下のとおりである:

#include "ApInc.h"
・・・
static uint8_t G_ucVar = 0;
・・・
enum TAG_INFO
{
    INFO,
    WARN,
    ERROR,
    INFO_MAX
};
・・・
#ifndef EXEC_GTEST
void vInit (void)
{
    uint8_t ucVal = 0;
    G_ucVar = 1;
・・・
}
・・・
#endif  /* EXEC_GTEST */
uint16_t usGetTimerVal(uint8_t ucTime)
{
    uint16_t usTimer = SEC_10;     /* SEC_10はdefine定義値10 */
    switch(ucTime)
    {
        case SET_TIME_10M:        /* SET_TIME_10Mはdefine定義値1 */
            usTimer = SEC_600;  /* SEC_600はdefine定義値600 */
            break;
        
        case SET_TIME_10S:        /* SET_TIME_10Sはdefine定義値0 */
        default:
            usTimer = SEC_10;       /* SEC_10はdefine定義値10 */
            break;
    }

    return usTimer ;
}

参考URLなど

  1. [ubuntu]Google Testの導入方法(2021/12/13現在)
  2. 花井志生:モダンC言語プログラミング,KADOKAWA,2019,pp.151-160(2021/12/13現在)

*1:前提として,C++コンパイラーとCMakeはインストールされているとする.

*2:GoogleTestはC++として動かすので拡張子はccとした.

*3:むしろ以下のエラーが出たので,このコンパイラーオプションをつけた.