3

My team is interested to apply TDD in our next software project. We have a discussion about how many details should be specified in design specs before the developers start coding.

Some of us propose that we should specify all classes and their relationship in specs before writing code because we have to design all test cases before implementing.

Some of us doubt that this will work practically. In previous projects, we only have a high-level design and let the developer designs the detail classes and methods on their own. The level of the detail in the specs is depending on the complexity of the requirement and experience of the developer. We write the specs only as detailed enough as to make the developer understand what should be done.

I am personally on the latter side of the discussion. I used to try to specify every bit of classes and methods in the specs in my previous projects before. Following are some example of problems that I encountered from doing so.

  • It takes too much time. To be able to design every classes and methods, an SA will more or less have to wear a developer hat to make sure that every bit of the design can be mapped to the actual code. As a result, the design often does not differ much from the implemented code. We have to work twice on the same thing: for the design and the implementation.

  • The developer does not have room to think. Writing the design this way sometimes leave only trivial logic for the developer to implement. It is like a fill in the blank kind of exercise for students.

  • The design often is premature. The design is done on a paper. We don't see how the code is actually running. There are some details we can only know when get our hand on the code. For example, the underlying framework might enforce us to design classes in some way and we don't know this while doing the design. And it is common to refactor some logic in a method to another class when we found appropriate. It's natural that new components can pop up during implementation.

I have tried researching on the Internet and found only this article related to this topic. The article seems to support my belief as it suggests that we don't have to do TDD on the detail classes.

However, the reference is not enough for us to decide. It is possible that designing every classes and methods is the right path and we just don't know the procedure to achieve that.

Do you think we should design every classes and methods in specs before writing code?

1

4 Answers 4

10

TDD is a design exercise. It does not mean "we have to design all test cases before implementing." Quite the opposite, in fact. In TDD, you repeat the following cycle many times:

  • Write exactly one failing test.
  • Write just enough of the implementation to pass that one test.
  • Refactor

It's okay to do a little bit of higher-level planning to make sure you aren't wasting time, but the idea behind TDD is most of your work is done test first, but in very small increments, usually a few minutes. Things like figuring out the exact set of classes you need falls very naturally out of that exercise.

4
  • Also, the tests you write don't necessarily care about the number of classes involved in making them pass. If you're writing a method to make a test happy and you figure out you'd best extract some functionality into a helper class for example, the test doesn't mind (though you more than likely end up writing another test for that helper class specifically).
    – jwenting
    Commented Apr 17, 2019 at 7:05
  • @jwenting, why I need to write unit tests for helper class if it's functionality already tested through tests for the class where helper is used?
    – Fabio
    Commented Apr 19, 2019 at 8:22
  • @Fabio because (IMO at least) you should test the smallest possible execution unit. If you test it only in the context of the class it's now used in, you may not test every code path, leaving future users with potentially nasty surprises if they find themselves in a code path you didn't use in your original code or with a result you gave special treatment in your original code (e.g. certain null returns).
    – jwenting
    Commented Apr 23, 2019 at 3:50
  • @jwenting, if I write tests first(TDD) and write only enough code to satisfy tests, then I never end up with untested code path. Then if other developer decide to reuse helper class, it will probably will write tests in own context and prove that helper can be used there as well.
    – Fabio
    Commented Apr 23, 2019 at 3:59
5

It sounds like your team has a fundamental misunderstanding of how TDD works. As with all things, there's the doctrine and there's reality. First the doctrine.

Starting with a blank slate:

  • Write a test to prove you have to change something
  • Run the test and watch it fail
  • Implement just enough to satisfy the test
  • Run the test and watch if pass
  • Refactor if necessary (and retest) then start over with a new test

All of these activities are being done by one developer. If you have a team, they are each running the same steps in parallel until the work is done.

The reality is that a lot of developers write the code first then try to shoehorn the tests in afterwards. While not explicitly TDD, as long as the code is testable and catches regressions it's less important whether you adhere to the letter of the law on this. It's more important that you have a codebase that works.

My team is interested to apply TDD in our next software project. We have a discussion about how many details should be specified in design specs before the developers start coding.

Then I would recommed looking at Behavior Driven Development (BDD), since it fits neatly into what you desire. In BDD, you write testable specifications, and those specifications dictate how the software as a whole is designed to work.

That approach is not TDD, nor are the two concepts mutually exclusive.

Some of us doubt that this will work practically. In previous projects, we only have a high-level design and let the developer designs the detail classes and methods on their own. The level of the detail in the specs is depending on the complexity of the requirement and experience of the developer. We write the specs only as detailed enough as to make the developer understand what should be done.

There's some truth to that, however, there should be some design effort up front so you know which way you intend to influence the implementation. For example, you should have your overall architecture decided on before you go too deep. I.e. whether you are building a functional application or using DDD. Also, whether you are building microservices or a more monolithic application.

1
  • I don't know why it was down voted. I like your answer, especially the BDD is interesting. Commented Apr 15, 2019 at 20:26
4

In TDD, should we specify all classes to implement before start coding

No -- in fact, TDD is specifically an alternative to that. Specifying all classes first is what we call Big Design Up Front.

The term BigDesignUpFront is commonly used to describe methods of software development where a "big" detailed design is created before coding and testing takes place. Several ExtremeProgramming (XP) advocates have said that such "big" designs are not necessary, and that most design should occur throughout the development process. While Xp does have initial design (the SystemMetaphor), it is considered to be a relatively "small" design.

See also http://wiki.c2.com/?DesignBeforeCoding

That said, the Look Ma, No Hands era of TDD is over.

We found that, in the small, you could start developing a system without having a plan for its design, and, through the application of a set of rules and some reflection, arrive at very good designs. When I think back about this, I call this the "Look ma, no hands!" era of TDD. Critics would rightly point out that people who were doing this well were drawing on a lot of tacit knowledge of good design. For the most part, we agreed. We just felt that this knowledge of good design could be taught, and people could make continuous decisions across the development cycle to grow good design organically. You do have to bring your design knowledge to the table. -- Michael Feathers, 2010.

TDD is going to work a lot better if you start with decent design heuristics. Stopping to have a think before you start writing your first test is completely within the rules.

1

Some of us propose that we should specify all classes and their relationship in specs before writing code because we have to design all test cases before implementing.

If you do that, then it's not TDD (specifically, the design is not driven in any way, plus you've locked yourself out of refactoring). TDD is a way of working, it's not primarily about testing.

You should do some initial design, you should have an idea where you are going, especially at the area where the outer layers of the application interface with the core domain. You'll also be designing stuff on the go. But you should avoid big upfront design, because generally you don't know enough about the domain at that point to design every detail of the implementation.

You should take care that the tests don't know about the internal implementation details of the core application layers; in other words, you should consciously control the surface area over which tests interact with the application, so that you can refactor - meaning restructure the code in those inner layers without changing the functionality, and without changing the tests. Here, tests serve as a safety net. If you can't do that, then you are not doing refactoring, by definition. You are doing something else.

As you refactor, you'll see opportunities to reorganize code into new classes, or merge classes, apply patterns, rearrange dependencies, etc. Having a specification of all the classes up front is incompatible with that.

The tests need to be maintained, too; but you don't want to do that and refactoring at the same time. This way tests and the code sort of support each other. Sometimes, you'll write tests that go into the internals, serving as a scaffold of sorts, constructed to help you deal with a difficult problem, but then you'll want to find a way to change those tests so that they work through the outer layers. Or you may wan't to delete them (and yes, it's OK to create such exploratory tests, and delete them later).

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.