There is a way of writing software that minimizes the chances of bugs and unexpected behaviours creeping in. Developing this way also results in small easy to change components.

This way of writing software is Test Driven Development. This post show how to practice TDD, and how it makes for a pleasurable and effective coding experience.

Giuseppe, the owner of you favourite pizza place, has hired you to build the iOS app for his business. Like the wise developer that you are you encourage Giuseppe to start small and iterate quickly. The two of you agree to start with a digital menu for his customers, to gauge its reception.

A way to move quickly in developing an app, while making sure it behaves as expected, is to detach its business logic from the details of the how inputs and outputs are sent to the users. Uncle Bob illustrates the idea in this talk and in his book Clean Architecture.

We should consider iOS the delivery layer for our applications, keeping as little business logic implementation living at that level as possible. Imagine you were building a CLI app instead, then pretend iOS is a GUI wrapper around it. Ideally none of the business logic should ever import UIKit.

The question you should always ask yourself when TDDing is "what's the smallest and simplest thing I can do?". What's the smallest thing we can build to start developing this app? Giuseppe's menu has three sections: pizzas, beverages, and desserts. Let's start with a menu app showing only the pizzas.

This version of the app with only pizzas is our walking skeleton, an app with only the bare minimum of functionality to make sure all the components are glued together and working end to end.

Since the core of our business logic is the conversion of Giuseppe's menu to a format that can be displayed let's start with a component for it, let's call it MenuDataSource.

When doing TDD we always start from the tests, so let's create one for MenuDataSource. In this post we'll use Apple's XCTest famework, but that's not the only option to write tests in Swift.

Our test should describe and verify the behaviour we want for MenuDataSource. We decided that our walking skeleton will have only pizzas, so let's make sure MenuDataSource provides only one section for the menu.

To test this behaviour we'll first need an instance of MenuDataSource.

The code is not compiling, as we haven't defined MenuDataSource yet. In TDD compilation failures count as test failures, and our job is to write the simplest code that we can to make the test pass, which in this case means compile.

To make the test compile we have to define MenuDataSource.

structMenuDataSource{ }

Notice that there are no properties or init parameters in this MenuDataSource. This is what means to write the simplest code possible, just enough code to make the test pass. We'll add properties to the type only as we'll need them to make the tests we'll write pass.

Now that the test is compiling let's move to asserting the behaviour of MenuDataSource when asked for sections.

A word of warning. You should never trust a test you haven't seen fail. We've seen the test fail due to a compilation error, but what about testing the actual behaviour? It wouldn't hurt to change the hardcoded 1 in MenuDataSource to another value, and verify that the test fails.

We finally have a passing test, let's commit: Implement MenuDataSource returning number of sections.

Hang on, how many rows should there be in a section? If our section is showing the pizzas then it should have as many rows as the pizzas to display. How can we test this behaviour? We need a way to control this input parameter, for example we can pass the array of pizzas to display to MenuDataSource at init time.

The act of writing tests for the behaviours we want reveals the design details of our software. We didn't decide upfront that MenuDataSource should be initialized with an Array<Pizza>, we let the need for it emerge from the tests.

Before solving that problem let's bring the test to a successful state. Let's just say number of rows should always be 3.

It's tempting to add properties to Pizza, like its name, price, and ingredients, but we don't need that code to make the test pass. The problem we're trying to solve is not writing a comprehensive Pizza, but making sure we can pass Pizzas to display to MenuDataSource. TDD is all about solving one very little problem after the other.

Now the tests are passing again, so let's commit, even if we haven't implemented the logic to read the number of Pizzas yet. Pass [Pizza] to MenuDataSource init.

Committing often makes TDD easier. We move a little bit at a time, and if we end up with something that doesn't work we only have little code to review. You could even discard those unstaged changes and start from scratch. Don't worry about the Git history becoming long, commits are cheap to store. If you want to have a more compact history before your changes are merged you can always do an interactive rebase and tidy it up.

Moving on, let's write the simplest code we can to read make MenuDataSource return a number of rows based on the given [Pizza].

Tests are still green, so our change is a valid implementation of the behaviour. Let's commit this little change: Return number of menu rows based on input [Pizza].

What's our next step? Before moving on to implement the next behaviour let's look at how we're testing numberOfRows(inSection:). Have we considered all its possible behaviours? To answer this question we can look at its input parameter, should the method return the same output for every input? What should it do if the section index provided doesn't match any of the sections -currently just one- in the menu? Given this method is called "number of rows" a reasonable thing to do would be to return 0. Let's write a test for this scenario.

