15

I'm fairly new to TDD and I have trouble when creating my first test when it comes before any of the implementation code. Without any framework to the implementation code I am free to write my first test however I want but it always seems to come out tainted by my Java / OO way of thinking about the problem.

For example in my Github ConwaysGameOfLifeExample the first test I wrote (rule1_zeroNeighbours) I started out by creating a GameOfLife object that hadn't been implemented yet; called a set method that didn't exists, a step method that didn't exist, a get method that didn't exist, and then used an assert.

The tests evolved as I wrote more tests and refactored, but originally it looked something like this:

@Test
public void rule1_zeroNeighbours()
{
    GameOfLife gameOfLife = new GameOfLife();
    gameOfLife.set(1, 1, true);
    gameOfLife.step();
    assertEquals(false, gameOfLife.get(1, 1));
}

This felt odd as I was forcing the design of the implementation based on how I had decided at this early stage to write this first test.

In the way that you understand TDD is this ok? I seem to be following the TDD / XP principles in that my tests and implementation evolved over time with refactoring, and so if this initial design had proved useless it would have been open to change, but it feels like I am forcing a direction on the solution by starting this way.

How else do people use TDD? I could have gone through more iteration of refactoring by starting out with no GameOfLife object, only primitives and static methods but that seems too contrived.

1
  • 5
    TDD replaces neither careful planning nor careful design pattern selection. That said, before you've written any implementation to satisfy the first few lines of test is a far better time than after you've written dependencies to recognize that your plan is stupid, that you've chosen the wrong pattern, or even just that it's awkward or confusing to invoke a class in the manner your test demands.
    – svidgen
    Commented Mar 17, 2015 at 12:57

6 Answers 6

9

This felt odd as I was forcing the design of the implementation based on how I had decided at this early stage to write this first test.

I think this is the key point in your question, Whether or not this is desirable depends on whether you lean towards codeninja's idea that you should design up-front then use TDD to fill in the implementation, or durron's idea that tests should be involved in driving out the design as well as the implementation.

I think which of these you prefer (or where you fall in the middle) is something you need to discover for yourself as a preference. It's useful to understand the pros and cons of each approach. There are probably plenty, but I'd say the main ones are:

Pro Upfront Design

  • However good a process TDD is at driving design, it's not perfect. TDing without a concrete destination in mind can sometimes run down dead ends, and at least some of these dead ends could have been avoided with a bit of upfront thinking about where you want to end up. This blog article makes this argument using the example of the Roman Numerals kata, and has a rather nice final implementation to show for it.

Pro Test-Driving Design

  • By building your implementation around a client of your code (your tests), you get YAGNI-adherence pretty much for free, as long as you don't start writing unneeded test cases. More generally, you get an API which is designed around its use by a consumer, which is ultimately what you want.

  • The idea of drawing a bunch of UML diagrams before writing any code then just filling in the gaps is nice, but rarely realistic. In Steve McConnell's Code Complete, design is famously described as a "wicked problem"- a problem you can't fully understand without first at least partially solving it. Couple this with the fact that the underlying problem itself may change through changing requirements, and this model of design starts to feel a bit hopeless. Test driving allows you to just bite off one chunk of work at a time- in design, not just implementation- and know that at least for the lifespan of turning red to green, that task will still be up to date and relevant.

As for your particular example, as durron says, if you did go with an approach of driving out the design by writing the simplest test, consuming the minimal interface it can, you probably would start with a simpler interface than the one in your code snippet.

3
  • The link was a very good read Ben. Thanks for sharing that.
    – RubberDuck
    Commented Mar 18, 2015 at 0:52
  • 1
    @RubberDuck You're welcome! I don't completely agree with it, actually, but I think it does an excellent job of arguing that point of view. Commented Mar 18, 2015 at 9:30
  • 1
    I'm not sure I do either, but it does make its case well. I think the right answer is somewhere in the middle. You've got to have a plan, but certainly if your tests feel awkward, redesign. Anyway... ++ good show old bean.
    – RubberDuck
    Commented Mar 18, 2015 at 10:09
17

In order to write the test in the first place, you have to design the API that you are then going to implement. You've already started on the wrong foot by writing your test to create the entire GameOfLife object and using that to implement your test.

From Practical Unit Testing with JUnit and Mockito:

At first you might feel awkward for writing something which is not even there. It requires a slight change to your coding habits, but after some time you will come to see it is a great design opportunity. By writing tests first, you have a chance of creating an API that is convenient for a client to use. Your test is the first client of a newly born API. This is what TDD is really all about: the design of an API.

Your test doesn't make much of an attempt to design an API. You have set up a stateful system where all the functionality is contained within the outer GameOfLife class.

If I were to write this application, instead I would think about the pieces I want to build. For example, I might make a Cell class, write tests for that, before moving on to the larger application. I would certainly create a class for the "infinite in every direction" data structure that is required to properly implement Conway, and test that. Once all of that was done, I would think about writing the overall class that has a main method and so forth.

It's easy to gloss over the "write a test that fails" step. But writing the failing test that works the way you want it to is the core of TDD.

4
  • 1
    Considering a Cell would just be a wrapper for a boolean, that design would certainly be worse for performance. Unless it needs to be extensible in the future to other cellular automata with more than two states? Commented Mar 17, 2015 at 22:01
  • 2
    @immibis That's quibbling over details. You could start with a class that represents the collection of cells. You could also migrate/merge the cell class and its tests with a class representing a collection of cells later if performance is an issue.
    – Eric
    Commented Mar 17, 2015 at 22:10
  • @immibis Number of live neighbours could be stored for performance reasons. Number of ticks the cell's been alive, for colouring reasons..
    – Blorgbeard
    Commented Mar 18, 2015 at 2:12
  • @immibis premature optimization is the evil... Moreover, avoiding primitive obsession is the great way to write good quality code, no matter how many states it supports. Take a look at: jamesshore.com/Blog/PrimitiveObsession.html
    – Paul
    Commented Mar 24, 2015 at 17:58
0

There different school of thought about this.

Some say: test not compiling is error - go fix write smallest available production code.

Some Say: it is OK to write test first check if it sucks(or not) ant then create missing classes/methods

With first approach you are really in a red-green-refactor cycle. With second you have a little bit wider overview what you want to achieve.

It is up to you to choose which way you work. IMHO both approaches are valid.

0

Even when I implement something in a "hack it together" way, I still think up the classes and steps that will be involved in the whole program. So you've thought this through and written these design thoughts down as a test first - that's great!

Now keep iterating through both implementation to fulfill this initial test, and then add more tests to improve and extend the design.

What might help you is to use Cucumber or similar to write your tests in.

0
0

Before you start writing your tests, you should think about how to design your system. You should spend some considerable amount of time during your designing phase. If you did it, you won't get this confusion over TDD.

TDD is just a development approach link: TDD
1. Add a test
2. Run all tests and see if the new one fails
3. Write some code
4. Run tests
5. Refactor code
6. Repeat

TDD helps you to cover all the required features what you have planned before you start developing your software. link: Benefits

0

I do not like system level tests written in java or C# for that reason. Look at SpecFlow for c# or one of the Cucumber based test framework for java (maybe JBehave). Then your tests can look more like this.

enter image description here

And you can change your object design without having to change all your system tests.

(“normal” unit tests are great when testing single classes.)

What are the differences between BDD frameworks for Java?

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.