I’ve Been Framed!: Solving the OCUnit App Test Problem with Frameworks

Wolf (a.k.a. “Jon”) pointed out to me that I don’t want to compile my tests into my application all the time, which my last post assumed I could do. Why? Because with the amount of tests you’re supposed to write, this could easily double the size of your application. But I don’t want target A that builds my code, and target B that builds my code+tests. I only want to build the code once!

The solution is to put as many of the tests as you can in a separate embedded framework. When you run or start a debugging session with the app, the framework is loaded at startup. OCUnit automatically runs the tests. Adding the tests is a copy phase, not a rebuild. Stripping the tests is as simple as not copying the framework into the application bundle. (This only works if you weak-link the framework; if you link the test framework regularly, then you’ll get linker errors when you run the app without it.)

I tried this with my much-abused TestExtras example from OCUnit, and immediately ran into a problem. The test framework needs to refer to the objects it’s testing, but you can’t link a framework against an application. (At least, not that I know of.) To get around this, my TestExtras application has two frameworks. One for the classes used by the application. Another for the tests on those classes.

I added the Person class from the first example in the OCUnit tutorial to my app framework, called Person.framework. I then added Person’s test class, PersonTest, to my Test.framework.

I linked against SentTestingKit.framework both in my Test.framework and in TestExtras.app itself, so that I can run both TestPerson and the tests TestExtras was originally created for, a CompletionTest instance instantiated in MainMenu.nib, at the same time. It worked! –Although the failure of the CompletionTest tests meant that the application quit immediately.

I’m pretty happy with this, so it’s time to try it out for real.

5 comments

  1. Uli Kusterer

    Andrew,

    do you think unit testing will really save you enough trouble to balance the problems you’ll have with this mixed frameworks setup?

    I’m still waiting to get on the unit testing bandwagon. Looks like I’ll be giving UnitKit a try in Filie. One of the GNUstep guys just started adding unit tests to his copy, and the class prefix goes well with all my other classes 😉

    Still, I can’t help but worry that with all this additional stuff you need to turn these tests on and off, it’ll get really annoying really quick keeping all the dependencies straight, and my experience tells me that when your debugging code becomes a liability, you’re doing something wrong…

    Anyway, good luck, and keep us current on all the positive and negative aspects of unit testing. The benefits sound promising.

    Cheers,
    — Uli

  2. Mark Dalrymple

    I’ve been playing with UnitKit and TDD for a MilleBornes game in my spare time. I’ve only been using TDD for the actual game/rules logic, and it’s been working pretty nicely.

    For another app that’s heavily graphics oriented, I haven’t found a good way to use TDD with it.

    Cheers,
    ++md

  3. Andrew Pontious

    Uli —

    >do you think unit testing will really save you enough trouble to balance the problems you’ll have with this mixed frameworks setup?

    We’ll see, won’t we?

    Also, I’m not sure about the “will have” part of your question. The whole point of my putting in the work at the beginning was to find something that would make future maintenance *easier*.

    If I had to maintain two targets or styles with exactly the same settings *except* for the testing stuff, then that would be an unwanted dependency.

    DIviding things into frameworks will be annoying at first, but it really should “just work” after I’m done setting it up.

  4. Ken Ferry

    Andrew –

    You ought to be able to reference objects in the main application from the framework at the cost of some compile time checking (in the test framework only). Try adding “force_flat_namespace -undefined suppress” to the “Other Linker Flags” in Xcode, then add the header files from the main project to the test framework.

  5. Ken Ferry

    (Addendum)

    I never tried the above with a framework, but I know it works with a bundle. So, if the above fails, you could make your test framework into a test bundle. You’d have to add a small amount of code to the main application to load the test bundle if it exists.