A basic principle of engineering, software or otherwise, is “don’t reinvent the wheel.” I’m gonna anyhow.
Last week I wrote about wanting a very lightweight C unit testing solution, and, in my bullheaded way, wanting to write it myself rather than learn an already-existing system. It’s a commonplace in software engineering that the impulse to start over from scratch is one of the worst habits of programmers; one way or another, reinventing the wheel means doing lots of work you didn’t have to, usually to get a less satisfactory result. Either you end up hand-rolling something you should have just used a library for; or you look at a mass of legacy code full of cryptic comments and weird edge-case handling, think “whew, what a mess, how hard to maintain! better to just scrap it all and start fresh, so it will be cleaner and more elegant,” only to discover along the way that all those weird edge cases really exist, and end up with just as patchwork a code base as the one you meant to improve.
All that said, I’m doing it anyway. It’s not like I’m busy right now, and this is what’s engaging my brain.
So, suppose you have a project projname
, and you want to add unit testing. Your project layout looks like this:
~/projname$ ls
include/
Makefile
src/
~/projname$ ls include
include/foo.h
~/projname$ ls src
src/foo.c
src/main.c
You want to add unit tests for the foo
module, which is sensible because it’s pretty complex and you want to be sure all of it works exactly right. The foo
module looks like this:
foo.h
#ifndef _FOO_H
#define _FOO_H
int foo();
#endif /* _FOO_H */
foo.c
#include "foo.h"
int foo()
{
return 42;
}
To add unit testing with Scuttle (let’s say it stands for “simple C unit testing tool, limited edition”), you just need to put scuttle.h
in your include path and scuttle.sh
in your executable path, create a test/
directory, and add test_projname_foo.c
:
#include "foo.h"
#include "test_projname_foo.h"
#include "scuttle.h"
#include <stdio.h>
SSUITE_INIT(foo)
printf("foo suite init\n");
SSUITE_READY
STEST_SETUP
printf("foo test setup\n");
STEST_SETUP_END
STEST_TEARDOWN
printf("foo test teardown\n");
STEST_TEARDOWN_END
STEST_START(foo_return_true)
int i = foo();
SASSERT_EQ(42, i)
STEST_END
STEST_START(foo_return_false)
int i = foo();
SREFUTE(i == 69)
STEST_END
You’ll have noticed that no test_projname_foo.h
header exists: scuttle.sh
will generate that, as well as a test/test_projname_foo_gen.c
source file with some data structures, and test/Makefile
, for you.
~/projname$ ls test/
test/test_projname_foo.c
~/projname$ scuttle.sh
This is Scuttle, v1.0.0.
Working...
* found suite test/test_projname_foo.c
* generated suite header test/test_projname_foo.h
* generated suite data test/test_projname_foo_gen.c
* generated harness test/test_projname.c
* generated makefile test/Makefile
Done.
Type 'make -C test/ test' to build and run your test harness.
~/projname$ ls test/
test/bin/
test/log/
test/Makefile
test/obj/
test/test_projname.c
test/test_projname_foo.c
test/test_projname_foo.h
test/test_projname_foo_gen.c
~/projname$ make -C test/ test
[$(CC) output]
test/test_projname > test/log/test_projname.log
~/projname$ cat test/log/test_projname.log
This is Scuttle, v1.0.0.
Running test harness for: projname
Test suite projname_foo:
*** Suite passed: 2 / 2 tests passed.
*** 1 / 1 suites passed
Aside from the simple SASSERT(x)
and SREFUTE(x)
, Scuttle provides convenience macros SASSERT_NULL(x)
, SASSERT_EQ(x,y)
, and SASSERT_STREQ(x,y)
, as well as SREFUTE
versions of the same.
As of this writing, scuttle.h
is at or near v1.0 completeness, targets for scuttle.sh
‘s output are determined, and I’m beginning work on scuttle.sh
itself. More on those soon.