When a test fails, we want to know the location of the failure. Getting this information in Objective-C required us to dance with the preprocessor. But with Swift, it’s much more straightforward.
By “location of the failure,” I mean the file name and line number of the code that calls the assertion.
This is about speed. The more I trust the tests over a section of code, the more fearless I can be with bold refactoring. Fearless refactoring means I don’t spend time checking correctness. That’s what the tests are for! Instead, I can focus completely on good design.
So how can you increase confidence in your test code?
Before I start the first networking calls in the iOS TDD sample app, I have a question for you.
How would you write unit tests for this code?
NSURLSession *session = [NSURLSession sharedSession]; NSURLSessionDataTask *dataTask = [session dataTaskWithURL:url completionHandler:completion]; [dataTask resume];
This fires off a request to download some data, then does something with that data. Most people would write the code pretty much this way. (Of course, supplying a URL and a completion block.)
Then they ask, “How do I unit test this?”
Think about it for a minute. What are your options?
If we’ve learned the Three Steps of TDD and the Three Laws of TDD, what keeps us from doing Test-Driven Development? Maybe it’s not knowing how to write unit tests. Or more specifically, not knowing how to write unit tests against “real” code.
And what keeps us from writing unit tests against our own code? I bet it’s not knowing what our many options are.
Dependency Injection can unlock your code.
How many times have you run your tests, gotten a failure, and had to go digging through the test code to try to understand what it’s complaining about? For many of you, you’re so used to doing this that you don’t even notice it’s a problem.
Every time you have to read test code, precious time is lost. The feedback cycle lengthens, and you’re breaking your stride.
Remember, we are trying to go for fast feedback. When I run unit tests and get a failure, I want to understand what I just broke. And I want to do this without reading the test code.
Ideally, the test name alone gives me the information I need.
Hit ⌘U to run your tests. …Once the tests start executing, how long do you have to wait for the results?
If you waited more than a few seconds, you may have a problem. Because one surefire way to discourage Test-Driven Development on your team is to have unit tests take 30 seconds or more.
When I was taking water safety training, one of the things we had to do was jump into the water fully clothed. You don’t normally notice the feel of your own clothing, so it’s surprising to feel how waterlogged clothes hinder every basic swimming movement. The training is to learn how to remove that clothing in the water, to restore normal swimming. It’s like shedding a heavy straitjacket; suddenly you can move again!
Many of us are tied down by our slow testing experiences. Slow tests mean it takes longer to get results. Taking longer to get results means we won’t run our tests as often. Not running our tests as often means we’re undercutting the core benefit of Test-Driven Development — namely, getting feedback frequently.
So let’s restore normal movement by shedding those heavy tests and setting them aside. But which tests are “heavy”? And what do we do with them?
Update: UIAlertView is deprecated. Check out how to test UIAlertControllers.
People assume you can’t write unit tests for user interface code. That just ain’t so. I’ve already shown you how to do UIViewController TDD. Can we do the same for UIAlertView and UIActionSheet? Sure!
This time instead of a screencast, I’ve put my code on GitHub, because you’ll want to incorporate some classes into your tests. Go to my iOSAlertViewActionSheetUnitTesting repository.
In my UIViewController TDD screencast, I put IBOutlets and IBActions in the header file. This made them accessible to unit tests, but I knew it would raise questions:
@qcoding This seems like it complicates the question of whether or not actions and outlets truly belong in the public interface or not.
— Aengus McMillin (@aengusmcmillin) January 22, 2013
It’s a fair question. There’s a tension between information hiding (don’t reveal things in the interface) and testability (certain things need to be exposed). Exploring that tension leads me to apply the Extract Class refactoring in places I hadn’t considered before.
Objective-C classes don’t have anything that’s truly private, but it’s understood that if something isn’t published in the header file, hands off. As of Xcode 4, you can define outlets and actions in the implementation file, effectively hiding them. More hiding is good. But my screencast example works against it, what gives?
There is a way to have it both ways, a technique I used to use. I’ll show you how, then explain why I stopped using it.
Update: Apple took this to heart, and improved the unit test template in Xcode 5 and greater.
Xcode 4 has a File Template Library for creating new files. It includes a unit test template called “Objective-C test case class.”
Don’t use it.
Wouldn’t you like your unit test development to go faster? Let me give you a better file template.
Apple’s template puts cruft in your project. Cruft slows you down.
What’s wrong with Apple’s unit test template? Depending on which version of Xcode 4 you’re using, the template can be unnecessarily complicated, due to a distinction between logic tests and application tests that no longer exists.
But the template’s description reveals an ongoing problem: “An Objective-C class containing an OCUnit test case with a header.” A header? What for?