18

I have just 2+ years of experience in application development. In those two years my approach towards development was as following

  1. Analyze requirements
  2. Identity Core component/Objects, Required functions, Behavior, Process and their constraints
  3. Create classes, relation between them, constraints on objects behavior & states
  4. Create functions, process with behavioral constraints as per requirements
  5. Manually test application
  6. If requirement changes modify component/functions, then manually test application

Recently I got Introduced to TDD and feel that this is very good way to do development as developed code has strong reason to exists and lot of post deployment issues are mitigated.

But my problem is I am not able to do create tests first, Rather I am identifying components and just writing test for them before I actually write components. my question is

  1. Am I doing it is right? If not what exactly I have to change
  2. Is there any way you can identify whether test you have written are enough?
  3. Is it good practice to writing test for very simple functionality which might be equivalent to 1+1 = 2 or is it just an overplay?
  4. Is it good to change functionality and accordingly test if requirement changes?
2
  • 2
    "I am identifying components and just writing test for them before I actually write components.": I find this correct: you first identify the coarse architecture of your system and then start coding. During coding (TDD) you work out the details of the individual components and possibly discover problems with your architecture that you can fix along the way. But I find it OK that you do not start coding without any prior analysis.
    – Giorgio
    Commented Nov 16, 2014 at 13:29
  • You could also look into doing automated unit/integration testing without doing TDD. The two are often confused, but they are not the same thing.
    – Andres F.
    Commented Jan 16, 2015 at 14:51

4 Answers 4

22

Am I doing it is right? If not what exactly I have to change

It's hard to say just from that short description, but I suspect that, no, you are not doing it right. Note: I am not saying that what you are doing doesn't work or is in some way bad, but you are not doing TDD. The middle "D" means "Driven", the tests drive everything, the development process, the code, the design, the architecture, everything.

The tests tell you what to write, when to write it, what to write next, when to stop writing. They tell you the design and the architecture. (Design and architecture emerge from the code through refactoring.) TDD is not about testing. It isn't even about writing tests first: TDD is about letting the tests drive you, writing them first is just a necessary prerequisite for that.

It doesn't matter whether you actually write the code down, or have it fully fleshed out: you are writing (skeletons of) code in your head, then writing tests for that code. That's not TDD.

Letting go of that habit is hard. Really, really hard. It seems to be especially hard for experienced programmers.

