EI-DHH (Boeing 737-8AS), short final to RWY 30, València-Manises (LEVC). (Photo credit: Wikipedia)
Here’s the context. Some guy with a smidge of pull in the Ruby on Rails community made a post about testing when coding. I was actually surprised to see a somewhat immature perspective on testing and what makes it valuable. His conclusions make perfect sense if the primary value of testing is bug detection and prevention, so I’m not saying he’s stupid or anything (he’s not). However, I was surprised to see that testing, for him, pretty much has bug detection and prevention as its primary value. That tends to be the ground floor understanding of unit testing that gets people in the door, but nobody stays there.
There have been a great number of responses to the article in comments, other blog posts, etc., and there’s not really a need for me to rehash what they’ve said or what I’ve said in the past on this issue over and over again.
Instead, I thought I’d interact with the view in more of a Q&A format, using questions and objections that people have actually shared with me when discussing this issue.
Q: How do you justify the extra time writing tests takes? Don’t your clients pay you to write code, not write tests?
A: My clients do not pay me to write code. My clients pay me to deliver valuable, high-quality software, and it is the most moral thing in the world to do what I can to make sure what I deliver is what they want and working properly. Can you imagine Boeing saying something like, “We get paid to build planes, not create mathematically sound blueprints, run stress tests, and do safety inspections?” Nobody wants a plane that can’t fly or accomodate passengers or will likely kill them, and you are pretty much to blame if that’s the plane you sell them.
Tests make sure that we are delivering what the client is asking for as well as only writing code that makes those requests into reality as opposed to unnecessary layers of abstraction, features we don’t need, features that deeply misunderstand the client’s expectations, etc. I don’t think of it as the amount of time necessary to write tests taking away from coding; I think of it as all the time I’m not wasting writing code that’s useless or will need rework.
Q: Do you think you should have 100% test coverage?
A: In the sense that all the code I write is in response to a test that captures a specification, yes.
Q: So you’d write a test for the getters and setters of every property?
A: See, that’s question begging. I don’t write code then write tests to “cover” that code. I write tests, then I write the code I need to pass those tests. If I write a property with a getter and setter, it’s because I needed that property to make an existing test pass. So, no, I wouldn’t write a unit test around a getter and setter of a property, because that doesn’t make any sense. Where did that property come from to begin with? If not from a test, then why is that property there?
Q: But what about writing tests around code that already exists?
A: Very different ballgame, because at that point, you’re writing tests primarily to catch and prevent defects, and then DHH’s points have a lot more value to them. But it also depends. If I’m writing tests for the purpose of rewriting that code or decoupling it or what have you, then my tests serve more of a design purpose, and we begin to shift back to writing tests around the expected behaviors first, then refactoring the code to those tests. But, yeah, if it’s just a matter of some class sitting out there and someone wants regression tests around it, then I’d probably triage my tests along lines similar to what DHH recommends. But that isn’t an ideal situation and shouldn’t be typical, either.
Q: Isn’t using Cucumber for tests the result of a drug-fueled vision of a magical land where non-programmers write your tests for you?
A: That’s what DHH said, but once again, it reflects a reasonably immature view of development and testing. I do find that, in the Ruby community, a lot of the development seems to be done by one person or two to four man teams who are trying to build a workable product as fast as possible and get it out the door. In that context, some of the views make more sense. You’re not collaborating with lines of business, federal compliance officers, third party vendors, mainframes, volatile APIs, and all the other things that go into many line of business applications when you’re trying to build Coderizer or Snufflehumpr or whatever and get it out the door that weekend, so the considerations are somewhat different.
Also, if you have a relatively Waterfallish view of software development where the business sits in Silo A and produces an artifact that is carried over to the developers in Silo B to build, then the views make a little more sense as well. If you tried to introduce Cucumber (or Gherkin, in my case) across the process in a phased-gate approach, the only real question left is which Silo will devolve into the Lord of the Flies first.
However, when you are working on decently-sized line of business applications in a more (ugh) agile manner, you find yourself collaborating together with said business people as well as QA people. When you have a business representative, a developer, and a QA person sitting down at a table together to talk about features, requirements, and expectations, Cucumber is an amazing tool to help bridge the language gap. And if Cucumber is still too techy, you can at least come up with acceptance criteria a grammatical step up from Cucumber that can easily become Cucumber scenarios.