Sometimes logic tests will crash because the code being exercised is trying to access things that don’t exist unless the application is loaded. For example, simply trying to instantiate an instance of UIFont will work in an OCUnit Application test, but crash in an OCUnit Logic test.
Another example I recently ran into was during testing of the AppDelegate. The application:didFinishLaunchingWithOptions created by the Empty Application project template can be run and tested with logic tests, but the Single View project template’s version will crash, requiring it to be run in an application test.
The difference between a logic test and an application test is that the application test target is configured to execute in the context of the loaded application. This is done by setting the bundle loader and test host build settings. The Apple developer documentation Setting Up Application Unit Tests describes in detail how to do this.
It is interesting to note, that it was the call to makeKeyAndVisible that crashed, and that call is present in both versions. It wasn’t until after I created a view and made it the rootViewController prior to calling makeKeyAndVisible that it started crashing.
This creates somewhat of a problem for TDD. The basic philosophy behind TDD is to create new tests, watch the tests fail, then implement code to make the tests pass. There is an expectation that new code added will not break previously passing tests unless a mistake is made. But this is exactly what happened.
So I’m currently working on a workflow where I create all tests as Logic tests, then move them to the Application test target if/when the application/bundle is needed. Both targets can be added to the Cmd+U Test action, keeping things fairly simple.
For an example of this code, refer to my HowsMyFuel project on Github. Take a look at the commit comments to see how the code progressed from Logic tests only, to crashing Logic tests, to finally adding Application tests and moving the failing test over.
Update 6/5/13: I now use Application tests almost exclusively. Once the situation described above is understood, then the change in behavior can be acknowledged, and tests updated appropriately. This may even result in an increased understanding of the run time behavior, which is a good thing.