The tests are failing, you're not surprised by that are you? The current version of numberOfRows(inSection:) always returns the count of the pizzas. Let's write as little code as we can to make the tests pass.

We made a change and the tests are passing, it's time to commit: Handle out of bounds sections in numberOfRows(inSection:).

Next step, make MenuDataSource return what to display for a given a section and row. It's useful to make the return value a distinct type, let's call it MenuItem. The reason this is better than returning a Pizza is that it introduces a separation between the domain of the data, pizzas, and the domain of the UI, menu items. This separation allows us to change either of the domains without affecting the other, only the code that converts between the two.

But how can we verify that the MenuItem returned by the method is the one we're expecting? Or rather, what's the simplest thing we can do to verify that the MenuItem returned by the method is the expected one? Using pizza name seems like a good candidate for this, if a pizza is named "Margherita" the MenuItem should have title "Margherita" as well.

We have a green test, it's admittedly incomplete, but still enough for us to have a solid foundation to build upon. Let's commit it: Implement dummy item(forRow:, inSection:) in MenuDataSource.

Our test is not yet testing the "if a pizza is named 'Margherita'" behaviour. To refine our test we need a way to give it Pizzas with different names. It's time to grow the definition of Pizza with a name property.

Before making the change to the Pizzainit and its consumers let's make the change easy to make. In Kent Beck's words "make the change easy, warning this might be hard, then make the easy change".

A way to make the change easier is to reduce number of places in the code where we init Pizza directly. We can do that using a fixture extension.

The tests are not compiling because Pizza and its fixture don't have a name. Here you can see how having a fixture makes changing tests easier. By defaulting the name parameter we make it so that all the tests calling .fixture() don't need to be updated, unless we want to test behaviour depending directly on the value by passing a custom one.

This time, rather than replacing the dummy implementation straightaway let's add another test to prove the code we have right now is incorrect. This technique is called triangulation (even if we're using only two assertions).

Like we did for numbe of rows, we should ask ourselves whether we've covered the behaviour for all the possible kinds of input. What would happen if we gave a section index that doesn't match? What about a row? Given this method is called "item for row in section" if the row and section pair doesn't match the backing data then returning "nothing" seems appropriate. Swift's beautiful Optional type is the best tool to describe this scenario.

Notice that this change in the type signature of item(forRow:, inSection:) from -> MenuItem to -> MenuItem? broke the tests. We need to update the other calls to the methods to take into account the new optionality.

All the tests are now passing, happy days. Commit: Address out of range behaviour of item(forRow:, inSection:).

We now have a functioning data source for our menu of pizzas. 🎉. The next step would be to feed it into a view controller to show that information to a user. We can of course drive that implementation with tests too, but that's out of the scope of this post.

"It's too slow"

A common observation I get from developers and managers is that TDDing takes longer. "It would have taken me way less time to write this without tests." Sure, it would have. But building a software product is not just a matter of writing code, we also need to make sure the code behaves as expect, and be able to change the code easily.

On top of that, when you have only one screen to work with this might seem like overkill, but most apps don't have only one screen or one feature do they? For example if we were to add a detail screen for each item in the menu to verify how that screen behaves we would have to always navigate to it. If most of the behaviour is covered by tests we can verify it simply by running the tests, and launch and navigate through the app only when necessary to verify UI details, saving a lot of time.

When driving your implementation with tests you go a bit slower in the writing phase to go faster in all the other steps of the development process. It's also important to note that as you get more hours of TDD under your belt you'll get faster at it. The maturity of a test driven codebase helps writing tests faster too, as you'll have a lot of infrastructure already in place.

Recap

We've seen how to write software driven by tests. The process is simple:

Write a test for the code you wish you had, it'll fail because the code is not there yet.

Use the failing test as feedback to implement just enough code to make the test pass.

Once you have a passing test make the underlying code better.

In other words: red, green, commit, refactor, commit.

This simplicity is what makes it powerful. Writing just enough test code to get a failure, then just enough code to make it pass means you're always solving small problems. This constant and fast feedback loop is a flow inducing process. Like athletes and stand up comedians know well, the faster your feedback loop the faster you'll improve.

TDD is more than a way to write code, is a business philosophy. It's a way to build a product in small incremental steps while continuously getting feedback, from the micro level of a single function, to the macro of an iteration on the next version.