5

I might be wrong, but I feel like the best approach to TDD is Solution, Red, Green, Refactor (adding "Solution" to the standard formula).

By coding the solution first, I feel like I am much more efficient when it comes to writing the actual tests. Sometimes, it's hard to know what the solution will be, much less what test to write. Writing a test when you have no idea where to start is daunting. I think you can also avoid pitfalls by rapidly coding a solution. Conversely, writing a test seems to be only a speculation that a problem will be solved once it passes. By coding first, you are assured that you will do the good verifications.

So my approach is:

  1. Code the solution
  2. Comment the solution and follow the standard formula (Green, Red, Refactor)

However, I might be missing something, maybe even an essential point of TDD. Maybe that by having thought through the problem a first time, you are less likely to make a complete set of tests, or the design will be different because of that.

3
  • The point is not to write implementation code until your tests are done. You can code all you want as long as it is just skeletons (empty methods). That should allow you to do iterative modeling and get something up. Commented Jul 7, 2018 at 8:35
  • Integration tests are different than unit tests. My 2 cents is you fix integration tests but always write unit tests without executing your implementation. Commented Jul 9, 2018 at 19:58
  • An example of how TDD is a lifestyle softwareengineering.stackexchange.com/questions/223991/… Commented Jul 9, 2018 at 20:18

6 Answers 6

8

You are not the first who stumbled over the fact TDD misses a solution finding phase. Here is a link to a german blog summarizing a whole article series of Ralf Westphal about the topic. If you don't speak german, try Google translate. At least you can look at the graphics in the middle, scetching how "TDD 2.0" could look like.

