Can TDD Be Simple? With Preparation, Yes

Click to play

May 17, 2016

0  comments

Test-driven development (TDD) doesn't make anything happen automatically. You really need to level up on two other skills as you go: design, and unit testing. Doing so can shift TDD from being daunting to being simple.

Let's look at how a change to unit testing empowers TDD.

[This post is part of the series TDD Sample App: The Complete Collection …So Far]

One way to get unit tests wrong is to make them more rigid than they need to be. In the MarvelBrowser iOS TDD sample app, we want to fetch comic book characters from the Marvel service. We'll do this by converting a fetch characters request model into a URL request. To fetch characters with:

  • names starting with NAME
  • paging the results into chunks of 10
  • skipping the first 3 pages, so starting from 30th result

we want to generate a URL with a query string. Something like:

http://gateway.marvel.com/v1/public/characters?nameStartsWith=NAME&limit=10&offset=30

Here’s the catch for unit testing: I said “something like,” not “exactly like.”

A naive TDD approach says, “testing for equality is easy, so do that.” But this isnt helpful for complex results. In this example,

  • A single test should examine one argument at a time, not all three.
  • The order of arguments can change and still be correct. So an equality check would prohibit valid results.
  • The query string will soon have more arguments. Specifically, we need to add authentication.

What we really need to do is parse the result. Of course, thats more complex than the linear given/when/then of a unit test. We need a test helper that is itself fully tested. I described various approaches in How to Avoid Problems with Assertion Test Helpers.

Watch what happens when I take time to prepare a custom matcher. In this 7½-minute screencast, I quickly TDD the URL path, then begin to generate the query string.

Outline:

  • TDD the URL path (0:00)
  • TDD the nameStartsWith parameter (2:54)
  • TDD percent-encoding of the parameter (5:17)

Show notes:

AppCodes “Refactor This” Menu

When I inlined the dummyRequestModel at 3:25, I used AppCodes “Refactor This” pop-up menu, which I invoked with Control-T. Typing anything filters the options. In the screencast, I got as far as “inl” to make “Inline…” the only option.

I like to lean on the “Refactor This” menu; its so convenient! Then I begin learning the keyboard shortcuts for options I use all the time, like Extract Variable.

TDD Cheating

I did cheat in my TDD. At 4:22, I had a failing test:

Expected URL with "nameStartsWith" = "NAME", but no query in <https://gateway.marvel.com/v1/public/characters>

Strict TDD suggests that I should hard-code the query string ?nameStartsWith=NAME. I probably should have. But in this video, I took the cheating shortcut of jumping ahead to a parameterized solution without a second test to drive this need.

My TDD of the percent-encoding also took a shortcut which leaves a potential hole. At 6:15 when I use URLQueryAllowedCharacterSet, theres nothing driving the use of that particular NSCharacterSet over any other.

Oh well. Id rather have imperfect TDD than no TDD. If enforcing the character set becomes a necessity, we can always add unit tests after the fact.

Wheres the Refactoring?

The 3 steps of the TDD Waltz are Fail, Pass and Refactor. But all you see in the screencast is Fail and Pass. The only “refactoring” I do is to use AppCodes refactoring support to create a new test and alter it. Its not really refactoring.

Why dont I show any refactoring? Two reasons:

  1. This is only the first query parameter. There are two more in the request model, plus the ones that come from authentication. We simply dont have enough changes yet.
  2. The custom hasQuery matcher greatly simplifies the test code. All the hard bits around parsing have already been tested and encapsulated.

This is the beauty of investing in test helpers. Without a mocking framework, Id have to write my own mock to record the calls to the NSURLSession. This hand-rolled mock would have to give me access to the first argument, the NSURL. And without a matching framework, Id probably pass the NSURL to a tested helper function. It would all work.

But with the OCMockito + OCHamcrest combination, we get all that functionality in something thats really easy to write. Better still, its also easy to read. (Note that if you prefer OCMock, it also accepts OCHamcrest matchers.)

I have no refactoring to show in this screencast because the TDD is too simple! Its not always that way, of course. But the more you invest in design skills and unit testing skills, the easier you will find TDD.

Where does it feel like your code resists TDD? Leave a comment below.

[This post is part of the series TDD Sample App: The Complete Collection …So Far]

The more you invest in design skills and unit testing skills, the easier you will find TDD.

Click to Tweet

How a TDD Mistake Revealed an Error in My Design

Jon Reid

About the author

Programming was fun when I was a kid. But working in Silicon Valley, I saw poor code lead to fear, with real human costs. Looking for ways to make my life better, I learned about Extreme Programming, including unit testing, test-driven development (TDD), and refactoring. Programming became fun again! I've now been doing TDD in Apple environments for 20 years. I'm committed to software crafting as a discipline, hoping we can all reach greater effectiveness and joy. Now a coach with Industrial Logic!

{"email":"Email address invalid","url":"Website address invalid","required":"Required field missing"}

Never miss a good story!

Want to make sure you get notified when I release my next article or video? Then sign up here to subscribe to my newsletter. Plus, you’ll get access to the test-oriented code snippets I use every day!

>