views:

121

answers:

3

I have a library I wrote with API based on opaque structures. Using opaque structures has a lot of benefits and I am very happy with it.

Now that my API are stable in term of specifications, I'd like to write a complete battery of unit test to ensure a solid base before releasing it.

My concern is simple, how do you unit test API based on opaque structures where the main goal is to hide the internal logic?

For example, let's take a very simple object, an array with a very simple test:

WSArray a = WSArrayCreate();
int foo = 5;
WSArrayAppendValue(a, &foo);
int *bar = WSArrayGetValueAtIndex(a, 0);

if(&foo != bar)
    printf("Eroneous value returned\n");
else
    printf("Good value returned\n");

WSRelease(a);

Of course, this tests some facts, like the array actually acts as wanted with 1 value, but when I write unit tests, at least in C, I usualy compare the memory footprint of my datastructures with a known state.

In my example, I don't know if some internal state of the array is broken.

How would you handle that? I'd really like to avoid adding codes in the implementation files only for unit testings, I really emphasis loose coupling of modules, and injecting unit tests into the implementation would seem rather invasive to me.

My first thought was to include the implementation file into my unit test, linking my unit test statically to my library.

For example:

#include <WS/WS.h>
#include <WS/Collection/Array.c>

static void TestArray(void)
{
    WSArray a = WSArrayCreate();
    /* Structure members are available because we included Array.c */
    printf("%d\n", a->count);     
}

Is that a good idea?

Of course, the unit tests won't benefit from encapsulation, but they are here to ensure it's actually working.

+2  A: 

I would test only the API, and focus on testing every possible corner case.

I can see the interest in checking that the memory structures hold what you expect. If you do this you will be tightly coupling the tests to the specifics of the implementation and I think creating a lot of long-term maintenance.

My thought here is that the API is the contract and if you fulfil that then yoru code is working. If you change the implementation later then presumably one of the things you need to know is that the contract is maintained. Your unit tests will verify that.

djna
A: 

Your unit tests shouldn't depend on the internal details of the code that they're testing. Your initial example is actually a pretty good test. It does one thing, then verifies that the state of the object is as expected.

You'd want to create tests that verify the behavior of other parts of the API as well, of course. Fir example, in the array case, you'd want to have test cases that verify that the length if the array is reported correctly after adding and removing items.

Writing unit tests that depend on an exact match with a known good memory snapshot is generally a really bad idea, in that every implementation change will cause the tests to fail. If you do decide to use snapshot-based tests, make sure there's an easy to regenerate the "known good" snapshots.

Mark Bessey
A: 

I would suggest splitting the unit testing into white box and black box unit testing. The white box testing focuses on the API interface, and correctness of results, while the black box testing focuses on the internals.

To facilitate this I use a private header (e.g. example_priv.h), with a #ifdef TESTING for function prototypes that are other internal / private. Thus you can exercise internal functions for unit testing purposes, without exposing them in the general case.

The only loss with this method is losing the ability to explicitly label the internal functions as static in their source file.

I hope that is helpful.

mctylr