Monday, January 11, 2016

Why Useful TDD is a Unicorn

Some description of the scene

First of all we must explain what is TDD, TDD = Test Driven Design, that is the design is driven by tests, only the design can be driven by tests if the tests are coded FIRST, that is BEFORE THE IMPLEMENTATION DETAILS.

Obviously the method or methods to test must be coded first, in theory with a simple and minimal implementation just to compile (in compiled languages of course) and run to make the test fail.

When I talk about TDD I want to underline the primary concept, TEST CODE IS FIRST CODED. Underlining this is absolutely necessary because many people think TDD = TESTING. Many articles about TDD are just articles about testing, and many praises to TDD are in practice praises to testing "in general", and when TDD is criticized or questioned, automatically is translated as a rant against TESTING. The need of testing is so old as software development.

In this article I'm going to talk about what I think about TDD, I'm no longer a young guy and fortunately I'm a not "consulting seller" and I'm not forced to public defend something I don't "believe", this is hard in a software world/industry plenty of people anxious to be "trendy" that we could name the "me too syndrome".

In this context is very hard to "contradict" the "industry", the industry is EVER trying to sell unicorns as real stuff, in my opinion TDD is one of these unicorns, many (most of?) people say "I've got TDD, I use TDD to make software, TDD is great". The problem of TDD is like teen sex, everybody talk a lot but in practice...

This article is brief, I've no interest on TDD ranting, TDD want to be a honest effort to make software better and predictable, there are a lot of articles about how great TDD is and of course some rants. This is just my vision.

Is TDD possible? The TDD paradox

Yes OF COURSE TDD is possible, the problem is in the details.

Someone has said you is doing TDD? Don't believe a word until you see him/her coding.

My thesis is simple, TDD is possible of course but only in trivial code!!! USEFUL TDD IS NEAR IMPOSSIBLE OR AN ABSURD WASTE OF TIME!!

Yes this provocative conclusion is presented first to make the explanation more expected :)

Try to remember how many articles introducing TDD you have read, yes most of them are trivial examples, the implementation code is so simple that most of the implementation must be written before the test because otherwise is not possible to execute any test.

Take a look to the Java code of this example, the article teaches you to avoid testing each method, instead invite you to test behaviors... Behaviors? What behaviors? The example is so extremely simple that is hard to find a "behavior" use case.

Do you remember the "revolutionary" Ian Cooper's talk TDD: Where Did It All Go Wrong?

The key phrases:

Avoid testing implementation details, test behaviors

– A test-case per class approach fails to capture the ethos for TDD. Adding a new class is not the trigger for writing tests. The trigger is implementing a requirement.

– Test outside-in, (though I would recommend using ports and adapters and making the ‘outside’ the port), writing tests to cover then use cases (scenarios, examples, GWTs etc.)

– Only writing tests to cover the implementation details when you need to better understand the refactoring of the simple implementation we start with.

Ian invites to avoid the classical typical low level testing apparently accepted by everybody for TDD (test any class, test any method). The rationale is simple, the less level, the more trivial code we are testing, the less level, the less value of the test. Consequently the number of tests are extremely high and in the same time the value of tests is extremely low, a lot of work for near nothing back.

OK we accept we must practice TDD (remember, test-first before implementation details) with behaviors. A "behavior" beyond a simple class or method implies in OOP a class system design,  that is, several top level classes collaborate to get a result or action (unless you are coding a kind of StringCalculator parser), ignoring the necessary internal classes (implementation details). Identifying the top level classes is not a trivial task, it involves trial and error, redesign, external API conceptualization...

In the best case you are going to get a bunch of main near empty top level classes and corresponding public methods, THEN perhaps you are ready to start to code tests (obviously red tests in TDD). When implementing the details frequently you are going to kick your head against the wall of the premature API optimism, the implementation details usually influences the public API of the module we are developing (the industry knows a lot about the problem of defining APIs in papers), because unless you have a clear pre-designed public external API for end users imposed as requisite, your API must be designed up to front and you must predefine the scope if you want really practice TDD.

The result is several iterations of implementation/refactorization coding and API changes until a final API is stable enough, meanwhile your first ambitious "test first" code in practice is just a draft being absurdly modified again and again and again... Are we really doing Test Driven Design? Really? Is worth the test first obsession? Is up to you but perhaps this is the time to think the sense of the hype:



No comments:

Post a Comment