CppUTestでC/C++コードをテストする

CppUTestは、単体テストを自動化するためのフリーソフトウェアです。

C/C++は非常に自由度が高く何でもできる反面、ちょっとしたミスですぐに不具合を起こします。このためソースコードレベルでテストを行うことが必須となります。これを行うのが単体テストです。テストを記述するのはとても面倒で億劫な作業です。しかし一度テストを記述すれば、そのあとはボタン1つで何度でもテストを実行できるのです。

皆さん何度か経験があるのではないでしょうか。手を加えた箇所と無関係だからと、手間なテストを怠ったことで不具合を起こしたことを。CppUTestは、このようなトラブルを防いでくれるかもしれないツールです。

目次

必要なもの

CMake

下記にしたがってダウンロードからインストールまで行います。

eclipse

下記にしたがってダウンロードからインストールまで行います。

CppUtest Test Runner

eclipseでCppUTestを使用するためのプラグインです。

STEP
下記サイトにアクセスします。
STEP
下図①②の順に操作し、zipファイルをダウンロードします。
CppUTest
STEP
下記サイトにアクセスします。
STEP
「Releases」をクリックします。
STEP
「Assets」をクリックします。
STEP
zipファイルをダウンロードします。

インストール

CppUtest Test Runner
STEP
「C:\eclipse\dropins」を開きます。
STEP
「CppUTest」フォルダーを作成します。
STEP
ダウンロードしたzipファイルを解凍し、「org.eclipse.cdt.testsrunner.cpputest」フォルダーを開きます。

「org.eclipse.cdt.testsrunner.cpputest」フォルダー内にある下記のフォルダーやファイルを、「C:\eclipse\dropins\CppUTest」フォルダーに移動します。

  • binフォルダー内にあるorgフォルダー
  • META-INFフォルダー
  • plugin.propertiesファイル
  • plugin.xmlファイル
STEP
移動後のフォルダー内の構成は下図のとおりになります。
STEP
コマンドプロンプトを起動し、「C:\eclipse」フォルダーに移動します。
STEP
「eclipse.exe -clean」を実行します。
CppUTest
STEP
ダウンロードしたzipファイルを解凍します。
STEP
解凍してできたフォルダー名を「CppUTest」に変更します。
STEP
CppUTestフォルダーをCドライブの直下に置きます。
STEP
スタートメニューから①コマンドプロンプトを探し出して右クリックし、「管理者として実行」をクリックします。
STEP
「cd C:\CppUTest\cpputest_build」と入力してEnterキーを押します。
STEP
「cmake -G “MinGW Makefiles” ..」と入力してEnterキーを押します。
STEP
「mingw32-make」と入力してEnterキーを押します。
STEP
「mingw32-make install」と入力してEnterキーを押します。
STEP
環境変数に新規で「CPPUTEST_HOME」「C:\Program Files (x86)\CppUTest」を追加します。
STEP
Windowsを再起動します。

eclipseの初期設定

eclipseでCppUTestを使えるようにするための設定を行います。

STEP
メニュー [実行 > 実行構成] をクリックします。
STEP
①をダブルクリックします。
STEP
①が表示されるので選択し、②に「Debug\CppUTest.exe」と入力します。
STEP
下図①を選択した後、②に「CppUTest Tests Runner」を選択します。最後に③④の順にボタンをクリックして終了です。

CppUTestによるテスト

プロジェクトの作成

STEP
eclipseを起動し、メニュー [ファイル > 新規 > プロジェクト] をクリックします。
STEP
下図①を選択した後、② [次へ] ボタンをクリックします。
STEP
下図①を選択した後、② [次へ] ボタンをクリックします。
STEP
下図①を選択した後、② [完了] ボタンをクリックします。

プロジェクトの設定

