BrowseOverflow using OCHamcrest/OCMockito
I recently came to Graham Lee’s book Test-Driven iOS Development when looking for an in-depth look on unit testing.
The Book doesn’t make use of mock objects but using what is coming with every Xcode installation – OCUnit/SenTestingKit.
The unit testing environment lacks mock objects, so as soon as you start to dive into test driven development you have to choose your tools.
I’ve decided to start with John Reid’s OCHamcrest/OCMockito and though I found it sometimes difficult to achieve what I needed, I finally was able to work through the books example without greater problems.
I even found a rather bad coding style on my side. So thinking about testing in the first place makes you more thinking about your design and coding style in the first place too.
If you haven’t done unit testing before I strongly recommend the excellent screen casts John made on http://www.qualitycoding.org.See Xcode project on GitHub
What has changed
In short – especially using OCHamcrest/OCMockito made the test code shorter and therefor easier to read.
You will see that the project uses storyboarding and more often property declarations and the automatic synthesize behavior. So I even deleted some of the tests where only setter behavior was tested (e.g. testAnswerHasSomeText in AnswerTest.m).
Here are some other things which changed
- Provider vs. DataSource/Delegate
When you work through the book you see that Graham developed a dataSource and delegate class and then merged the delegate methods into the dataSource class. The class is called ..DataSource.h/.m which I find isn’t ideal.So the classes, as we need several provider, are all named following that naming convention (TopicTableDataSource -> TopicTableProvider).
- BrowseOverflowDelegate Protocol
Instead of Graham’s BrowseOverflowObjectConfiguration class I moved the code into the AppDelegate and created a protocol to have access to the StackOverflowManager, the AvatarStore and the initial topics array.
- Moving methods
I reduced the number of classes a bit moving methods into other classes.
You wont find an AnswerBuilder class as the only method
- (BOOL)addAnswersToQuestion:(Question *)question fromJSON:(NSString *)objectNotation error: (NSError **)error;
seemed to me better placed into the Question class as
- (BOOL)addAnswersFromJSON: (NSString *)objectNotation error: (NSError **)error;
The same happened to
- (void)fillInDetailsForQuestion: (Question *)question fromJSON: (NSString *)objectNotation;
from QuestionBuilder class, which I renamed to
- (void)fillInDetailsFromJSON: (NSString *)objectNotation;
thus removing some test as well.
- Performing actions on private methods
For testing purposes you sometimes need to have access to an otherwise private method or instance variable and you feel uncomfortable for exposing it.Well, you can always use
- Added MethodsI wrote for most of the classes a description method, so that you can easily log things. I also used methods to display localized content for numbers as the recommended ways. This is also an interesting point when you unit test your user interface.
Where to go from here
As Graham pointed out on page 199 the app doesn’t work as one might expect. If you browse the same topic twice you find the same questions added again to the recentQuestions list.
At least this problem can be easily solved by adding only questions to the recentQuestions array if they are not already part of it by examining the questionID.
I extended the QuestionTest class by three additional test
plus extending the Question class with a compare method
and the Topic class with a method which returns TRUE if a question with a given questionID can be found in the current list.
So before adding a new question to the list we can check if it is already part of the current list and skip adding if it is.
Another point is that the app starts networking tasks to fetch the content for the table views. Here we have only the 20 most recent questions but it is worse thinking about how to stop an ongoing task when the user leaves that view or scrolls the table view cell away from the visible screen area.
If you follow the books example page by page and write your tests and code together you might think that it takes a while before you see the app in action and you are right.
In agile development one would define a User Stories e.g. When the app starts it should show a list of topics.
As Jonathan Rasmusson in his book The Agile Samurai has put it, the user story shoud comply to INVEST (Independable, Negotiable, Valuable, Estimatable, Small, Testable).
For the BrowseOverflow app you could think of one user story to show the list of topics. And than the Questions for a specific topic.
If you try to group your test code around one user story you not only have a success when running your unit test but you could show also that you managed to code a running app.
Most likely you would have noticed that selecting a topic twice leads to adding the same questions again.
I read the book from Graham Lee and saw the screen casts for John Reid which gave me a lot of input to improve my skills – thanks for your awesome work.