2011-04-09 19:54:42 +00:00
|
|
|
HOW TO USE
|
|
|
|
|
|
|
|
You can use CuTest to create unit tests to drive your development
|
|
|
|
in the style of Extreme Programming. You can also add unit tests to
|
|
|
|
existing code to ensure that it works as you suspect.
|
|
|
|
|
|
|
|
Your unit tests are an investment. They let you to change your
|
|
|
|
code and add new features confidently without worrying about
|
|
|
|
accidentally breaking earlier features.
|
|
|
|
|
|
|
|
|
|
|
|
LICENSING
|
|
|
|
|
|
|
|
For details on licensing see license.txt.
|
|
|
|
|
|
|
|
|
|
|
|
GETTING STARTED
|
|
|
|
|
|
|
|
To add unit testing to your C code the only files you need are
|
|
|
|
CuTest.c and CuTest.h.
|
|
|
|
|
|
|
|
CuTestTest.c and AllTests.c have been included to provide an
|
|
|
|
example of how to write unit tests and then how to aggregate them
|
|
|
|
into suites and into a single AllTests.c file. Suites allow you
|
|
|
|
to put group tests into logical sets. AllTests.c combines all the
|
|
|
|
suites and runs them.
|
|
|
|
|
|
|
|
You should not have to look inside CuTest.c. Looking in
|
|
|
|
CuTestTest.c and AllTests.c (for example usage) should be
|
|
|
|
sufficient.
|
|
|
|
|
|
|
|
After downloading the sources, run your compiler to create an
|
|
|
|
executable called AllTests.exe. For example, if you are using
|
|
|
|
Windows with the cl.exe compiler you would type:
|
|
|
|
|
|
|
|
cl.exe AllTests.c CuTest.c CuTestTest.c
|
|
|
|
AllTests.exe
|
|
|
|
|
|
|
|
This will run all the unit tests associated with CuTest and print
|
|
|
|
the output on the console. You can replace cl.exe with gcc or
|
|
|
|
your favorite compiler in the command above.
|
|
|
|
|
|
|
|
|
|
|
|
DETAILED EXAMPLE
|
|
|
|
|
|
|
|
Here is a more detailed example. We will work through a simple
|
|
|
|
test first exercise. The goal is to create a library of string
|
|
|
|
utilities. First, lets write a function that converts a
|
|
|
|
null-terminated string to all upper case.
|
|
|
|
|
|
|
|
Ensure that CuTest.c and CuTest.h are accessible from your C
|
|
|
|
project. Next, create a file called StrUtil.c with these
|
|
|
|
contents:
|
|
|
|
|
|
|
|
#include "CuTest.h"
|
|
|
|
|
|
|
|
char* StrToUpper(char* str) {
|
|
|
|
return str;
|
|
|
|
}
|
|
|
|
|
|
|
|
void TestStrToUpper(CuTest *tc) {
|
|
|
|
char* input = strdup("hello world");
|
|
|
|
char* actual = StrToUpper(input);
|
|
|
|
char* expected = "HELLO WORLD";
|
|
|
|
CuAssertStrEquals(tc, expected, actual);
|
|
|
|
}
|
|
|
|
|
|
|
|
CuSuite* StrUtilGetSuite() {
|
|
|
|
CuSuite* suite = CuSuiteNew();
|
|
|
|
SUITE_ADD_TEST(suite, TestStrToUpper);
|
|
|
|
return suite;
|
|
|
|
}
|
|
|
|
|
|
|
|
Create another file called AllTests.c with these contents:
|
|
|
|
|
|
|
|
#include "CuTest.h"
|
|
|
|
|
|
|
|
CuSuite* StrUtilGetSuite();
|
|
|
|
|
|
|
|
void RunAllTests(void) {
|
|
|
|
CuString *output = CuStringNew();
|
|
|
|
CuSuite* suite = CuSuiteNew();
|
|
|
|
|
|
|
|
CuSuiteAddSuite(suite, StrUtilGetSuite());
|
|
|
|
|
|
|
|
CuSuiteRun(suite);
|
|
|
|
CuSuiteSummary(suite, output);
|
|
|
|
CuSuiteDetails(suite, output);
|
|
|
|
printf("%s\n", output->buffer);
|
|
|
|
}
|
|
|
|
|
|
|
|
int main(void) {
|
|
|
|
RunAllTests();
|
|
|
|
}
|
|
|
|
|
|
|
|
Then type this on the command line:
|
|
|
|
|
|
|
|
gcc AllTests.c CuTest.c StrUtil.c
|
|
|
|
|
|
|
|
to compile. You can replace gcc with your favorite compiler.
|
|
|
|
CuTest should be portable enough to handle all Windows and Unix
|
|
|
|
compilers. Then to run the tests type:
|
|
|
|
|
|
|
|
a.out
|
|
|
|
|
|
|
|
This will print an error because we haven't implemented the
|
|
|
|
StrToUpper function correctly. We are just returning the string
|
|
|
|
without changing it to upper case.
|
|
|
|
|
|
|
|
char* StrToUpper(char* str) {
|
|
|
|
return str;
|
|
|
|
}
|
|
|
|
|
|
|
|
Rewrite this as follows:
|
|
|
|
|
|
|
|
char* StrToUpper(char* str) {
|
|
|
|
char* p;
|
|
|
|
for (p = str ; *p ; ++p) *p = toupper(*p);
|
|
|
|
return str;
|
|
|
|
}
|
|
|
|
|
|
|
|
Recompile and run the tests again. The test should pass this
|
|
|
|
time.
|
|
|
|
|
|
|
|
|
|
|
|
WHAT TO DO NEXT
|
|
|
|
|
|
|
|
At this point you might want to write more tests for the
|
|
|
|
StrToUpper function. Here are some ideas:
|
|
|
|
|
|
|
|
TestStrToUpper_EmptyString : pass in ""
|
|
|
|
TestStrToUpper_UpperCase : pass in "HELLO WORLD"
|
|
|
|
TestStrToUpper_MixedCase : pass in "HELLO world"
|
|
|
|
TestStrToUpper_Numbers : pass in "1234 hello"
|
|
|
|
|
|
|
|
As you write each one of these tests add it to StrUtilGetSuite
|
|
|
|
function. If you don't the tests won't be run. Later as you write
|
|
|
|
other functions and write tests for them be sure to include those
|
|
|
|
in StrUtilGetSuite also. The StrUtilGetSuite function should
|
|
|
|
include all the tests in StrUtil.c
|
|
|
|
|
|
|
|
Over time you will create another file called FunkyStuff.c
|
|
|
|
containing other functions unrelated to StrUtil. Follow the same
|
|
|
|
pattern. Create a FunkyStuffGetSuite function in FunkyStuff.c.
|
|
|
|
And add FunkyStuffGetSuite to AllTests.c.
|
|
|
|
|
|
|
|
The framework is designed in the way it is so that it is easy to
|
|
|
|
organize a lot of tests.
|
|
|
|
|
|
|
|
THE BIG PICTURE
|
|
|
|
|
|
|
|
Each individual test corresponds to a CuTest. These are grouped
|
|
|
|
to form a CuSuite. CuSuites can hold CuTests or other CuSuites.
|
|
|
|
AllTests.c collects all the CuSuites in the program into a single
|
|
|
|
CuSuite which it then runs as a single CuSuite.
|
|
|
|
|
2020-03-07 14:11:10 +00:00
|
|
|
A CuSuite also allows you to execute setup (for preparations) and
|
|
|
|
teardown (for cleanup) procedures that are shared among all tests
|
|
|
|
grouped in the respective suite. This is typically used to create
|
|
|
|
and/or setup your unit under test and simplifies the actual test
|
|
|
|
to be executed. A typical use is shown below in the section
|
|
|
|
"USING THE SETUP-TEARDOWN FACILITY".
|
|
|
|
|
2011-04-09 19:54:42 +00:00
|
|
|
The project is open source so feel free to take a peek under the
|
|
|
|
hood at the CuTest.c file to see how it works. CuTestTest.c
|
|
|
|
contains tests for CuTest.c. So CuTest tests itself.
|
|
|
|
|
|
|
|
Since AllTests.c has a main() you will need to exclude this when
|
|
|
|
you are building your product. Here is a nicer way to do this if
|
|
|
|
you want to avoid messing with multiple builds. Remove the main()
|
|
|
|
in AllTests.c. Note that it just calls RunAllTests(). Instead
|
|
|
|
we'll call this directly from the main program.
|
|
|
|
|
|
|
|
Now in the main() of the actual program check to see if the
|
|
|
|
command line option "--test" was passed. If it was then I call
|
|
|
|
RunAllTests() from AllTests.c. Otherwise run the real program.
|
|
|
|
|
|
|
|
Shipping the tests with the code can be useful. If you customers
|
|
|
|
complain about a problem you can ask them to run the unit tests
|
|
|
|
and send you the output. This can help you to quickly isolate the
|
|
|
|
piece of your system that is malfunctioning in the customer's
|
|
|
|
environment.
|
|
|
|
|
|
|
|
CuTest offers a rich set of CuAssert functions. Here is a list:
|
|
|
|
|
|
|
|
void CuAssert(CuTest* tc, char* message, int condition);
|
|
|
|
void CuAssertTrue(CuTest* tc, int condition);
|
|
|
|
void CuAssertStrEquals(CuTest* tc, char* expected, char* actual);
|
|
|
|
void CuAssertIntEquals(CuTest* tc, int expected, int actual);
|
|
|
|
void CuAssertPtrEquals(CuTest* tc, void* expected, void* actual);
|
|
|
|
void CuAssertPtrNotNull(CuTest* tc, void* pointer);
|
|
|
|
|
|
|
|
The project is open source and so you can add other more powerful
|
|
|
|
asserts to make your tests easier to write and more concise.
|
|
|
|
Please feel free to send me changes you make so that I can
|
|
|
|
incorporate them into future releases.
|
|
|
|
|
|
|
|
If you see any errors in this document please contact me at
|
|
|
|
asimjalis@peakprogramming.com.
|
|
|
|
|
|
|
|
|
|
|
|
AUTOMATING TEST SUITE GENERATION
|
|
|
|
|
|
|
|
make-tests.sh will grep through all the .c files in the current
|
|
|
|
directory and generate the code to run all the tests contained in
|
|
|
|
them. Using this script you don't have to worry about writing
|
|
|
|
AllTests.c or dealing with any of the other suite code.
|
|
|
|
|
2020-03-07 14:11:10 +00:00
|
|
|
USING SETUP-TEARDOWN FACILITY
|
|
|
|
Virtually every test should be written in a setup-test-assert-
|
|
|
|
tearodwn sequence. Although test and assertion vary depending on
|
|
|
|
the concrete test cases, it's quite common, that a group of tests
|
|
|
|
have a common starting point or common needs that have to be
|
|
|
|
setup before the test is executed. Those can be grouped in a suite
|
|
|
|
where they share their setup and teardown code.
|
|
|
|
|
|
|
|
Below is a code example showing how this can be done based on an
|
|
|
|
assumed vector arithmetics library that is supposed to be tested.
|
|
|
|
|
|
|
|
For the demonstration it is assumed, that a Point.h & Point.c
|
|
|
|
file exist implementing some vector arithmetics:
|
|
|
|
typedef struct PointXY {
|
|
|
|
int x;
|
|
|
|
int y;
|
|
|
|
}PointXY;
|
|
|
|
|
|
|
|
void POINT_add(PointXY *result, const PointXY *a, const PointXY *b) {
|
|
|
|
...
|
|
|
|
}
|
|
|
|
|
|
|
|
void POINT_sub(PointXY *result, const PointXY *a, const PointXY *b) {
|
|
|
|
...
|
|
|
|
}
|
|
|
|
|
|
|
|
In order to test those functions, your PointTest.c would look as follows:
|
|
|
|
#include "CuTest.h"
|
|
|
|
|
|
|
|
static void PointSetup(CuTest *tc) {
|
|
|
|
PointXY *uut = calloc(2, sizeof PointXY);
|
|
|
|
uut[0].x = 1;
|
|
|
|
uut[0].y = -3;
|
|
|
|
|
|
|
|
uut[1].x = 5;
|
|
|
|
uut[1].y = -7;
|
|
|
|
CuTestContextSet(tc, uut);
|
|
|
|
}
|
|
|
|
|
|
|
|
static void PointTeardown(CuTest *tc) {
|
|
|
|
PointXY *uut = CuTestContextGet(tc);
|
|
|
|
CuTestContextSet(tc, NULL);
|
|
|
|
free(uut);
|
|
|
|
uut = NULL;
|
|
|
|
}
|
|
|
|
|
|
|
|
static const CuTestFrame PointTestFrame = {
|
|
|
|
.setup = PointSetup,
|
|
|
|
.teardown = PointTeardown,
|
|
|
|
};
|
|
|
|
|
|
|
|
void TestVectorAddition(CuTest *tc) {
|
|
|
|
PointXY result;
|
|
|
|
PointXY *args = CuTestContextGet(tc);
|
|
|
|
|
|
|
|
POINT_add(&result, &(args[0]), &(args[1]));
|
|
|
|
|
|
|
|
CuAssertIntEquals(tc, 6, result.x);
|
|
|
|
CuAssertIntEquals(tc, -10, result.y);
|
|
|
|
}
|
|
|
|
|
|
|
|
void TestVectorSubstract(CuTest *tc) {
|
|
|
|
PointXY result;
|
|
|
|
PointXY *args = CuTestContextGet(tc);
|
|
|
|
|
|
|
|
POINT_sub(&result, &(args[0]), &(args[1]));
|
|
|
|
|
|
|
|
CuAssertIntEquals(tc, -4, result.x);
|
|
|
|
CuAssertIntEquals(tc, 4, result.y);
|
|
|
|
}
|
|
|
|
|
|
|
|
CuSuite* PointTestSuiteGet() {
|
|
|
|
CuSuite* suite = CuSuiteNewWithFrame(&PointTestFrame, NULL);
|
|
|
|
|
|
|
|
SUITE_ADD_TEST(suite, TestVectorAddition);
|
|
|
|
SUITE_ADD_TEST(suite, TestVectorSubstract);
|
|
|
|
|
|
|
|
return suite;
|
|
|
|
}
|
|
|
|
|
|
|
|
The second parameter for CuSuiteNewWithFrame can be used to preset
|
|
|
|
the context to the test respectively the setup function that is called.
|
|
|
|
|
|
|
|
A context modified during test execution is always brought back to the
|
|
|
|
suite. Hence be careful to not introduce test interdependencies.
|
2011-04-09 19:54:42 +00:00
|
|
|
|
|
|
|
CREDITS
|
|
|
|
|
2012-05-30 15:27:27 +00:00
|
|
|
These people have contributed useful code changes to the CuTest project.
|
|
|
|
Thanks!
|
|
|
|
|
|
|
|
- [02.23.2003] Dave Glowacki <dglo@hyde.ssec.wisc.edu>
|
|
|
|
- [04.17.2009] Tobias Lippert <herrmarder@googlemail.com>
|
|
|
|
- [11.13.2009] Eli Bendersky <eliben@gmail.com>
|
|
|
|
- [12.14.2009] Andrew Brown <abrown@datasci.com>
|