C Unit Testing
2024-07-12
During my second college semester studying pure math, I was taking a very entry level Computer Science course that went over the basics of Java and C++. The only homeworks were programs that would be due in about a week or two, so not too complex, but also not "Hello, World".
Because of my extreme programming brainwashing, I needed to write all of homeworks with TDD. For the Java half of the course, the decision to use JUnit was pretty straightforward, especially because of my experience using it during my apprenticeship (see math language, HTTP Server).
However, during the C++ half of the course, the decision of what unit testing framework I would use was not so obvious. I did have some parameters, though:
- The easiest way to use it can't be Visual Studio (so basically it can't be Microsoft's C++ Test Framwork)
- It should be simple to set up with existing projects
- Not bloated
- I shouldn't have to manually add every test to a call list (essentially another file that tells the framework what tests to run)
- This ensures that TDD is fluid
After some light Googling, I was surprised to find nothing that met these criteria. So, I decided to make it myself.
specc
specc is what I came up with and it meets all of the criteria. It's written entirely in C, but also works for C++. The framework is modeled after Micah Martin's speclj for Clojure (which is based on RSpec). Here's some specc example code:
// in file example.c
#include "specc.h"
module(simple, {
describe("simple test", {
it("this is what a passing test looks like", {
should(1 == 1);
});
it("failing boolean test", {
should(1 == 0);
});
});
});
Then I compile with specc:
g++ -c specc.c
g++ -c example.c
g++ specc.o example.o
./a.out
And here's the output:
And that's it. That's all you have to do.
If you're curious about how it works, I'll try to shed some light:
Q: How does the program even run? You never wrote a main
function.
A: specc.c
contains the entry point which runs the tests.
Q: But how does specc.c
know what functions to call? You never copied the tests into a call list.
A: All of the functions shown in the example are really macros. The module
macro defines a function with the given name and body. Then, module
uses __attribute__((constructor)) __construct_
to add that newly defined function to a linked list of functions that specc.c
calls in main
. This is the only way I know is possible to call 'dynamically' defined functions in C. It also shifts the problem of dependency load order to the compiler.
Q: I have another question
A: It's open source