STEP
メニュー [プロジェクト > プロパティ―] をクリックします。
STEP
下図①~③の順に選択した後、④ [追加] ボタンをクリックします。
STEP
下図①に「${CPPUTEST_HOME}/include」を入力した後、② [OK] ボタンをクリックします。
STEP
続けてもう一度 [追加] ボタンをクリックします。
STEP
下図①に「${CPPUTEST_HOME}/include/CppUTest」を入力した後、② [OK] ボタンをクリックします。
STEP
下図①を選択した後、② [追加] ボタンをクリックします。
STEP
下図①に「CppUTest」を入力した後、② [OK] ボタンをクリックします。
STEP
続けてもう一度 [追加] ボタンをクリックします。
STEP
下図①に「CppUTestExt」を入力した後、② [OK] ボタンをクリックします。
STEP
下図①を選択した後、② [追加] ボタンをクリックします。
STEP
下図①に「${CPPUTEST_HOME}/lib」を入力した後、② [OK] ボタンをクリックします。
STEP
[適用して閉じる] ボタンをクリックします。

プロジェクトの構成例

下記のようなプロジェクト構成であったとします。

C:\workspace
└ CppUTest
  └ src
   ├ AllTests.cpp      ... テストコード
   ├ base64Test.cpp    ... テストコード
   ├ base64.c	   ... テスト対象
   └ base64.h	   ... テスト対象

[base64.c]:テスト対象のソースコード

//! Base64エンコードテーブル
static const uint8_t m_enc_table[] =
    "ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789+/";

/** ----------------------------------------------------------------------------
 * @brief Base64エンコード
 *
 * @param [in] bin : バイナリーデータ
 * @param bin_size : バイナリーデータ長
 * @param [out] ascii : ASCIIデータ
 * @return ASCIIデータ長
 */
int32_t base64_encode(const uint8_t bin[], uint32_t bin_size, uint8_t ascii[])
{
  uint8_t  bit6;
  uint8_t  buf_size   = 0;
  uint16_t buf        = 0;
  uint32_t ascii_size = 0;

  for (uint32_t i = 0; i < bin_size; i++) {
    buf = (uint16_t)(buf << 8) | bin[i];
    buf_size += 8;

    // 6ビット毎に変換
    do {
      bit6 = (uint8_t)(buf >> (buf_size - 6)) & 0x3f;
      buf_size -= 6;
      ascii[ascii_size] = m_enc_table[bit6];
      ascii_size++;
    } while (buf_size >= 6);
  }

  // 端数ビットがある場合は0を補完して変換
  if (buf_size > 0) {
    bit6              = (uint8_t)(buf << (6 - buf_size)) & 0x3f;
    ascii[ascii_size] = m_enc_table[bit6];
    ascii_size++;
  }

  // バイト数が4の倍数でないなら'='を追加
  while ((ascii_size % 4) != 0) {
    ascii[ascii_size] = (uint8_t)'=';
    ascii_size++;
  }
  ascii[ascii_size] = (uint8_t)'\0';

  return (int32_t)ascii_size;
}

[AllTests.cpp]:何も考えずにこのファイルを作成してください。

#include "CppUTest/CommandLineTestRunner.h"

int main(int argc, char *argv[])
{
  return CommandLineTestRunner::RunAllTests(argc, argv);
}

[base64Test.cpp]:「base64.c」ファイル用のテストコードです。

#include "CppUTest/TestHarness.h"    // 必要なもの

#include <string.h>

#include "base64.h"

// テストグループとテストグループ内の各テストを実行する前後に行う処理を定義します
// ( )内の"base64"は識別子で、テスト対象のファイル名にすると良いでしょう
TEST_GROUP(base64){
  // 各テストケースの実行直前に呼ばれる仮想メソッド
  TEST_SETUP(){
  }

  // 各テストケースの実行直後に呼ばれる仮想メソッド
  TEST_TEARDOWN(){
  }
};

// 実行するテストを記述します
// "base64"はTEST_GROUPと同じ識別子で、"Test_base64_encode"はテストの識別子です
TEST(base64, Test_base64_encode)
{
  uint8_t bin[128];
  uint8_t ascii[128];
  int32_t size;

  strncpy((char *)bin, "ABCDE", 5);
  size = base64_encode(bin, 5, ascii);
  LONGS_EQUAL(size, 8);  // テスト1
  STRNCMP_EQUAL((char *)ascii, "QUJDREU=", size);  // テスト2
}

