“You don’t need static type checking if you have 100% unit test coverage.”
This is one of the closing statements from a post called Type Wars. Various people jumped all over this, with much LOL.
Who’s right? Is the post right? Are the detractors right?
Applying my faith to life, I find that “right and wrong” isn’t a helpful filter.
I want to start by trying to see the points of view of the detractors. But then I want to expand on the controversial statement, from my point of view. Finally, what do I think this means for Swift?
What are the detractors saying?
“100% coverage is a bad goal.”
I think this is stated from two points of view. First, low code coverage clearly shows a lack of tests, but the opposite isn’t true. That is, high code coverage says nothing about the quality of the tests. You could, in fact, have “100% code coverage” without a single useful test.
Another point of view is that 100% code coverage is unattainable. In fact, pursuing it is a waste of time. That time could be better spent improving the production code.
“But tests vary in quality.” To some, it seems nonsensical to compare unit tests to type safety. Especially because test quality can vary, while type safety is strictly defined.
“Type safety eliminates the need for many tests.” You no longer have to write tests that ensure a particular object has a certain interface.
“Why would you eliminate something that ensures correctness?” Tests may be well and good, but why even give up type safety? That’s like turning off warnings.
Then what is he saying about coverage?
That sentence, “You don’t need static type checking if you have 100% unit test coverage,” is a lightning rod.
It’s also easy to pull out of context and completely misunderstand.
What’s the context? I’m looking at the previous sentence: “My own prediction is that TDD is the deciding factor.”
TDD makes all the difference. It explains most of the conflicting views. Let’s take another look at those earlier statements.
“100% coverage is a bad goal.”
Code coverage isn’t a goal of TDD. It’s a natural outcome. Moreover, 100% coverage isn’t unattainable. With TDD, it’s what you get by definition.
This doesn’t mean I have 100% unit test coverage in my modules. Why not? Because not everything I code is 100% TDD. For example, I TDD user interface functionality, but not its appearance. But test-first or test-after, I aim for 100%. If it’s not impractical to plug a hole, plug it!
“But tests vary in quality.” That’s why I periodically do audits of test code. If I’m working in an area, the tests in that area are fair game. Always be refactoring! Code I once thought was “clean” may look messy after 6 months of improving my skills.
“Type safety eliminates the need for many tests.” I actually find it quite rare to write tests verifying that a certain object has a particular interface. The only times it’s important is when an incoming object can in fact be of any type, such as in OCHamcrest assertions. (That’s why I test matchers against
nil and plain NSObject.)
So this hasn’t been an issue for me. I have to wonder if people are writing more tests than they actually need. (And not enough of the ones they do need.)
“Why would you eliminate something that ensures correctness?” I wouldn’t! You know me, I’m a big believer in fast feedback. If I can get feedback even before compiling, that’s great. It’s why I turn up the warnings.
The Big Question Robert Martin asks about strong typing isn’t about eliminating safeguards. It’s about the ability to move faster. That’s the question worth disagreeing about.
The focus on 100% code coverage is misplaced. If you don’t TDD, you may not understand a TDDers perspective of coverage. Don’t let the 100% coverage thing distract you from the Big Question, which is worth debating.
The path my experiences took
I learned TDD while coding C++. The nature of C++’s strict rules made it a challenge. But I had nothing to compare the difficulties with, so I kept at it.
Then I started working in Objective-C. Compared to C++, it was so much easier to get results! In both test code and production code, my boost in productivity was noticeable.
It was like returning to Smalltalk, where I first learned Object Oriented Programming. The focus was no longer on objects and creative use of disguised function pointers. The ability to send any message to any object was liberating!
At MCE^3 in Warsaw, I shared this quote from Alan Kay:
For example, mock objects are really easy to create in Objective-C. The mock doesn’t have to share anything in common with the actual object. It just has to answer the right messages.
So I’ve experienced a boost in productivity. I’ve attributed this to switching from a strict type-checking language (C++) to a dynamic language with type support (Objective-C). But that’s not the only difference between those two languages. Regarding my increase in productivity, one Twitter friend said, “You could say that about going from C++ to anything.”
@qcoding you could say that about going from c++ to anything.
We’re more productive in swift than objc, even while it’s a moving target
— Christopher Pickslay (@cpickslay) May 4, 2016
So based on my experience, I’ve long assumed that Robert Martin’s statements about dynamic language productivity were true. But I see now that there are more factors. There are always more factors.
What about Swift?
Swift is where things get interesting.
The initial buzz around Swift was around ? and !. I saw developers rejoicing that this would eliminate, or at least reduce, null pointer crashes. Frankly, I didn’t understand this fascination. Why not? Because I rarely encounter such crashes in my code. I think TDD helps prevent this.
And the strict typing does mean it’s more difficult to create mock objects. Maybe I shouldn’t say “difficult” because really, it’s not hard. Remember, as I shared in my talk on Dependency Injection, I encourage people to create mocks by hand when first starting out. But creating mocks by hand is more time-consuming. With OCMockito, I can create a fully-functioning mock object in a few keystrokes.
But Swift is far more than ?, ! and strict typing. The expressiveness of the language makes many things simpler to code. Simpler is cleaner, and cleaner is good for everyone.
So I may lose some coding time on Swift test code. But that time may be made up for on the production code side. It’s not just the time it takes to initially write something. It’s the time saved in the long run by having cleaner code.
I find the mocking comments about 100% coverage to be entirely misplaced. The code of a TDDer has different rules. It takes commitment to press through the TDD learning curve. I’m here to encourage you to give it your best shot.
And code coverage really wasn’t Martin’s point at all. His main point, that dynamic typing leads to greater productivity, is worth debating. Many of us have experienced that greater productivity. But maybe it has less to do with strong typing and more to do with other language constructs.
So let’s find out. I’ve started to add some Swift to this blog. I’ve added a Swift version of the Bowling Game TDD Kata. In the process, I created a Swift unit testing template and my Swift code snippets.
In coming months, I plan to do a Swift screencast of some basic TDD. And I’ll do a parallel Swift version of my iOS TDD sample app. As I share about TDD and unit testing, I’d appreciate your feedback about ways to make my Swift code less ObjC-style and more Swifty!
Meanwhile, here are two resources for TDD with Swift I recommend:
- Joe Masilotti’s blog features Swift articles on TDD, along with other testing topics.
- Dominik Hauser’s book Test-Driven iOS Development with Swift (Disclosure: This book link is an affiliate link. If you buy anything, I earn a commission, at no extra cost to you.)
Also check out part 2, Static Analysis: Will It Free You from TDD?
What are the pluses and minuses in productivity you’ve experienced across languages? What about TDD in those languages? Share your experiences in the comments below.