When engaging with a team new to agile methods of the XP flavour at a day to day technical level, there are some questions and discussions that I’ve come to expect as inevitable.
Adding methods ‘just for testing’
The approach of test driven development is a distinct change of mindset for most developers. The idea that the unit tests incrementally drive out functionality by specifiying it from the perspective of the calling code can feel like turning your designs inside out. More accurately it makes you think from the outside in. I have only used TDD in anger in object oriented languages, and it seems particularly appropriate for this style. TDD (also called behaviour driven development) forces you to think about what a class’s responsibility is, and how you will know if its living up to that responsibility. The upshot being that ultimately yes, you do end up with methods that you wouldn’t have added if you weren’t doing TDD, and that this is actually a good thing. I have heard complaints about polluting the public API, which I believe is a fear-based argument rooted in the concern that other developers may use your non-private methods in an unexpected way that causes bugs. Which leads me to my next point.
TDD designs are more complicated
This for me is purely in the eye of the beholder. I’d argue that TDD designs are generally more object oriented than non TDD designs, which personally I find less complicated. I can think about a system on a more conceptual level and in smaller chunks if I can read through a cohesive group of collaborating classes than I can if I have to read the same functionality in a single 150 line method. TDD codebases tend to grow a larger number of small classes with small methods than other approaches. The discipline of specifying behaviour in tests forces a decoupling, otherwise the test code becomes very hairy and brittle. Part of learning TDD it seems is going through that pain. To clarify the link from my earlier point, TDD done attentively tends to eliminate, or make explicit, a lot of the internal assumptions within a class about how it will be used. Of particular interest is method call sequencing. I see a lot of (non TDD) code where a public method on a class only works correctly if the object is instantiated in an environment where a singleton factory has been initialized somewhere else in the codebase beforehand, and three or four other methods have been called first with the correct parameters, themselves being complicated objects with brittle state. In this environment, reducing the number of public methods possibly serves to reduce the chances of calling them in the wrong order. Using tests to explicitly assert the behaviour of an object under different scenarios leads to objects that not only behave as expected in situations that have been considered but also behave robustly in situations that have not. Writing a class in such a way that it is usable immediately after construction is considered by many to be a best practice anyway (sometimes termed the Good Citizen approach). TDD just adds an extra impetus. Duplication in test code can be used as a hint to what reasonable assumptions can be made at construction time, and when in fact explicitly throwing an exception is the right thing to do. Many java developers are so fed up with unexpected NullPointerExceptions that explicitly throwing one from a constructor seems bizarre. If a class really cannot do anything useful when that field is null, throw as soon as you know. Its far easier to track down the cause than to wait until the exception gets thrown by something else the first time something tries to use the null field.
All this test code takes so long to write, we could be finished in half the time without it
For some value of finished perhaps. For a system of any significance whose production lifetime is quite probably going to be several times (and quite possibly an order of magnitude) longer than the initial development period, I disagree. It might seem slow, but that is usually due to two factors. The team are new to TDD and asking lots of questions like the ones I’m describing here (and arguing, debating, thinking etc.), and the discipline of TDD is pushing lots of small assumptions to the surface that would otherwise remain buried and implicit, in wait for the unsuspecting support and maintenance teams. If a project goes on for even a couple of months TDD, done conscientiously, can give a significant increase in productivity. New features become almost effortless to add to a loosely coupled, strongly cohesive codebase with a solid suite of automated unit tests. I’ve worked on systems that seemed to write themselves towards the end of the project.
Do the simplest thing for the task at hand
Originally a pushback against speculative development (particularly in code frameworks), this guideline is very easy to distort if you want to attack agile methods. Developing incrementally does not (in my opinion) mean taking your brain out before starting. I generally have an idea of where I’m going when developing, based on knowledge and experience. I use that mental roadmap to decide how to write the next test. Sometimes I’ll skim through and try a quick implementation of part of a feature to see if it looks right (quick meaning minutes, not days). Then I’ll write the tests that exercise my intended implementation, which will probably change it slightly to make it more testable. Code is clay, not marble. Or at least it should be.