As our project’s code has grown, and the use cases have become more complex and hard to remember, I have shut out the practice of writing tests as a method to ensure the quality of my code.
Our application runs as a plugin within many different host applications, and so our deployment environment is next to impossible to predict. As such, we are spending most of our time just writing the new code, compiling the entire plugin (500+ source files), and debugging it straight up to get everything to work. basically, we don’t have time for tests.
I know a bunch of you will say “but the time gained by forgetting tests is lost in maintaining the bugs you will create.” Well, there is actually a window created by excessive feature requests and tight time constraints where you just don’t have the time to deal with tests. You get it working, you submit the code, you deal with it later.
But, after hiring a new developer to help handle my load, I have started to have a little more time here and there and I have started to inkle out a few tests here and there. Writing test is a skill that can become second nature with practice. Not only does it help verify your code, but it gives you a chance to massage it’s flexibility by looking at it from a new perspective.
We don’t use a sophisticated testing framework or anything, but one technique that I find very helpful is to slap the tests for a class into a static class right a the bottom of the .cpp file, and put the test code right in the constructor. That way your code is run before your app code (during your runtime’s static initialization phase) muddies up static data.
Here is an example:
class test_UValArgs
{
public:
void test(const UValArgs &args, const char *formats[])
{
for(int i=0; formats[i] != 0; ++i)
{
const char *fmt = formats[i];
try
{
args.checkTypes(fmt);
}
catch(ArgumentError &e)
{
printf("test_UValArgs: FAILED "%s": %sn", fmt, e.what());
}
}
}
test_UValArgs()
{
// UVal e;
// e.exc_setException("ArgumentException", "hey you guys");
// printf("%sn", e.toCString());
UValArgs a0; a0 << (int) 1 << (int) 2;
const char *f0[] = { "ii|i", 0};
test(a0, f0);
UValArgs a0; a0 << (int) 1 << (float) 2 << (double) 3;
const char *f0[] = { "ifd", "if|d", "if", "i", 0 };
// test(a0, f0);
UValArgs a1; a1 << "hey" << (int) 3;
const char *f1[] = { "s", "i", "si|f", 0 };
// test(a1, f1);
UValArgs a2; a2 << "hey" << "you";
const char *f2[] = { "s|s", 0 };
test(a2, f2);
}
};
static test_UValArgs _test_UValArgs;
When you are done with the test you can just use a ‘#if 0 .. #endif’ or a comment block to exclude it from your code.
What I usually do is build test executables and have them run directly as part of the build process. If the tests fail the executable fails, and so does the build. That way you can’t build software with broken tests (which hopefully indicate broken code). I recommend UnitTest++ its super simple to use (just a few macros), and has automatic test gathering. See the example below:
// test.cpp
#include
TEST(FailSpectacularly)
{
CHECK(false);
}
int main()
{
return UnitTest::RunAllTests();
}
That the way in the late ’80 people created “tests” in C code. If you’ve got chance to deal with legacy code you’re likely to come across that style. It has been superseeded by splitting tests in other files for a good reason (flexibility mainly).
This comment has been removed by the author.
I’ve had good luck with CUTE on my C++ projects. The Eclipse plugin for it is a nice extra as well.
http://r2.ifs.hsr.ch/cute/wiki/WritingUnitTests
I’d also recommend UnitTest++.
It’s super easy to
1) get it hooked into our IDE’s to build our test apps automatically to prevent devs from ‘forgetting’ to run the tests before checking in.
2) instead of a developer-launched build, I wrote a wrapper around UnitTest::RunAllTests() so that the automated build can define a certain environment variable to have it output an XML report that can be sucked into Hudson’s test reports.