Live data from Hacker News

MinUnit – A minimal unit testing framework for C (2002)

jera.com

11–20 of 56 posts

Re: MinUnit – A minimal unit testing framework for C (2002)

#11
post #9

Why not just 1 line? Of course, the application will halt after one test fails, but some people like it this way. #include 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.

Comment won't be included the assertion message.

If you want to put arbitrary string there, you need something like this:

    assert("foo should be 4" && (foo() == 4));

Re: MinUnit – A minimal unit testing framework for C (2002)

#12
One thing I'm pretty doctrinaire about when it comes to this sort of thing is printing out more than just a simple message. Quite often, this makes the problem obvious with no need for deeper investigation.

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.

Re: MinUnit – A minimal unit testing framework for C (2002)

#13
post #11
post #9

Why not just 1 line? Of course, the application will halt after one test fails, but some people like it this way. #include 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.

Comment won't be included the assertion message. If you want to put arbitrary string there, you need something like this: assert("foo should be 4" && (foo() == 4));

Ah, thanks, I had forgotten.

Re: MinUnit – A minimal unit testing framework for C (2002)

#14
I'm not very familiar with C and macros so it took me a while to realize that this line:

  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 
  #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;
  }

Re: MinUnit – A minimal unit testing framework for C (2002)

#15
post #7

Earlier quoted context omitted.

For many novice engineers this is a huge revelation.

Really? Is it perhaps because of those libraries that do their best to make test call sites look like weird pseudo-English. Stuff like having a library function named it(): it(“should be confusing”, function() { ... })

You're describing "fluent programming" ("it().shouldEqual(x)..."). Unfortunately it's not just unit testing frameworks where this nightmare exists. Plenty of production code abuses the builder pattern to have this "it's like reading English" stuff.

Re: MinUnit – A minimal unit testing framework for C (2002)

#17
post #9

Why not just 1 line? Of course, the application will halt after one test fails, but some people like it this way. #include 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.

I was about the point this out! At the end of the day, unit testing is just contract assertions, and contract assertions are usually conditions! But actually this isn't really the crux of unit testing, the value of unit testing is to iterate and refactor your program for a more predictable and bug-free application, not just writing a bunch of conditions.

Re: MinUnit – A minimal unit testing framework for C (2002)

#18
post #15
post #7

Earlier quoted context omitted.

Really? Is it perhaps because of those libraries that do their best to make test call sites look like weird pseudo-English. Stuff like having a library function named it(): it(“should be confusing”, function() { ... })

You're describing "fluent programming" ("it().shouldEqual(x)..."). Unfortunately it's not just unit testing frameworks where this nightmare exists. Plenty of production code abuses the builder pattern to have this "it's like reading English" stuff.

It's not really that terrible of a concept -- some of the earliest high-level languages (e.g., COBOL) were designed to be "readable" by less-technical staff. It just requires careful design and structure.

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.

Re: MinUnit – A minimal unit testing framework for C (2002)

#19
post #10
post #7

Earlier quoted context omitted.

Really? Is it perhaps because of those libraries that do their best to make test call sites look like weird pseudo-English. Stuff like having a library function named it(): it(“should be confusing”, function() { ... })

It always really bothered me that there had to be so much syntactical sugar on top of testing frameworks. Unit testing should be written in exactly the syntax of the language you are working with. I don't want to have to carry that additional crap in my working memory while writing code. I can't stand having to look up Gherkin syntax just to write a few dumb tests. The only reason I see the usefulness of additional s…

Oh man, I wonder how your opinions would change if you used a language with great first class testing. Elixir's unit testing framework is part of the language, and is excellent. https://hexdocs.pm/ex_unit/master/ExUnit.html

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

Post reply on HN