Movatterモバイル変換


[0]ホーム

URL:


Skip to content

Navigation Menu

Search code, repositories, users, issues, pull requests...

Provide feedback

We read every piece of feedback, and take your input very seriously.

Saved searches

Use saved searches to filter your results more quickly

Sign up

Simple header-only C/C++ unit testing facility.

License

NotificationsYou must be signed in to change notification settings

mity/acutest

Folders and files

NameName
Last commit message
Last commit date

Latest commit

 
 
 
 
 
 
 
 
 
 
 
 
 

Repository files navigation

Home:https://github.com/mity/acutest

What Is Acutest

Acutest is C/C++ unit testing facility aiming to be as simple as possible, notto stand in the developer's way and to minimize any external dependencies.

To achieve that, the complete implementation resides in a single C header file,and its core depends only on few standard C library functions.

Overview

Main features:

  • Unit tests in C or C++ are supported.
  • No need to install/setup/configure any testing framework. Acutest is justa single header file,acutest.h.
  • The header provides the program entry point (functionmain()).
  • Minimal dependencies: Core features only depend on few standard C headers,optional features may use more if available on the particular system.
  • Trivial interface for writing unit tests: Few preprocessor macros describedfurther below.
  • Rudimentary support forTest Anything Protocol(use--tap option).
  • Rudimentary support for xUnit-compatible XML output (use--xml-output=FILE).

C++ specific features:

  • Acutest catches any C++ exception thrown from any unit test function. Whenthat happens, the given test is considered to fail.
  • If the exception is derived fromstd::exception,what() is written outin the error message.

Unix/Posix specific features:

  • By default, every unit test is executed as a child process.
  • By default, if the output is directed to a terminal, the output is colorized.
  • If the system offers Posix timer (clock_gettime()), user can measure testexecution times with--time=real (same as--time) and--time=cpu.

Linux specific features:

  • If a debugger is detected, the default execution of tests as child processesis suppressed in order to make the debugging easier.

Windows specific features:

  • By default, every unit test is executed as a child process.
  • If a debugger is detected, the default execution of tests as child processesis suppressed in order to make the debugging easier.
  • By default, if the output is directed to a terminal, the output is colorized.
  • Acutest installs a SEH filter to print out uncaught SEH exceptions.
  • User can measure test execution times with--time.

macOS specific features:

  • If a debugger is detected, the default execution of tests as child processesis suppressed in order to make the debugging easier.

Any C/C++ module implementing one or more unit tests and includingacutest.h,can be built as a standalone program. We call the resulted binary as a "testsuite" for purposes of this document. The suite is then executed to run thetests, as specified with its command line options.

We say any unit test succeeds if and only if:

  1. all condition checks (as described below) called throughout its executionpass;
  2. the test does not throw any exception (C++ only); and
  3. (on Windows or Unix) the unit test subprocess is not interrupted/terminated(e.g. by a signal on Unix or SEH on Windows).

Writing Unit Tests

Basic Use

To use Acutest, simply include the header fileacutest.h on the beginning ofthe C/C++ source file implementing one or more unit tests. Note the headerprovides implementation of themain() function.

#include"acutest.h"

Every test is supposed to be implemented as a function with the followingprototype:

voidtest_example(void);

The tests can use some preprocessor macros to validate the test conditions.They can be used multiple times, and if any of those conditions fails, theparticular test is considered to fail.

TEST_CHECK is the most commonly used testing macro which simply tests aboolean condition and fails if the condition evaluates to false (or zero).

For example:

voidtest_example(void){void*mem;inta,b;mem=malloc(10);TEST_CHECK(mem!=NULL);mem=realloc(mem,20);TEST_CHECK(mem!=NULL);}

Note that the tests should be completely independent on each other. Wheneverthe test suite is invoked, the user may run any number of tests in the suite,in any order. Furthermore by default, on platforms where supported, each unittest is executed as a standalone (sub)process.

Finally, the test suite source file has to list the unit tests, using themacroTEST_LIST. The list specifies name of each test (it has to be unique)and pointer to a function implementing the test. I recommend names which areeasy to use on command line: especially avoid space and other specialcharacters which might require escaping in shell; also avoid dash (-) as afirst character of the name, as it could then be interpreted as a command lineoption and not as a test name.

TEST_LIST= {   {"example",test_example },   ...   { NULL,NULL }/* zeroed record marking the end of the list */};

Note the test list has to be ended with zeroed record.

For a basic test suites this is more or less all you need to know. HoweverAcutest provides some more macros which can be useful in some specificsituations. We cover them in the following sub-sections.

Aborting on a Check Failure

There is a macroTEST_ASSERT which is very similar toTEST_CHECK but, if itfails, it aborts execution of the current unit test instantly.

For example:

voidtest_example(void){void*mem;inta,b;mem=malloc(10);TEST_ASSERT(mem!=NULL);mem=realloc(mem,20);TEST_ASSERT(mem!=NULL);}

The abortion in the case of failure is performed either by callingabort()(if the test is executed as a child process) or vialongjmp() (if it is not).

Therefore it should be used only if you understand the costs connected withsuch a brutal abortion of the test. Depending on what your unit test does,it may include unflushed file descriptors, memory leaks, C++ objects destructedwithout their destructors being called and more.

In general,TEST_CHECK should be preferred overTEST_ASSERT, unless youknow exactly what you do and why you choseTEST_ASSERT in some particularsituation.

Testing C++ Exceptions

For C++, there is an additional macroTEST_EXCEPTION for verifying the givencode (typically just a function or a method call) throws the expected type ofexception.

