HOME > ソフテックだより > 第357号(2020年7月1日発行) 技術レポート「C言語ライブラリの単体テストの自動化について」

「ソフテックだより」では、ソフトウェア開発に関する情報や開発現場における社員の取り組みなどを定期的にお知らせしています。
さまざまなテーマを取り上げていますので、他のソフテックだよりも、ぜひご覧下さい。

ソフテックだより(発行日順)のページへ
ソフテックだより 技術レポート(技術分野別)のページへ
ソフテックだより 現場の声(シーン別)のページへ


ソフテックだより 第357号(2020年7月1日発行)
技術レポート

「C言語ライブラリの単体テストの自動化について」

1. はじめに

以前、C言語ライブラリの開発を担当した時に、ライブラリの単体テスト(ユニットテストとも呼ばれる)の自動化対応を行いました。今回のソフテックだよりでは、その時の取り組みについて紹介します。

ライブラリは汎用性の高い複数のプログラムを再利用可能な形でひとまとまりにしたもので、他のプログラムから呼び出されて利用されます。
単体テストは、関数やメソッドなどの小さな単位で検証するテストのことですが、この時のライブラリ開発ではライブラリを呼び出すテストプログラムを作成して単体テストを行いました。
開発したライブラリは約800の関数がありましたが、テストプログラムが自動で作成でき、テスト結果を自動で判定ができれば効率アップになり、確認ミスも防げると考え、単体テストの自動化について検討することにしました。

第277号(2017年3月1日発行)技術レポート『WindowsアプリケーションにおけるMSTestの活用』で紹介したMSTestや、第293号(2017年11月1日発行)技術レポート『アプリケーション開発におけるCppUnitの活用』で紹介したCppUnitなど、単体テストを自動化するためのテスト用フレームワーク(ツール)は多く存在します。
最初は存在を知っていたMSTestを使用できないかと考えましたが、開発ライブラリはマルチOS動作(WindowsやLinux)を前提としており、MSTestはWindowsのVisual Studio上で動作するツールであるため、使用しませんでした。検討していくうちに自前で対応するのが1番早く対応できそうであったため、仕組みを検討して対応することにしました。

2. 単体テストの自動化のフロー

検討した仕組みは次の(1)〜(7)のフローになります。設計作業などの開発工程の記載は省略しています。

(1) ライブラリのプログラムを作成する
ライブラリのプログラムを作成します。ライブラリ開発作業ですので、ここは単体テストの自動化とは関係しません。
(2) 関数情報を設定する
テストプログラムを作成する場合、ライブラリ関数の引数の数や種類、戻り値の有無などを知る必要があります。
プログラムコードから抽出して解析することも可能ですが、EXCELに設定する手順の方が短時間で開発できるため、EXCELへ設定する方法としました。
(3)で説明するテストプログラム生成ツールで読み込めるように、CSVファイル形式で保存します。
(3) テストプログラム生成
(2)で作成した関数情報を元に、テストプログラム生成ツールでテストプログラム(ソースファイル)を自動生成していきます。テストプログラム生成ツール(Windowsアプリ)は弊社で作成しました。
(4) 実行ファイルの作成
(1)で開発したライブラリのソースと、(3)のテストプログラムのソースをビルドして、実行ファイルを生成します。
(5) テストデータの作成
テストプログラムで使用するテストデータを作成します。
どのような単体テストを行うかを事前にテスト項目書にまとめ、テストを実施する予定で進めていました。テスト項目書に記入した入力値をそのままテストデータとして使用できれば時間短縮になると考え、その仕組みとしました。
(6) テスト実行
プログラムを実行すると、テストデータ分のテストを実行し、テスト結果をログファイルに書き出します。
(7) テスト結果の確認
作成されたテスト結果ログファイルを開き、テスト結果(OK/NG)を目視で確認し、テスト項目書へ結果を記入します。

(1)〜(7)をフローチャートにまとめると図 1のようになります。

クリックで拡大
        図1.  単体テストの自動化のフローチャート        (クリックで拡大)

繰り返しの説明になる部分もありますが、次に具体的な関数を使用した例で説明します。

3. 単体テスト実行例

割り算を行うDIV関数の単体テストを行う場合を例に説明していきます。

(1) ライブラリのプログラムを作成する
DIV関数 は図 2のようなプログラム関数になります。
bool DIV(short int dividend, short int divisor, short int *answer)
{
    // 入力値のエラーチェック
    bool result = CheckDivParam(dividend, divisor);

    if (result)
    {
        *answer = dividend / divisor;
    }
    else
    {
        *answer = 0;
    }

    return result;
}

図2.  ライブラリ関数の例

(2) 関数情報を設定する
DIV関数の単体テストプログラム作成に必要な関数情報を表 1のようにEXCELへ設定し、テストプログラム生成ツールで読み込めるように、CSVファイル形式で保存します。

表1.  関数情報の設定

