cutest/README
2020-03-07 15:11:10 +01:00

304 lines
9.9 KiB
Plaintext

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.
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".
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.
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.
CREDITS
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>