Keith Braithwaite has created an exercise he calls TDD As If You Meant It. It consists of a set of rules (based on Uncle Bob Martin's Three Rules of TDD, but much stricter) that you must strictly follow and that are designed to steer you towards applying TDD more rigorously. It works best with pair programming (so that your pair can make sure you are not breaking the rules) and an instructor.

The rules are:

  1. Write exactly one new test, the smallest test you can that seems to point in the direction of a solution
  2. See it fail; compilation failures count as failures
  3. Make the test from (1) pass by writing the least implementation code you can in the test method.
  4. Refactor to remove duplication, and otherwise as required to improve the design. Be strict about using these moves:
    1. you want a new method—wait until refactoring time, then … create new (non-test) methods by doing one of these, and in no other way:
      • preferred: do Extract Method on implementation code created as per (3) to create a new method in the test class, or
      • if you must: move implementation code as per (3) into an existing implementation method
    2. you want a new class—wait until refactoring time, then … create non-test classes to provide a destination for a Move Method and for no other reason
    3. populate implementation classes with methods by doing Move Method, and no other way

Typically, this will lead to very different designs than the oft-practiced "pseudo-TDD method" of "imagining in your head what the design should be, then writing tests to force that design, implement the design you had already envisioned before writing your tests".

When a group of people implement something like a tic tac toe game using pseudo-TDD, they typically end up with very similar designs involving some kind of a Board class with a 3×3 array of Integers. And at least a portion of the programmers will actually have written this class without tests for it because they "know that they're gonna need it" or "need something to write their tests against". However, when you force that same group to apply TDD As If You Meant It, they will often end up with a wide diversity of very different designs, often not employing anything even remotely similar to a Board.

Is there any way you can identify whether test you have written are enough?

When they cover all the business requirements. Tests are an encoding of the system requirements.

Is it good practice to writing test for very simple functionality which might be equivalent to 1+1 = 2 or is it just an overplay?

Again, you have it backwards: you don't write tests for functionality. You write functionality for tests. If the functionality to get the test to pass turns out to be trivial, that's great! You just fulfilled a system requirement and didn't even have to work hard for it!

Is it good to change functionality and accordingly test if requirement changes?

No. The other way round. If a requirement changes, you change the test which corresponds to that requirement, watch it fail, then change code to make it pass. The tests always come first.

It is hard to do this. You need dozens, maybe hundreds of hours of deliberate practice in order to build up some sort of "muscle memory" to get to a point, where when the deadline looms and you are under pressure, you don't even have to think about it, and doing this becomes the fastest and most natural way to work.

8
  • 1
    A very clear answer indeed! From the practical perspective, a flexible and powerful testing framework is very enjoyable when practising TDD. While independent of TDD, the ability to automatically run tests is invaluable to debug an application. To get started with TDD, non-interarctive programs (UNIX-style) are probably the easiest, because a use-case can be tested by comparing the exit-status and the output of the program to what is expected. A concrete example of this approach can be found in my Gasoline library for OCaml. Commented Nov 20, 2014 at 13:58
  • 5
    You say "when you force that same group to apply TDD As If You Meant It, they will often end up with a wide diversity of very different designs, often not employing anything even remotely similar to a Board" as if it is a good thing. It is not clear at all to me that it is a good thing, and may even be bad from a maintenance standpoint since it sounds like the implementation would be very counter-intuitive to someone new. Could you explain why this implementation diversity is a good thing, or at least not bad?
    – Jim Clay
    Commented Nov 20, 2014 at 18:02
  • 3
    +1 The answer is good in that it correctly describes TDD. However, it also shows why TDD is a flawed methodology: careful thought and explicit design is needed, especially when faced with algorithmic problems. Doing TDD "in the blind" (as TDD prescribes) by pretending not to have any domain knowledge leads to needless difficulty and dead-ends. See the infamous Sudoku solver debacle (short version: TDD cannot beat domain knowledge).
    – Andres F.
    Commented Jan 16, 2015 at 14:48
  • 1
    @AndresF.: Actually, the blog post you linked to seems to echo the experiences Keith made when doing TDD As If You Meant It: when doing "pseudo-TDD" for Tic-Tac-Toe, they start by creating a Board class with a 3x3 array of ints (or something like that). Whereas, if you force them to do TDDAIYMI, they often end up creating a mini-DSL for capturing the domain knowledge. That's just anecdotal, of course. A statistically and scientifically sound study would be nice, but as is often the case with studies like this, they are either way too small or way too expensive. Commented Jan 16, 2015 at 15:07
  • @JörgWMittag Correct me if I misunderstood you, but are you saying that Ron Jeffries was doing "pseudo-TDD"? Isn't that a form of the "no true Scotsman" fallacy? (I agree with you about the need for more scientific studies; the blog I linked to is just a colorful anecdote about the spectacular failure of a specific instance of TDD usage. Unfortunately, it seems as if the TDD evangelists are too loud for the rest of us to have a real analysis of this metholody and its alleged benefits).
    – Andres F.
    Commented Jan 16, 2015 at 17:12
5

You describe your development approach as a "top-down-only" process - you start from a higher abstraction level and go more and more into the details. TDD, at least in the form as it is popular, is a "bottom-up" technique. And for someone who is working mostly "top-down" it can be indeed very unusal to work "bottom-up".

So, how can you bring more "TDD" into your development process? First, I assume your actual development process is not always so "top-down" as you described it above. After step 2, you will probably have identified some components which are independent from other components. Sometimes you decide to implement those components first. The details of the public API of those components probably does not follow your requirements alone, the details also follow your design decisions. This is the point where you can start with TDD: imagine how you are going to use the component, and how you actually will use the API. And when you start coding such an API usage in form of a test, you just started with TDD.

Second, you can do TDD even when you are going to code more "top-down", starting with components which are dependent on other, non-existing components first. What you have to learn is how to "mock out" these other dependencies first. That will allow you to create and test high-level components before going to the lower-level components. A very detailed example on doing TDD in a top-down-manner can be found in this blog post of Ralf Westphal.

4

Am I doing it is right? If not what exactly I have to change

You're doing just fine.

Is there any way you can identify whether test you have written are enough?

Yes, use a test/code coverage tool. Martin Fowler offers some good advice on test coverage.

Is it good practice to writing test for very simple functionality which might be equivalent to 1+1 = 2 or is it just an overplay?

In general, any function, method, component etc. that you expect to yield some result given some inputs is a good candidate for a unit test. However, as with most things in (engineering) life, you need to consider your trade-offs: Is the effort offset by writing the unit test resulting in a more stable code-base in the long-run? In general, opt to write test code for crucial/critical functionality first. Later if you find there are bugs associated with some untested part of the code, add more tests.

Is it good to change functionality and accordingly test if requirement changes?

The good thing about having automated tests is that you will immediately see if a change breaks previous assertions. If you expect this because of changed requirements, yes it is ok to change the test code (in fact, in pure TDD you would change the tests first according to requirements, then adopt the code until it meets the new requirements).

1
  • Code coverage might not be very reliable measure. Enforcing % of coverage usually results in a lot of non-necessary tests (like tests for all parameters null checks, etc. - which are tests for the sake of tests that add almost no value) and wasted development time, while hard to test code paths might not be tested at all.
    – Paul
    Commented Nov 16, 2014 at 22:31
4

Writing tests first is a completely different approach to writing software. Tests are not only a tool of proper code functionality verification (they all pass) but the force that defines the design. While test coverage might be a useful metric, it must not be the goal in itself - the goal of TDD is not to get to a good % of code coverage, but to think about testability of your code before writing it.

If you have troubles with writing tests first, I would highly recommend doing a session of pair-programming with someone who is experienced in TDD, so that you get a hands on experience of "the way to think" about the whole approach.

Another good thing to do is to watch online video where software is being developed using TDD from the very first line of it. Good one that I once used to introduce myself to TDD was Let's Play TDD by James Shore. Take a look, it will illustrate how emergent design works, what questions should you ask yourself while writing tests and how new classes and methods are created, refactored and iterated upon.

Is there any way you can identify whether test you have written are enough?

I believe this is the wrong question to ask. When you do TDD, you chose to do TDD and emergent design as the way to write software. If any new functionality you need to add always starts with a test, it will always be there.

Is it good practice to writing test for very simple functionality which might be equivalent to 1+1 = 2 or is it just an overplay?

Obviously it depends, use your judgement. I prefer not to write tests on parameters null checks, if the method is not part of public API, but otherwise, why would you not confirm that method Add(a,b) returns a+b indeed?

Is it good to change functionality and accordingly test if requirement changes?

Again, when you change or add new functionality to your code, you start with a test, whether it's adding new test or changing existing one when requirements change.

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.