Robert Martin brought condemnation from the Swift community with his post The Dark Path. How dare he insult our wonderful new language? Clearly, he’s a newbie who hasn’t done enough Swift programming.
Except that he’s no newbie—not even close. As Mark Seemann said,
Let’s examine the controversial post as it relates to Swift—focusing on unit testing and TDD.
False: “You Don’t Need As Many Tests”
Let me call out a common response. It was even stated in Chris Eidhof’s calm and measured Types vs TDD: A Response. Chris argues that it’s not strong typing or TDD, but both-and. I agree. Robert Martin does, too, if you read his follow-up Types and Tests.
But Chris’s article included a statement I disagree with. It’s something I hear from quite a few people.
A type checker actually does testing for you. It’s not a replacement for TDD, but it allows you to completely get rid of a whole bunch of tests. For example, if you define a method foo that returns an Int, you can be sure it will only return Int. Not String, not nil or null, not anything else. No need to write a test.
In my experience, I haven’t found this to be true.
Swift's strict typing hasn't reduced the number of tests I write.
This may surprise you, especially if you know my enthusiasm for TDD. Why wouldn’t I write tests guaranteeing the return type?
Because most of the time, the “assert” section of a unit test is an equality check. It’s a simple way to say, “These are the property values I expect.” Having the same type is an implicit detail.
All that matters is that the object’s notion of equality is satisfied. So it could even be a different type! Conceptually, we’re just comparing the object’s properties. “When I send this object a particular message, do I get the expected result?” What people forget about object-oriented programming is that types are just a helpful means to an end. We don't actually care what type something is. What we care about is: does this object do the things we want? (Types happen to be convenient for this, but are not the whole story.)
There are even times when testing for equality is over-specification. In some situations, we care only about one property, not all of them. Both OCHamcrest and Swift Hamcrest support hasProperty matching. Even without Hamcrest, it’s why I recommend that Swift mock objects shouldn’t test for equality.
Over-specified tests are fragile tests. I usually don’t think about testing for a particular type. I’m even watchful when testing using equality.
When I Do Write Unit Tests for Types
Like any rule-of-thumb, there are exceptions to the rule. I can think of a common case when I do test for a particular type. When I write unit tests around push navigation, I check:
- Did the UINavigationController receive pushViewController with the expected type of view controller?
- Were the required settings made on that view controller?
That’s the only common scenario in which I test the type: when there’s some sort of hand-off to a framework, where the handed-off object is a subclass.
Curiously, in such scenarios, strong typing doesn’t reduce test scope! The framework method signature only knows about the superclass, but the actual object is a subclass.
But What About nil?
“What about nil arguments, though, or nil return values?”
What about them? If I need them, I specify them in tests. If I don’t, I don’t.
In Objective-C, nil was rarely ever a problem. It provides an automatic implementation of the Null Object Pattern.
Swift forces me to consider nil for every Optional type. This is usually good: it reminds me, “Oh, I may need more tests here.”
But it doesn’t decrease the tests.
Swift’s Strict Typing Slows Down My TDD
Strict typing has noticeably slowed my TDD. This is in contrast to Objective-C’s hinted-but-still-duck typing.
Why? It makes it harder to substitute fakes. There’s no way to say, “You expect type X. But I’m only testing. Could you give this object a pass? It’ll handle all the messages you need.”
Swift’s current inability to do so works against easy testing.
Perhaps some folks who are clever with compilers can find a workaround, kind of like @testable. Given Apple’s track record with unit testing support, we’ll need clever people from the community.
But I’m not holding my breath, and I can’t wait. Neither should you. Here’s my plea:
Swift makes TDD harder. Don’t let that stop you from doing TDD.
The benefits of TDD are so strong, it’s worth pressing through the learning curve. Even when a particular language makes that curve harder. (Hey, I learned TDD in C++.)
Don’t give up. It’s worth it.
Both-And… But It’s Not Additive with Swift
I’ve been doing TDD for some time now. As far as I can tell, Swift’s strict typing hasn’t reduced the number of tests I write. Not by a single test.
Like Chris Eidhof, and even like Robert Martin, I appreciate type-checking. I value early feedback from the compiler. But don’t ever mistake “it satisfies the compiler” for “it meets my requirements.” Type-checking can ensure that you’re getting the right type. Ah, but unit testing. Unit testing can ensure that those types are the right values.
So yeah, it’s both-and. The problem is, one is interfering with the other. It’s unit tests that are non-negotiable. I do hope Swift might evolve to be more tolerant of simple testing.
- Type Safety, 100% Coverage, and Robert Martin: Who’s Right?
- Static Analysis: Will It Free You from TDD?
Agree? Disagree? Please leave your comments below.