This seems outright absurd at first blush. But when one has written more unit test code than the amount of code being tested, wouldn’t the argument for unit testing then also apply to the unit test code itself?
One of the observations that I made earlier is that refactoring for clean code as advocated by Robert C. Martin would greatly simplify unit testing. So let me give you an example, using some code I’m actually working with, not just something I threw together to support my argument.
This is from my personal motorcycle app that I use to display navigation, time, temp, and weather forecast while riding. I currently have a Weather model file that provides basic weather properties such as current temp. During initialization of this object, it needs to setup a timer to periodically check for any changes in the weather, as well as register to receive location notifications. This particular object was written long ago, before I became a 100% code coverage fanatic. I didn’t know how to test the init method back then. But now that I do, I’m going to refactor this code both to make Uncle Bob happy, as well as to add full unit test coverage for it.
So here’s the original init method:
This code looks pretty typical for an init method, and there isn’t anything functionally wrong with it. Ignoring the NSLog statement, and standard call to super and return of self, it does 3 things:
- Set the default current location. Location is needed for getting local weather, and since I’m on a motorcycle, it will change periodically. I initialize it with a constant that is the zipcode where my home is located.
- Setup a timer to fire off every 5 minutes to update weather.
- Register to receive location change notifications from a Location object I also created.
I’ve used comments to identify what is happening on 2 of these things, the 3rd being so obvious I didn’t bother. However, I’ve learned over the past few years that comments should be considered smells. I believe that a better approach is to extract the commented code to a separate method, and name the method using the text from the comment. Let’s see how that also helps make unit testing easier.
Let’s extract the first thing to its own helper method: setLocationToDefault. We can use the refactor command as shown here:
Following that, we’ll do the same thing for the other 2 things that init is doing. This results in the following refactored code:
Now our code reads like prose, and the extracted helper methods do exactly what you would expect. These are 2 attributes of good code.
Create a class extension file to contain the declarations of the helper methods. This file can then be #imported in the unit test case file, but kept private to normal class users:
Prior to making these changes, our unit tests for init would require verifying that:
- The current location (lastLocation property) is set to our default value.
- The timer was set to callback every 5 minutes
- A location notification is set.
The first item is very easy to test. But the inclusion of the other 2 things in init could complicate and make the first test unreliable and/or outright fail. What if a side effect of the other 2 causes the current location to change? There is a potential race condition here. Even if it didn’t break at first, it might in the future as changes are made to the code. This code is fragile.
In this particular case, it probably isn’t a problem. But it shows the type of problem that unit testing can encounter whenever a method does multiple things. In Clean Code, Uncle Bob admonishes us to have each method do exactly 1 thing (the Curly principle?). That may sound a bit extreme, but look how it has simplified things here, and made things safer to extend or change later.
After refactoring, the unit tests for init can focus on making sure that it calls the methods that do these things, and not worry about exactly how they are done, how long it takes, or whether they have conflicting effects. We’ll use a partial mock object to do this, as shown here:
In the code above, self.weather refers to the test weather object created in the setUp method. The partial mock created will intercept the expected calls, but pass anything else on to the test object. This code therefore tests only the code in the init method. The other unit tests that verify that the 3 things happen correctly would call the helper methods directly, thereby eliminating the possibility of any conflicting side effects.
A common construct in Objective-C code is alloc init:
[[NSSomeObject alloc] init]
This line allocates memory and initializes an Apple framework object (as indicated by the NS prefix). So how do we test this? Should we test this?
I will assert that we should not test this. It is Apple’s code, let them test it. Our unit tests should be concerned with testing the code that we write. So what do we do with our code that includes such constructs?
We have at least a couple options. We could essentially ignore that its there, and unit test the code containing it. This would be suboptimal from a performance perspective, and would cause problems if the init code has other dependencies, for example the presence of other files, data models, etc.
I suggest that a better approach is to extract the code to a separate method. Once isolated there, the method can be intercepted using a partial mock, and a mock object returned instead. I’ll provide an example of doing this in an upcoming Core Data example.
I’ve been working through the Xcode project template code to add unit test to it. Trying to add unit tests to the unmodified, template generated boilerplate code is not easy. Some might say impossible or not worthwhile. However, once I started refactoring the code using Uncle Bob’s recommendations, the code magically became easy to unit test.
I highly recommend Clean Code. It will make your life easier.
Ok, let me start by repeating one of my mantras: don’t unit test Apple’s code. We’re going to assume that Core Data works as designed. And we have pretty high confidence that this is the case because I have been told that the Core Data group actually unit tests their code.
What we need to unit test is the code that we write. I would also add to this that we should write tests for the Core Data boiler plate code. This is the code that is created by the Xcode project templates when we create a new project. I am fairly certain that Apple does not unit test their template code, judging by the times that unit tests fail to work upon a new release of Xcode. Since the boiler plate remains essentially the same for each project, it makes sense to create a reusable starting point project that includes unit tests and reuse that. I’ve created one of these for the Master-Detail Application and will be sharing it publicly once I decide how and where to put it. Even better would be a new set of project template files to do so, but at the rate that these change, I’m not sure I’m willing to sign up to do that 🙂
In the boilerplate Master-Detail application code, the Core Data stack is instantiated in the appDelegate. So the tests for that code should appear in the appDelegate’s unit tests file. Once again, let me state that we don’t want to test that Core Data works as intended. We want to verify that our code works as intended.
Graham Lee, author of Test-Driven iOS Development, discusses using unit tests to perform what I would consider Core data integration testing. This is good information, and I won’t duplicate that here. Go buy the book. But since one of the premises of unit tests is that they execute quickly, we need to consider carefully whether or not to use the actual data model during unit tests, or to delegate that to integration tests. This of course will depend on how big the model is. A good approach might be to use the actual data model, and if unit tests begin to take too long, then revert to a test model.
I’ll stop here for this post, and add notes regarding specific Core Data components going forward. Please feel free to drop me a note regarding your experiences with unit testing Core Data. Many developers believe that Core Data code cannot be unit tested. I’m on a quest to dispel that notion.
OCMock is a framework for using mock objects in Objective-c projects. If you’re not familiar with mock objects, refer to mockobjects.com. For more detailed instructions, refer to the mock objects section in the Unit Testing iOS Applications in Xcode 4 course on lynda.com. The framework and source code are available on ocmock.org and also on github. Here are the basic steps for adding it to your project:
- Download the OCMock dmg file from the ocmock.org download page.
- Double-click the dmg file to extract the files from it.
- Add a Libraries group to your project at the top level.
- Select the Libraries group, and select Files -> Add Files to <project name> (or Cmd+Opt+A)
- Navigate to the OCMock files just downloaded. These will appear under “Devices” on the left.
- Select the libOCMock.a file and OCMock folder inside the iOS folder.
- Set the “Copy items into destination group’s folders” checkbox.
- Set the “Add to targets” checkboxes for the projects unit test target(s).
- Clear the main project executable target.
- Click Add.
- Open the build settings for the test target, and add “$(SRCROOT)/OCMock” to the Header Search Paths setting.
- Also add “$(SRCROOT)” to the Framework Search Path.
- Add -all_load to the Other Linker Flags build setting for the test target also.
One of the questions that will arise once you start really using unit testing is “How much of my code is actually tested?”. iOS and CoverStory make determining this fairly simple.
During compile, setting the profile-arcs and test-coverage flags and including the libprofile_rt.dylib causes the compiler to generate gcov records that report statistics about test coverage.
CoverStory can then be used to display the information from those records. Refer to the instructions for CoverStory for details on how to download, install, and run CoverStory.
Now I need to investigate how to make this work when using GHUnit…
Classes often have helper methods that are called by the public methods in the class. In order to keep the public interface clean, these helper methods should not be declared in the header file (.h). Instead, these should be declared in a class extension.
A class extension is a category defined on a class without a name. Sometimes these are referred to as anonymous categories. Methods declared in a class extension must be defined in the implementation (.m) file for the class.
In order to access these private methods declared in the class extension, simply move the class extension into a separate .h file, and include it in both the implementation file as well as the unit test file.
Be sure to name the class extension header file something that makes it clear that it should not be used only for testing; for example, include the word “private” or “fortesting”.
- View controllers typically contain most of the developer written code for an iOS project. Choosing not to test view controllers is essentially making the decision to not test most of the code you write.
- ViewController unit tests should test the methods of the view controller. This means that each test method in the ViewControllerTests.m file should be testing one of the methods of the view controller.
So for example, I would expect there to be a test method named “testViewDidLoad” that tests whether viewDidLoad works as intended. This means verifying that the code you put in viewDidLoad does what you expect the it to do.
- Unit testing a view controller nearly always means writing the view controller methods differently. In untested view controllers, viewDidLoad usually has a LOT of code thrown into it. This works, but is not very testable. Instead, viewDidLoad should call helper methods. Each of the helper methods should do just one thing (Refer to SOLID object oriented principles). Then the unit tests can test those helper methods to verify that the one thing is done correctly. It also means that the test for viewDidLoad probably just verifies that those helper methods are actually called. This can be done using a partial mock object. Each helper method should also be named in a way that makes it clear exactly what that one this is.
- The view controller unit tests should not be testing anything besides the view controller. If the view controller calls another object, then the view controller tests should verify that the other object is called (for example, using mocks), but should not test what the other object does. The unit tests for that other object should test that.
- And as with all unit tests, we should not try to test things that are done by the runtime or iOS libraries.
One of the things that I learned at WWDC 2012 is that Apple has changed the definition of OCUnit Logic and OCUnit Application tests. Apple does not split OCUnit test into “Application” and “Logic” tests anymore. Instead, OCUnit test targets can optionally specify a “test host” in the target’s build settings. What we think of currently as “Application” unit tests are tests that have designated a test host. What we think of as “Logic” unit tests do not.
One of the useful side effects of this is that both types of tests can be run in the same scheme and action. Previously I had been creating two separate schemes in my projects; one for the Application tests and one for the Logic tests. Both can actually be combined into a single scheme and run together if desired.