AceUnit (Advanced C and Embedded Unit): a comfortable C code unit test framework. AceUnit is JUnit 4.x style, easy, modular and flexible. AceUnit can be used in resource constraint environments, e.g. embedded software development.
AceUnit is a unit test framework.
As such it provides a framework for you that you can use for writing your unit tests. As a framework for unit testing, AceUnit provides you with a hierarchical test structure. Test Suites consist of Test Suites (recursively) and Test Fixtures. A Test Fixture consists of Test Cases that can have a preparation (beforeClass / before) and cleanup (afterClass / after). AceUnit provides a Runner that will automatically traverse that hierarchical structure, run all the tests from it and log the results. AceUnit also provides a Generator that will derive the structural information required by the Runner from some simple conventions. The Generator replaces what in other languages (like Java) is provided by reflection. The Runner also includes an assertion and a logging infrastructure.
The most frequent use of AceUnit is to link AceUnit and tests written for AceUnit with the testling that you want to test with AceUnit. Because usually the testling already has a main()
and because (unlike e.g. in Java) the testling can have only one main()
, AceUnit does not, unlike e.g. JUnit, provide its own main()
. To run AceUnit, hook the invocation of the Runner in your program wherever it looks appropriate to you. An example would be somewhere within or near main()
, where the arguments are parsed.
Unit Tests in AceUnit are declared using annotations. This works pretty much the same as JUnit 4.x. JUnit 4.x can be considered industry reference for unit test frameworks. Much of AceUnit was modelled after JUnit 4.x.
The most important annotation that you will need is A_Test
. The A_Test
annotation tells AceUnit that a function is a unit test. Simply place this as a modifier before your function declaration, where you would write static in case you would declare your function static. This annotation will be the main interface between your unit tests and the AceUnit framework which will run them.
A_Test void someTestMethod() { // test case implementation for this test case goes here }
Assume your source is named MyTest.c. There are only two more things which you will need in your source. Both of these things are very easy.
#include "MyTest.h"
at the beginning of your source to include the required header file.java -jar AceUnit.jar MyTest >MyTest.h
to generate this header file.That's all what you need.
AceUnit.jar
? AceUnit.jar
is a Java program and the core for automation in AceUnit. It scans a test source code and will generate all required information that the AceUnit framework will need to run your tests. That information will be placed in the specified header file.
We know that running Java in a C environment is not convenient. As soon as the main framework is stable, there will be a C implementation of AceUnit.jar.
AceUnit supports the following styles of declaring unit tests:
A_Test void someTest() {...}
When declaring tests with annotations, the following annotations are available:
A_Test
A_Before
A_After
A_BeforeClass
A_AfterClass
A_Ignore
A_Test
. The following naming convention is recommended for the test names:
Writing AceUnit tests is easy, very easy.
fooOp()
and fooData
from foo.c
.fooTest.c
.Our scenario is developing a sorting algorithm. A sorting algorithm works if the list that it was sorting is sorted after the algorithm is done. To verify whether a sorting algorithm works, we need verify that its data was sorted. Both, the sorting and the verification need a comparator that determines whether an element should be sorted before or after another element.
The comparator works when it returns the expected result for each pair of values.
/** A Comparator compares two values and returns a comparison result. * @param o1 Pointer to first object to compare. * @param o2 Pointer to second object to compare. * @return Comparison result * @retval 0 if o1 and o2 are equal. * @retval <0 if o1 should be sorted before o2. * @retval >0 if o1 should be sorted after o2. */ typedef int8_least_t(*comparator)(const void *o1, const void *o2);
For testing purposes, we want to sort ints. So the first thing that we do is we test whether our comparator implementation works.
/** Comparator for int values. * @see comparator */ int8_least_t compareInt(const void *o1, const void *o2) { return 0; }
It is obvious that our current implementation is bogus. In test first programming, you intentionally start with a bogus implementation to verify that your test actually detects that the implementation is bogus.
/** Tests that #compareInt() works. */ A_Test void testCompareInt() { int n1; int n2; n1 = 0; n2 = 0; assertEquals("Comparing two equal numbers must return 0.", 0, compareInt(&0, &1)); n1 = 1; n2 = 2; assertTrue("Comparing 1 with 2 must return a value <0.", compareInt(&n1, &n2) < 0); n1 = 2; n2 = 1; assertTrue("Comparing 2 with 1 must return a value >0.", compareInt(&n1, &n2) > 0); }
AceUnit supports the following options:
#define ACEUNIT_STATIC_ASSERTIONS
static
. That way you have easy and flexible control over whether test methods are static or not.Declaring test methods as static has a significant advantage. You will be able to use the same name in different fixtures and still build and run them all together. For logging, it is not required that test methods have unique names. Logging will always also include the associated fixture (e.g. filename + line number). So you will always uniquely identify which test case failed, even if the names of the test cases are not unique.However, there are debuggers which will cause head ache if you try to debug functions which are static. Some tool chains (compilers / linkers) will not emit proper debug information for functions which are static.The pros and cons for static test methods are both very significant. Whether or not you want to use static depends on your environment. Because of that, we've left the choice to you.But we've prepared everything for you. Toggling between these two options is as simple as (not) defining this macro.#define ACEUNIT_EMBEDDED