The check fails if the function does not throw any exception or if it throwsanything incompatible.

For example:

voidtest_example(void){TEST_EXCEPTION(CallSomeFunction(), std::exception);}

Richer Failure Diagnosis

If a condition check fails, it is often useful to provide some additionalinformation about the situation so the problem is easier to debug. Acutestprovides the macrosTEST_MSG andTEST_DUMP for this purpose.

The former one outputs anyprintf-like message, the other one outputs ahexadecimal dump of a provided memory block.

So for example:

voidtest_example(void){charproduced[100];charexpected[]="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));}

Note that both macros output anything only when the most recently checkingmacro has failed. In other words, these two are equivalent:

if(!TEST_CHECK(some_condition!=0))TEST_MSG("some message");
TEST_CHECK(some_condition!=0);TEST_MSG("some message");

(Note thatTEST_MSG requires the compiler with variadic macros support.)

Loops over Test Vectors

Sometimes, it is useful to design your testing function as a loop over dataproviding a collection of test vectors and their respective expected outputs.For example imagine our unit test is supposed to verify some kind of a hashingfunction implementation and we've got a collection of test vectors for it inthe hash specification.

In such cases, it is very useful to get some name associated with every testvector and output the name in the output log so that if any check fails, it iseasy to identify the guilty test vector. However, the loop body may executedozens of checking macros and so it may be impractical to add such name tocustomize every check message in the loop.

To solve this, Acutest provides the macroTEST_CASE. The macro specifiesa string serving as the test vector name. When used, Acutest makes sure thatin the output log the provided name precedes any message from subsequentcondition checks.

For example, lets assume we are testingSomeFunction() which is supposed,for a given byte array of some size, return another array of bytes in a newlymalloc-ed buffer. Then we can do something like this:

structTestVector {constchar*name;constuint8_t*input;size_tinput_size;constuint8_t*expected_output;size_texpected_output_size;};structTestVectortest_vectors[]= {/* some data */};voidtest_example(void){inti;constuint8_t*output;size_toutput_size;for(i=0;i<sizeof(test_vectors) /sizeof(test_vectors[0]);i++) {structTestVector*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);        }    }}

The specified name applies to all checks executed after the use ofTEST_CASE

  • until the unit test ends; or
  • untilTEST_CASE is used again to specify another name; or
  • until the name is explicitly reset by usingTEST_CASE with theNULLas its argument.

Custom Log Messages

Many of the macros mentioned in the earlier sections have a counterpart whichallows to output a custom messages instead of some default ones.

All of these have the same name as the aforementioned macros, just with theunderscore suffix added. With the suffix, they then expectprintf-like stringformat and corresponding additional arguments.

So, for example, instead of the simple checking macros

TEST_CHECK(a == b);TEST_ASSERT(x < y);TEST_EXCEPTION(SomeFunction(), std::exception);

we can use their respective counterparts with a custom messages:

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");

You should use some neutral wording for them because, with the command lineoption--verbose, the messages are logged out even if the respective checkpasses successfully.

(If you need to output some diagnostic information just in the case the checkfails, use the macroTEST_MSG. That's exactly its purpose.)

Similarly, instead of

TEST_CASE("name");

we can use richer

TEST_CASE_("iteration #%d",42);

However note all of these can only be used if your compiler supports variadicpreprocessor macros. Variadic macros became a standard part of the C languagewith C99.

Building the Test Suite

When we are done with implementing the tests, we can simply compile it asa simple C/C++ program. For example, assumingcc is your C compiler:

$ cc test_example.c -o test_example

Running Unit Tests

When the test suite is compiled, the resulted testing binary can be used to runthe tests.

Exit code of the test suite is 0 if all the executed unit tests pass, 1 if anyof them fails, or any other number if an internal error occurs.

By default (without any command line options), it runs all implemented unittests. It can also run only subset of the unit tests as specified on thecommand line:

$ ./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

Note that a single command line argument can select a whole group of test unitsbecause Acutest implements several levels of unit test selection (the 1st onematching at least one test unit is used):

  1. Exact match: When the argument matches exactly the whole name of a unittest then just the given test is selected.

  2. Word match: When the argument does not match any complete test name, butit does match whole word in one or more test names, then all such tests areselected.

    The following characters are recognized as word delimiters: space,tabulator\t, dash-, underscore_, slash/, dot., comma,,colon:, semicolon;.

  3. Substring match: If even the word match failed to select any test, thenall tests with a name which contains the argument as its substring areselected.

By adopting an appropriate test naming strategy, this allows user to run (orto skip if--exclude is used) easily whole family of related tests with asingle command line argument.

For example consider test suitetest_example which implements testsfoo-1,foo-2,foomatic,bar-1 andbar-10:

$ ./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)

You may use--list or-l to just list all unit tests implemented by thegiven test suite:

$ ./test_example --list

To see description for all the supported command line options, run the binarywith the option--help:

$ ./test_example --help

FAQ

Q: Wasn't this project known as "CUTest"?

A: Yes. It has been renamed as the original name was found to betoo much overloaded.

Q: Do I need to distribute fileREADME.md and/orLICENSE.md?

A: No. The headeracutest.h includes URL to our repo, copyright note andthe MIT license terms inside of it. As long as you leave those intact, we arecompletely fine if you only add the header into your project. After all,the simple use and all-in-one-header nature of it is our primary aim.

License

Acutest is covered with MIT license, see the fileLICENSE.md or beginning ofacutest.h for its full text.

More Information

The project resides on github:

You can find the latest version of Acutest there, contribute with enhancementsor report bugs.


[8]ページ先頭

©2009-2025 Movatter.jp