What you need is something more like this. (I'll write out one case, and you can figure out the rest.) It's more lines, but I don't think you'll feel like it's bad value per LOC.
For the actual condition you write in your code, you want a macro that expands to something with __FILE__ and __LINE__, and uses the stringizing operator to get the LHS and RHS as strings. Here's one that compares to ints (hence II) for equality (hence EQ).
#define TEST_EQ_II(A,B) (DoTestEqII((A),#A,(B),#B,__FILE__,__LINE__))
The function that implements that. Note that because C is stupid, the int parameters basically have to be int64_t... \this isn't really a problem in practice. void DoTestEqII(int64_t a,const char *a_str,
int64_t b,const char *b_str,
const char *file,int64_t line)
{
DoTestII(a,a_str,b,b_str,file,line,a==b,"==");
}
Clearly I'm all about the layering, and here's the next layer. void DoTestII(int64_t a,const char *a_str,
int64_t b,const char *b_str,
const char *file,int64_t line,
int result,const char *oper)
{
if(!result) {
Print("%s%s%" PRId64 "%s: test failed\n",file,LINE_PREFIX,line,LINE_SUFFIX);
Print(" Expected expression: %s\n",a_str);
Print(" Operator: %s\n",oper);
Print(" Actual expression: %s\n",b_str);
Print(" Expected value: %" PRId64 " (0x%" PRIx64 ")\n",a,a);
Print(" Actual value: %" PRId64 " (0x%" PRIx64 ")\n",b,b);
Break();
}
}
For VC++: #define Break() __debugbreak()
#define LINE_PREFIX "("
#define LINE_SUFFIX ")"
Then make Print print via OutputDebugString (VS2015 does C99-style snprintf, so this is easy to arrange perfectly), so the output goes to the output window. Visual Studio treats output window lines beginning like "FILE(LINE):" as referring to source locations, so when you get a failing test you can just tap F8 - I think? I'm not in Windows right now - and you'll be taken to the line and file with the error.For POSIX-type stuff, e.g., Emacs:
#define Break() asm volatile ("int $3\n") /* if you get a build error here, your computer is shit. */\
/* Throw it away, and buy a proper one, with an x64 CPU. */
#define LINE_PREFIX ":"
#define LINE_SUFFIX ""
And make Print print to stderr. I typically run my tests as part of the build process, invoked via M-x compile, and Emacs then picks up the FILE:LINE: markup when you use M-x next-error and M-x previous-error.(If you use vim, or whatever... I'm sorry. But lesser editors often understand the standard FILE:LINE: markup, so even when you're slumming it, you're not, like, totally slumming it.)
You can probably figure out how this extends from int equals int to int not equals int, int less than int, blah blah... and from there to string vs string, and double vs double, and so on. It's not really the operations that are the key thing, because of course they're easy to add; for my money the benefit comes from auto-generating the output text from the condition, and from having it integrate tidily with the calling process - Visual Studio, Emacs, other (notepad, vim, etc.) - in such a way that you can jump immediately to the source location when a test fails.
Where the article and I do agree: the conclusion, all of it. Writing a unit test system isn't hard, and writing your own isn't crazy. It probably won't take you even one hour.
P.S. if you like C++, you can go one better, and have the output text auto-generated without even having to say what sort of comparison it is: https://github.com/philsquared/Catch
Only downside is that while you'll save one hour not writing your own, you'll lose ten hours waiting for it to compile. Also, good luck trying to write test programs that have command line options. This is what I believe they call "technical debt".