ホーム: https://github.com/mity/acutest
Acutest は、開発者の邪魔にならず、外部依存関係を最小限に抑え、可能な限りシンプルにすることを目的とした C/C++ 単体テスト機能です。
これを実現するために、完全な実装は単一の C ヘッダー ファイル内に存在し、そのコアはいくつかの標準 C ライブラリ関数のみに依存します。
主な特徴:
acutest.h
だけです。main()
) を提供します。--tap
オプションを使用)。--xml-output=FILE
を使用)。C++ 固有の機能:
std::exception
から派生した場合、 what()
がエラー メッセージに書き込まれます。Unix/Posix 固有の機能:
clock_gettime()
) を提供している場合、ユーザーは--time=real
( --time
と同じ) および--time=cpu
を使用してテストの実行時間を測定できます。Linux 固有の機能:
Windows 固有の機能:
--time
を使用してテストの実行時間を測定できます。macOS 固有の機能:
1 つ以上の単体テストを実装し、 acutest.h
を含む C/C++ モジュールは、スタンドアロン プログラムとして構築できます。このドキュメントでは、結果のバイナリを「テスト スイート」と呼びます。次に、コマンド ライン オプションで指定されたとおりにスイートが実行され、テストが実行されます。
単体テストは次の場合にのみ成功すると言えます。
Acutest を使用するには、1 つ以上の単体テストを実装する C/C++ ソース ファイルの先頭にヘッダー ファイルacutest.h
インクルードするだけです。ヘッダーがmain()
関数の実装を提供していることに注意してください。
#include "acutest.h"
すべてのテストは、次のプロトタイプを使用して関数として実装される必要があります。
void test_example ( void );
テストでは、いくつかのプリプロセッサ マクロを使用してテスト条件を検証できます。これらは複数回使用でき、これらの条件のいずれかが失敗すると、特定のテストは失敗したとみなされます。
TEST_CHECK
は最も一般的に使用されるテスト マクロで、単にブール条件をテストし、条件が false (または 0) と評価された場合に失敗します。
例えば:
void test_example ( void )
{
void * mem ;
int a , b ;
mem = malloc ( 10 );
TEST_CHECK ( mem != NULL );
mem = realloc ( mem , 20 );
TEST_CHECK ( mem != NULL );
}
テストは互いに完全に独立している必要があることに注意してください。テスト スイートが呼び出されるたびに、ユーザーはスイート内で任意の数のテストを任意の順序で実行できます。さらに、デフォルトでは、サポートされているプラットフォームでは、各単体テストがスタンドアロン (サブ) プロセスとして実行されます。
最後に、テスト スイートのソース ファイルでは、マクロTEST_LIST
を使用して単体テストをリストする必要があります。リストでは、各テストの名前 (一意である必要があります) と、テストを実装する関数へのポインタを指定します。コマンドラインで使いやすい名前をお勧めします。特に、シェルでエスケープが必要になる可能性のあるスペースやその他の特殊文字は避けてください。また、名前の最初の文字としてダッシュ ( -
) を使用することも避けてください。ダッシュ ( - ) は、テスト名ではなくコマンド ライン オプションとして解釈される可能性があります。
TEST_LIST = {
{ "example" , test_example },
...
{ NULL, NULL } /* zeroed record marking the end of the list */
};
テストリストはゼロ化されたレコードで終了する必要があることに注意してください。
基本的なテスト スイートの場合、知っておく必要があるのはほぼこれだけです。ただし、Acutest は、特定の状況で役立つマクロをさらにいくつか提供します。これらについては、次のサブセクションで説明します。
TEST_CHECK
によく似たマクロTEST_ASSERT
がありますが、失敗すると現在の単体テストの実行が即座に中止されます。
例えば:
void test_example ( void )
{
void * mem ;
int a , b ;
mem = malloc ( 10 );
TEST_ASSERT ( mem != NULL );
mem = realloc ( mem , 20 );
TEST_ASSERT ( mem != NULL );
}
失敗した場合の中止は、 abort()
(テストが子プロセスとして実行される場合) を呼び出すか、 longjmp()
(そうでない場合) を介して実行されます。
したがって、このような残忍な中絶検査に伴うコストを理解している場合にのみ、この検査を使用する必要があります。単体テストの内容によっては、フラッシュされていないファイル記述子、メモリ リーク、デストラクタが呼び出されずに破棄された C++ オブジェクトなどが含まれる場合があります。
一般に、特定の状況で何を行うのか、およびTEST_ASSERT
選択した理由が正確にわかっていない限り、 TEST_ASSERT
よりもTEST_CHECK
を優先する必要があります。
C++ の場合、指定されたコード (通常は単なる関数またはメソッド呼び出し) が予期されるタイプの例外をスローすることを検証するための追加マクロTEST_EXCEPTION
があります。
関数が例外をスローしないか、互換性のないものをスローした場合、チェックは失敗します。
例えば:
void test_example ( void )
{
TEST_EXCEPTION ( CallSomeFunction (), std:: exception );
}
条件チェックが失敗した場合、問題のデバッグを容易にするために、状況に関する追加情報を提供すると役立つことがよくあります。 Acutest は、この目的のためにマクロTEST_MSG
およびTEST_DUMP
を提供します。
前者はprintf
のようなメッセージを出力し、もう一方は提供されたメモリ ブロックの 16 進ダンプを出力します。
たとえば、次のようになります。
void test_example ( void )
{
char produced [ 100 ];
char expected [] = "Hello World!" ;
SomeSprintfLikeFunction ( produced , "Hello %s!" , "world" );
TEST_CHECK ( strcmp ( produced , expected ) == 0 );
TEST_MSG ( "Expected: %s" , expected );
TEST_MSG ( "Produced: %s" , produced );
/* Or, if the function could provide some binary stuff, we might rather
* use TEST_DUMP instead in order to output a hexadecimal dump of the data.
*/
TEST_DUMP ( "Expected:" , expected , strlen ( expected ));
TEST_DUMP ( "Produced:" , produced , strlen ( produced ));
}
どちらのマクロも、最後にチェックしたマクロが失敗した場合にのみ何かを出力することに注意してください。言い換えれば、これら 2 つは同等です。
if (! TEST_CHECK ( some_condition != 0 ))
TEST_MSG ( "some message" );
TEST_CHECK ( some_condition != 0 );
TEST_MSG ( "some message" );
( TEST_MSG
可変個引数マクロをサポートするコンパイラが必要であることに注意してください。)
場合によっては、テスト ベクトルとそれぞれの期待される出力のコレクションを提供するデータのループとしてテスト関数を設計すると便利です。たとえば、単体テストがある種のハッシュ関数の実装を検証することになっており、そのためのテスト ベクトルのコレクションがハッシュ仕様に含まれていると想像してください。
このような場合、すべてのテスト ベクトルに関連付けられた名前を取得し、その名前を出力ログに出力すると、チェックが失敗した場合に有罪のテスト ベクトルを簡単に特定できるため、非常に便利です。ただし、ループ本体では数十のチェック マクロが実行される可能性があるため、そのような名前を追加してループ内のすべてのチェック メッセージをカスタマイズするのは非現実的かもしれません。
これを解決するために、Acutest はマクロTEST_CASE
を提供します。このマクロは、テスト ベクター名として機能する文字列を指定します。 Acutest を使用すると、出力ログ内で、指定された名前が後続の条件チェックのメッセージよりも前に配置されるようになります。
たとえば、あるサイズの指定されたバイト配列に対して、新しくmalloc
されたバッファ内の別のバイト配列を返すとするSomeFunction()
テストしていると仮定します。次に、次のようなことができます。
struct TestVector {
const char * name ;
const uint8_t * input ;
size_t input_size ;
const uint8_t * expected_output ;
size_t expected_output_size ;
};
struct TestVector test_vectors [] = {
/* some data */
};
void test_example ( void )
{
int i ;
const uint8_t * output ;
size_t output_size ;
for ( i = 0 ; i < sizeof ( test_vectors ) / sizeof ( test_vectors [ 0 ]); i ++ ) {
struct TestVector * vec = & test_vectors [ i ];
/* Output the name of the tested test vector. */
TEST_CASE ( vec . name );
/* Now, we can check the function produces what it should for the
* current test vector. If any of the following checking macros
* produces any output (either because the check fails, or because
* high `--verbose` level is used), Acutest also outputs the currently
* tested vector's name. */
output = SomeFunction ( vec -> input , vec -> input_size , & output_size );
if ( TEST_CHECK ( output != NULL )) {
TEST_CHECK ( output_size == vec -> expected_output_size );
TEST_CHECK ( memcmp ( output , vec -> expected_output , output_size ) == 0 );
free ( output );
}
}
}
指定された名前は、 TEST_CASE
の使用後に実行されるすべてのチェックに適用されます。
TEST_CASE
再度使用して別の名前を指定するまで。またはNULL
を引数としてTEST_CASE
を使用して名前が明示的にリセットされるまで。前のセクションで説明したマクロの多くには、デフォルトのメッセージの代わりにカスタム メッセージを出力できる対応するマクロがあります。
これらはすべて、前述のマクロと同じ名前ですが、接尾部のアンダースコアが追加されています。接尾辞を使用すると、 printf
のような文字列形式と、対応する追加の引数が期待されます。
したがって、たとえば、単純なチェックマクロの代わりに、
TEST_CHECK (a == b);
TEST_ASSERT (x < y);
TEST_EXCEPTION (SomeFunction(), std::exception);
それぞれの対応するものをカスタム メッセージで使用できます。
TEST_CHECK_ (a == b, " %d is equal to %d " , a, b);
TEST_ASSERT_ (x < y, " %d is lower than %d " , x, y);
TEST_EXCEPTION_ (SomeFunction(), std::exception, "SomeFunction() throws std::exception");
コマンド ライン オプション--verbose
を使用すると、それぞれのチェックが正常にパスした場合でもメッセージがログアウトされるため、中立的な表現を使用する必要があります。
(チェックが失敗した場合に備えて診断情報を出力する必要がある場合は、マクロTEST_MSG
を使用します。それがまさにその目的です。)
同様に、代わりに
TEST_CASE ( "name" );
もっと豊かに使えるよ
TEST_CASE_ ( "iteration #%d" , 42 );
ただし、これらはすべて、コンパイラが可変個引数プリプロセッサ マクロをサポートしている場合にのみ使用できることに注意してください。可変引数マクロは、C99 で C 言語の標準部分になりました。
テストの実装が完了したら、単純な C/C++ プログラムとしてコンパイルするだけです。たとえば、 cc
C コンパイラであると仮定します。
$ cc test_example.c -o test_example
テスト スイートがコンパイルされると、結果のテスト バイナリを使用してテストを実行できます。
テスト スイートの終了コードは、実行されたすべての単体テストが成功した場合は 0、いずれかが失敗した場合は 1、内部エラーが発生した場合はその他の数値になります。
デフォルトでは (コマンド ライン オプションなし)、実装されたすべての単体テストが実行されます。コマンドラインで指定された単体テストのサブセットのみを実行することもできます。
$ ./test_example # Runs all tests in the suite
$ ./test_example test1 test2 # Runs only tests specified
$ ./test_example --exclude test3 # Runs all tests but those specified
Acutest は複数のレベルの単体テスト選択を実装しているため (少なくとも 1 つのテスト ユニットに一致する最初のものが使用される)、単一のコマンド ライン引数でテスト ユニットのグループ全体を選択できることに注意してください。
完全一致: 引数が単体テストの名前全体と完全に一致する場合、指定されたテストのみが選択されます。
単語一致: 引数が完全なテスト名に一致しないが、1 つ以上のテスト名の単語全体には一致する場合、そのようなテストがすべて選択されます。
次の文字は単語の区切り文字として認識されます: スペース
、タブレータt
、ダッシュ-
、アンダースコア_
、スラッシュ/
、ドット.
、カンマ,
コロン:
、セミコロン;
。
部分文字列一致: 単語一致でもテストを選択できなかった場合、引数を部分文字列として含む名前を持つすべてのテストが選択されます。
適切なテスト命名戦略を採用することで、ユーザーは 1 つのコマンド ライン引数で関連するテスト ファミリ全体を簡単に実行 ( --exclude
が使用されている場合はスキップ) できるようになります。
たとえば、テストfoo-1
、 foo-2
、 foomatic
、 bar-1
、およびbar-10
を実装するテスト スイートtest_example
考えてみましょう。
$ ./test_example bar-1 # Runs only the test 'bar-1' (exact match)
$ ./test_example foo # Runs 'foo-1' and 'foo-2' (word match)
$ ./test_example oo # Runs 'foo-1', 'foo-2' and 'foomatic' (substring match)
$ ./test_example 1 # Runs 'foo-1' and 'bar-1' (word match)
--list
または-l
使用すると、指定されたテスト スイートによって実装されたすべての単体テストを一覧表示できます。
$ ./test_example --list
サポートされているすべてのコマンド ライン オプションの説明を表示するには、オプション--help
を指定してバイナリを実行します。
$ ./test_example --help
Q:このプロジェクトは「CUTest」という名前ではなかったですか?
A:はい。元の名前では過負荷が多すぎることが判明したため、名前が変更されました。
Q: ファイルREADME.md
および/またはLICENSE.md
を配布する必要がありますか?
A:いいえ。ヘッダーacutest.h
は、リポジトリへの URL、著作権表記、MIT ライセンス条項が含まれています。これらをそのまま残しておく限り、プロジェクトにヘッダーのみを追加してもまったく問題ありません。結局のところ、その簡単な使用とオールインワンヘッダーの性質が私たちの主な目的です。
Acutest には MIT ライセンスが適用されます。全文については、ファイルLICENSE.md
またはacutest.h
の冒頭を参照してください。
プロジェクトは github にあります。
そこで Acutest の最新バージョンを見つけたり、機能強化に貢献したり、バグを報告したりできます。