tags:

views:

169

answers:

3

In an object-oriented language with inheritence and virtual functions, removing dependancies (e.g. database, API calls, etc) from your unit testing code can be as simple as encapsulating those dependancies in their own methods and then overriding those methods in a test class inheriting from the class to be tested.

However, I've run into a problem when trying to do something similar for procedural code (C specifically). Without inheritence I can't override those calls, so how does one provide similar removals of dependancies when unit testing procedural code?

One option would be to provide alternatives to the calls to these dependancies and surround them with #ifdefs, but the ideal approach would be to have the unit test apply to the same code as is going into the final build. Is this possible?

A: 

Conditional compilation would work, I've done it myself before. Takes discipline though, as it can become tempting to say "that bit's too hard to test" and then just not test it.

The other option, as I see it, would be to build special test versions of these libraries that contain your DB/API calls (they are in libraries, aren't they?). The test version could maintain the same interface (obviously a manual process since you have no object/interface to inherit from) but would call through to your unit test framework and be queryable via your unit test framework. I've done a similar thing for creating FitNesse versions of libraries, and I see no reason it couldn't work for unit tests.

Downside is that you'll need a separate executable and compilation per object under test, but depending on your test framework that may be the case anyway.

Good luck!

Rodyland
+6  A: 

Get Working Effectively with Legacy Code and read the chapter titled "My Application Is All API Calls".

Basically, Feathers describes two options:

The "linker seam": You can compile in a different set of implementations for the API calls you're trying to stub without having to change the code - basically change the makefile/.sln to compile in different implementations of the functions.

If that doesn't work, he talks about "skin and wrap", where you basically move all the API functions into an abstract base class, create two derived classes - a production and a unit-testing implementation with forwarding calls to the appropriate methods - then use dependency injection to pass the appropriate set of functions in.

Joe Schneider
Wouldn't "skin and wrap" not work for procedural/non-OO code?
dlanod
I absolutely second the aquisition of this book. It is the best thing you can do to educate yourself about how to work around (and out of) such situations. Everyone on our team now has a copy of this book and it's one of the two books we consider essential.
jkp
"skin and wrap" could be emulated by creating wrappers with #ifdefs inside for production or unit testing behavior.
Raim
A: 

I confront this quite often, its one of the biggest reasons that I'm so horribly behind in writing / maintaining test suites. There are some situations where conditional compilation will help, but not always.

For instance, if the suite should be testing a wrapper around a lower level interface to talk to a hypervisor, you need to test the suite under the hypervisor.. or write a bunch of clever macros / inline functions to 'fake' the hypervisor which in reality isn't testing anything at all.

Its a little easier to do this when working with legacy dbms APIs or the typical acme widget API.

For legacy tests, I strongly recommend downloading the tap (test anything protocol) from ccan and keep those tests isolated.

Usually, 80% of my tests have to be wrapped like this and its very tedious and frustrating to maintain. Hopefully, you only have a few oddballs to contend with.

Until someone writes some magic wand to extract test conditions and parameters from comments with sanity, you're stuck in the same fly paper.

Good luck, I really do sympathize.

Tim Post