MinUnit – A minimal unit testing framework for C (2002)
jera.com
jera.com
#include <assert.h>
Usage: void test_foo() {
assert(foo() == 4 /* foo should be 4 */);
}
Output would be something like Assertion failed at "foo() == 4 /* foo should be 4 */"
If you run the application in a debugger like GDB, you can see the frame when the abort trap is called.If you want to put arbitrary string there, you need something like this:
assert("foo should be 4" && (foo() == 4));I'm also tempted to say that if you need to use GDB to figure out where your tests have failed exactly your framework is not particularly user friendly. Normally when a test fails it should be pretty clear where it failed and why. The root cause of the issue might be further away of course but then again assertions won't help with that either.
>I'm also tempted to say that if you need to use GDB to figure out where your tests have failed exactly your framework is not particularly user friendly.
Am I missing something?
#include <assert.h>
int main(void)
{
assert(0);
return 0;
}
When using clang I get a clear message that includes the filename, the line, and the function: assert.bin: assert.c:5: int main(void): Assertion `0' failed.I similarly wrote a simple C testing framework for a little project I was working on, based on assertions. The framework uses signal handlers for aborts and segfaults, and then setjmp/longjmp to handle resuming the test suite at the next test case. This has the particularly nice effect of turning segfaults into (marked as such) test failures, instead of just terminating everything. It probably wouldn't be too hard to fit custom messages in too, but I hadn't felt the need yet.
I agree that for a decent test framework it might be the best approach but I think at this point we're no longer talking about "a minimal unit testing framework" which was the point of TFA.
To do this I have a bunch of macros like this:
/* check A and B are equal. */
#define EQ_II(A,B,M) (CheckEQII((A),(B),M,#A,#B,__FILE__,__LINE__))
You use it like this: EQ_II(i,3,"blah blah blah");
CheckEQII looks roughly like this: void CheckEQII(int64_t a,int64_t b,const char *message,const char *a_str,const char *b_str,const char *file,int64_t line) {
if(a!=b) {
printf("%s:%" PRId64 ": test failed: %s\n",file,line,message);
printf(" Values not equal.\n");
printf(" Got expr : %s\n",a_str);
printf(" Wanted expr : %s\n",b_str);
printf(" Got value : %" PRId64 " (0x%" PRIx64 ")\n");
printf(" Wanted value: %" PRId64 " (0x%" PRIx64 ")\n");
DEBUG_BREAK();
exit(1);
}
}
(DEBUG_BREAK breaks into the debugger if you're running in the debugger.)The FILE:LINE notation is probably clickable in your favourite text editor. (For VC++, use "FILE(LINE):". Just do #ifdef _MSC_VER or something.) Very convenient if you run tests as part of the build.
And you can flesh it out for strings, arrays, floats, doubles, and all the rest. You can fit everything you need into about 500 lines.
This isn't quite as impressive as the 3 lines here, but compared to something like Catch - which is a huge amount of C++ code, crazy C++ code to boot, that adds literally seconds to your build time - and, no, the fact that seconds is a drop in the ocean in C++land is not an excuse - it's in the same ballpark. At least, its extra utility should prove, over the course of a project, in my view, commensurate with the extra LOC.
You just `assert` an expression, and on failure it'll break down each sub-expression and show whatever it yielded, plus by default it captures all output (stdout and stderr) and prints it out only on failure (so logging and other console output is not painfully annoying while testing) and the fixtures systems is just fantastic and parameterised tests make for much clearer data-driven (/table-driven) tests and the tests selection at the CLI is awesome.
And all the complexity is in the test framework and runner, all you do is write
def test_thing(a_fixture):
assert foo(bar) == a_fixture.some_thing()
or whatever.And that is the problem.
I have met teams that have gone into analysis paralysis about which unit test framework to use.
I have met teams where one of the impediments to unit testing was "we don't have time to learn something that complicated, we don't understand why all this stuff is necessary".
Answer:
It isn't necessary. Get off your ass and start unit testing with the simplist framework possible.
It's more important, way way more important to just do it than fuss about the framework.
Like TFA.
Once you have actually done that for awhile, someone will say, "Wouldn't it be nice if..."
And _that's_ the time to introduce a framework. Or grow what you have.
On a C project I elected to grow, the nice thing about it is test setup and teardown? It's exactly the same thing as process set up and teardown.
So valgrind will tell you _exactly_ if anything leaked or was uninitialized!
There's various reasons why one might be doctrinaire about stuff. One common reason: you're an arsehole. Another one: you're an engineer, and this was what you were trained to do. A bonus third: you had it beaten into you by bitter experience.
Me? Well, I will say that I'm not clever enough to be an engineer.
it(“should be confusing”, function() { ... })
The only reason I see the usefulness of additional syntax just for tests is the case to have non-technical people writing tests. From my experience, this is a terrible idea.
Its not really about syntactical sugar, and more about being able to see the data the failed, and make the testing experience easy so that you can write tests with less effort
YMMV, the BDD implementations I've seen so far did not convince me of its value to put it mildly.
IMO, it only falls apart (and becomes such a nightmare) because of the imprecision of spoken & written word. Computers don't understand idioms, colloquialisms, and the like, and so non-technical staff are tricked into thinking a language/program can do more than it actually can do.
It’s sample data, verification, analysis, and reporting. The if statement is just the verification step.
$ curl -s --head http://www.jera.com/techinfo/jtns/jtn002.html | grep ^Last
Last-Modified: Fri, 06 Sep 2002 07:16:46 GMT mu_assert("error, bar != 5", bar == 5);
will return out of the function before reaching the next line.For anyone else like me, I put together the "inlined" version of the code to help me understand what is happening:
#include <stdio.h>
#include "minunit.h"
int tests_run = 0;
int foo = 7;
static char * test_foo() {
do {
if (!(foo == 7))
return "error, foo != 7";
} while (0);
return 0;
}
static char * all_tests() {
do {
char *message = test_foo();
tests_run++;
if (message)
return message;
} while (0);
// ... more tests here
return 0;
}I obviously vouched for the comment, but yeesh.
--
[0] http://www.throwtheswitch.org/unity/
[1] https://gitlab.com/oliver117/blut/blob/master/test/test_math...
unittest { ... }
and they all run during compilation.
Edit:
URL to version() docs:
Here is mine Minimal Unit Testing FW for C:
I wrote a minimal C test framework too: https://github.com/codeplea/minctest It's got some real-world usage. Still only just one header file, but it'll time each test and if an equal assertion fails it'll print the variable values.
Apologies, its been a while since I've touched C/C++