関数名 戻り値の型 引数の入出力 引数名 引数の型
DIV bool IN dividend short int
IN divisor short int
OUT answer short int
(3) テストプログラムの自動生成
関数情報のCSVファイルを元に、テストプログラム生成ツールで自動生成したプログラムが図 3になります。
void Test_DIV(void)
{
    CTestProject testProjct;
    std::string strPath = TESTDATA_PATH;
    strPath += "TestData_DIV.csv";
    int max = testProjct.SetCsvFile(strPath);
        
    for (int line = 1; line <= max; line++)
    {
        // 入力値読み込み
        short int dividend = testProjct.GetDataToInt16(line, "dividend");
        short int divisor = testProjct.GetDataToInt16(line, "divisor");
        
        // 期待値読み込み
        bool exp_result = testProjct.GetDataToBool(line, "result");
        short int exp_answer = testProjct.GetDataToInt16(line, "answer");
        
        // ライブラリ関数実行
        short int answer;
        bool result = DIV(dividend, divisor, &answer);
        
        // 結果判定
        testProjct.AreEqual(1, line, "DIV", "result", exp_result, result);
        testProjct.AreEqual(1, line, "DIV", "answer", exp_answer, answer);
    }
}

図3.  自動生成したテストプログラム

テストプログラムでは以下の処理を行います。
  • テストデータファイル(TestData_DIV.csv)から関数へ渡す入力値を読み込む。
  • テストデータファイル(TestData_DIV.csv)から期待値を読み込む。
  • ライブラリ関数へ入力値を渡す。
  • 実行結果と期待値を比較する関数呼び出しを行う。
  • テストデータ件数分、No.1〜4を繰り返す。
(4) 実行ファイルの作成
開発したライブラリのソース(図 2)とテストプログラムのソース(図 3)をビルドして、実行ファイルを作成します。
(5) テストデータの作成
表 2のようにEXCELでテスト項目を作成していき、テストデータをテストプログラムで読み込めるように、CSVファイル形式で保存します。

表2.  テスト項目書例

No. テスト関数 項目 入力 出力(期待結果) テスト結果
dividend divisor result answer 結果 確認日 確認者 Ver
1-1 DIV 被除数が正の最大 32767 12 true 2730        
1-2 除数が正の最大 34 32767 true 0        
1-3 被除数が0 0 56 true 0        
1-4 除数が0(0除算エラー) 78 0 false 0        
(6) テストプログラムの実行
テストプログラムを実行すると、テストデータ分のテストを実行し、テスト結果をログファイルに書き出していきます。
(7) テスト結果の確認
実行結果と期待値が一致している場合は”OK”、実行結果と期待値が一致していない場合は”NG”と不一致内容が分かるように期待値と実際のテスト結果がログファイルに書き出されています。
テスト結果ログファイルイメージが図 4になります。最終行がNGの例です。
検索ワード目的と、目視でもNGを探しやすくするために、NG項目の最後に「★エラー」も出力しています。
テスト結果を目視で確認し、テスト項目書へ結果を記入します。
No.1-1 : DIV result = OK(true)
No.1-1 : DIV answer = OK(2730)
No.1-2 : DIV result = OK(true)
No.1-2 : DIV answer = OK(0)
No.1-3 : DIV result = OK(true)
No.1-3 : DIV answer = OK(0)
No.1-4 : DIV result = OK(false)
No.1-4 : DIV answer = NG(0, 98)  ★エラー

図4.  テスト結果ログファイルイメージ

4. メリット・デメリット

この単体テストの仕組みのメリット・デメリットについて説明します。

メリット
  • ライブラリ変更や修正後の再テストが短時間で実施できる
  • テストケースが増加してもテストプログラムを変更する必要がない
  • ライブラリ関数が増加してもテストプログラムを作成する必要がない
  • テスト項目書の内容がそのままテストデータとなる

テストケースやライブラリ関数が増えてもテストプログラムのプログラミングが不要であるため、極端な話、仕様を理解できている人であればテストを実施することが可能です。

デメリット
  • 関数の実行条件や実行タイミングによって実行結果が変わるライブラリの場合は使用できない
  • 構造体や配列が引数の関数やC++クラスのテストができない

開発したライブラリは関数に渡した値が同じであれば、何回関数呼び出しを行っても実行結果は必ず同じになるライブラリ(例えば、割り算の関数で10 ÷ 5を何度実行しても結果は2になる)であったため、問題ありませんでした。
しかし、関数の実行条件やタイミングによって実行結果が変わるライブラリの場合は使用できません。

構造体や配列が引数の関数やC++クラスの単体テストの場合は、生成されたテストプログラムそのままではコンパイルエラーとなるため、手直ししなければなりませんでした。

5. おわりに

今後の改善点がいくつかありますが、以下の点は改善したいと考えています。
  • 構造体や配列が引数の関数やC++クラスの単体テストにも対応したい。
  • 関数情報(引数や戻り値有無)はEXCELへの設定ではなく、プログラムコードから抽出するようにしたい。
  • テスト項目書への結果記入は、EXCELでログファイルを読み込んで転記できるようにしたい。

今後も有効なツールがあれば有効活用し、もし目的に合ったツールがなければ仕組みを検討して導入し、開発効率アップに役立てていきたいと思います。

(M.A.)


関連ページへのリンク

関連するソフテックだより

ページTOPへ