(Westphal's articles are not available on the web, AFAIK, but you can find some of his ideas in his blog).

By coding the solution

That is IMHO not the best approach. "Solution finding" is ideally a phase without (full) coding. You may scetch out the API in code, you may write down some conceptual comments in natural language, or some high-level pseudo code, but you don't let the implementation details disturb your thought process. It is a top-down approach, opposed to the usual "bottom-up" process of TDD.

If you start "coding a solution first", you will probably end up in doing no TDD at all: you take a high risk of not writing enough tests to make sure every important aspect of your solution is covered, you take a high risk of implementing things "just in case", though you don't need them (yet), and you don't really think about the API requirements from the viewpoint of a user of your API.

This is actually very old wisdom: when I was in school (>30 years ago), I already learned the terms "bottom-up" and "top-down" as programming techniques both supporting each other. "TDD by the book" just covers the "bottom-up" aspect, the "top-down" aspect is introduced by explicitly doing (at least a little bit) analysis and design first.

So yes, developing a solution first is fine, but if you want to to TDD, don't implement your solutions in code first before you start to write tests.

3
  • 5
    I don't think I agree with this. I've TDD'd top down... As far as I'm concerned, your driving tests don't need to start with small-unit tests. You can have an integration or big-unit test driving interface design and other tests.
    – svidgen
    Commented Jul 7, 2018 at 15:07
  • 1
    I have been told that if the book is "Growing OO Software Guided by Tests" then top-down TDD is well covered. I have not read that book myself but the approach is actually quite common. Your first tests specify how the system interacts with the outside world in the simplest possible terms. All the upfront design you need is specifying an input / event and an output / side effect and that goes straight into the test. As you add tests with more details you start to split the system into components and give them different responsibilities. Commented Jul 7, 2018 at 16:01
  • 1
    In this case "1. Code the solution" really becomes a proof of concept, which rarely has the same testing requirements as production code. They say the proof is in the pudding --- so make a small batch of pudding first! And if the pudding tastes good, remake it with a TDD approach. Nothing wrong with that. Experiment. Commented Jul 9, 2018 at 2:22
10

Yes, it is bad practice.

Not because of your solution phase, but because of your commenting out and contrived TDD tacked on the end.

If you have the solution coded, simply add tests and you are done.

Don't try and squeeze in a practice just because you want to say 'we did TDD'.

Do what works and then say 'we didn't do TDD because solutions don't magically appear from iterative steps'

Choose a name for your new methodology* and write a book about it. Step 3: Profit!

*not STDD

3
  • 5
    Wait. Is it really a"bad practice?" Or is it just "not really TDD?"
    – svidgen
    Commented Jul 7, 2018 at 15:09
  • 1
    This answer isn't entirely accurate - commenting out the code and writing the test does accomplish something: it proves that the test fails without that code. If you don't comment out the code to verify that the test fails without it, you could write a test that doesn't actually test anything.
    – TehShrike
    Commented Jul 7, 2018 at 15:15
  • 1
    @TehShrike "commenting out the code and writing the test does accomplish something"`- Yes, but it is missing the main line of TDD: *Code exists to provide behavior required by a test. What the OP does is the opposite: A test exists to cover some code. I'd argue that this tests tend to be code centric and not behavior centric and that they tend to step in your way while refactoring. Commented Jul 8, 2018 at 14:46
5
  1. Code the solution
  2. Comment the solution and follow the standard formula (Green, Red, Refactor)

You aren't actually all that far from a common pattern in TDD. Your step #1 is analogous to a spike solution. James Shore introduces spikes this way

We perform small, isolated experiments when we need more information.

In TDD, the spike is discarded, and a new implementation is designed from scratch.

Conversely, writing a test seems to be only a speculation that a problem will be solved once it passes.

Not at all - we frequently get a test to pass by simply hacking the right answer into the production code. Nobody considers that the problem is "solved" at this point, only that the test is calibrated, and will detect a particular fault.

Actually solving the problem usually occurs under the green bar.

To understand the distinction between what you are doing and TDD, it may help to review the difference between testing and checking. In the process you describe, you are adding calibrated checks to the code you have written, but you aren't really testing the design.

TDD deliberately goes the other way around -- we design by writing the tests, and paying attention to how the API feels to use. Then we create a solution that is constrained by our API design.

Writing checks that conform to a pre-canned solution is one of the techniques that we use to manage legacy code; we're operating under a different set of constraints, so we use different techniques.

But to voluntarily add a legacy constraint when creating something new seems back to front.

3

Both the tests and the implementation code should flow from the requirements. One of the main ideas behind TDD is that it forces you to think about the interfaces (and keeping them simple) up front, since you need to provide inputs and acquire outputs much the same way that any other consumer of your code would. But, you should always be able to write one without the other if you are thoughtful and your requirements are properly fleshed out.

If you don't know what software you're writing, you need to establish that first as requirements. If you do, then you should be careful to avoid tightly coupling your tests to the implementation; it makes the system brittle and difficult to update. What you're suggesting very much sounds like it will result in this, or at least be exactly like following a tests-last methodology while simply making things harder for yourself in the middle.

However, there are certainly cases where requirements-driven tests break down. Requirements sometimes change, and there's often really nothing you can do about it besides adapt. (And they have flaws, and you iterate.) But when only the specific implementation changes, the interface should not; meaning, your tests for those interfaces remain valid. You can and should add tests as you go along to cover edge cases specific to your implementation, and so on, when following TDD.

2

The idea of TDD is not only test, but also guide development, so that breaking into units would be testable. I'd rather call your "Solutuion" a "Prototype", which may help to clear the task, but generally should not be inherited to production.

Conversely, writing a test seems to be only a speculation that a problem will be solved once it passes. By coding first, you are assured that you will do the good verifications.

As far as I understand, the solution may be "test more". Or, rather, design your atrchitecture and tests so that all cases are covered. A prototype could help with that as well.

2

If you code your solution first and then write tests for it- it's BDD. So I believe what you're trying to do is not a bad practice, it's just not TDD.

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service and acknowledge you have read our privacy policy.

Not the answer you're looking for? Browse other questions tagged or ask your own question.