// 他の関数のテストも行うなら、TESTを分けると良いでしょう
TEST(base64, Test_base64_decode)
{
  uint8_t bin[128];
  uint8_t ascii[128];
  int32_t size;

  // 成功パターン
  strncpy((char *)ascii, "QUJDREU=", 8);
  size = base64_decode(ascii, 8, bin);
  LONGS_EQUAL(size, 5);
  STRNCMP_EQUAL((char *)bin, "ABCDE", size);
}

プロジェクトの実行

STEP
①でCppUTestを、②で実行を選択し、③のボタンをクリックします。その結果すべてのテストに合格すると、④に緑色のバーが表示されます
STEP
1つでもテストで誤りが見つかった場合、①に赤色のバーが表示されます。

また②にはテストの一覧が表示されています。この中で誤りの発生したテストは、アイコンの左下に青色で小さく「×」と表示されます。そこをダブルクリックしていくとテストに引っ掛かった箇所へ移動できるので、誤りを訂正してください。

TESTマクロ

TESTマクロリスト

CppUTestが用意しているTESTマクロです。CppUTestではこれらのマクロを駆使して単体テストを行います。

CHECK(boolean condition)

ブール結果がtrueかをチェックします。

CHECK_FALSE(condition)

ブール結果がfalseかをチェックします。

STRCMP_EQUAL(expected, actual)

strcmpを使用して、文字列が等しいかをチェックします。

STRNCMP_EQUAL(expected, actual, length)

strncmpを使用して、文字列が等しいかをチェックします。

STRCMP_NOCASE_EQUAL(expected, actual)

大文字と小文字を区別せずに、文字列が等しいかをチェックします。

STRCMP_CONTAINS(expected, actual)

actualにexpectedが含まれているかをチェックします。

LONGS_EQUAL(expected, actual)

2つの数値を比較します。

UNSIGNED_LONGS_EQUAL(expected, actual)

2つの正の数を比較します。

LONGLONGS_EQUAL(expected, actual)

2つの数値を比較します。

UNSIGNED_LONGLONGS_EQUAL(expected, actual)

2つの正の数を比較します。

BYTES_EQUAL(expected, actual)

8ビット幅の2つの数値を比較します。

POINTERS_EQUAL(expected, actual)

2つのポインターを比較します。

DOUBLES_EQUAL(expected, actual, tolerance)

2つの浮動小数点数が許容範囲内(tolerance)かを比較します。

FUNCTIONPOINTERS_EQUAL(expected, actual)

2つのvoid(*)関数ポインターを比較します。

MEMCMP_EQUAL(expected, actual, size)

メモリーの2つの領域を比較します。

BITS_EQUAL(expected, actual, mask)

マスクを適用して、期待値と実際のビットを少しずつ比較します。

TESTマクロの使用例

CHECK(true);           // 判定値がtrueなら合格
CHECK_FALSE(false);    // 判定値がfalseなら合格

STRCMP_EQUAL("abc", "abc");        // 文字列が一致すれば合格
STRNCMP_EQUAL("abc", "abc", 3);    // 文字列が一致すれば合格
STRCMP_NOCASE_EQUAL("abc", "ABC");    // 大文字・小文字を区別せず一致すれば合格
STRCMP_CONTAINS("123", "ABC123");     // 文字列が含まれていれば合格

// 数値が一致すれば合格
LONGS_EQUAL(-1, -1);
UNSIGNED_LONGS_EQUAL(1, 1);
LONGLONGS_EQUAL(-1, -1);
UNSIGNED_LONGLONGS_EQUAL(1, 1);
BYTES_EQUAL(-1, 255);
DOUBLES_EQUAL(1.000, 1.001, 0.001);
BITS_EQUAL(0x31, 0x01, 0x0F);

MEMCMP_EQUAL("string", "string", 6);

// ポインターが一致すれば合格
POINTERS_EQUAL(0, 0);
FUNCTIONPOINTERS_EQUAL(0, 0);

その他テストコードのサンプル

CppUTestタグのある記事には、テストコードのサンプルがあります。それらも参考にしてください。

目次