Kirsteins
Kirsteins

Reputation: 27335

Writing tests before writing code

As far as I understand TDD and BDD cycle is something like:

  1. Start by writing tests
  2. See them fail
  3. Write code
  4. Pass the tests
  5. Repeat

The question is how do you write tests before you have any code? Should I create some kind of class skeletons or interfaces? Or have I misunderstood something?

Upvotes: 1

Views: 493

Answers (5)

Stephen Byrne
Stephen Byrne

Reputation: 7485

The question is how do you write tests before you have any code? Should I create some kind of class skeletons or interfaces? Or have I misunderstood something?

To expand on a point that lared made in his comment:

Then you write tests, which fail because the classes/whatever doesn't exist, and then you write the minimal amount of code which makes them pass

One thing to remember with TDD is that the test you are writing is the first client of your code. Therefore I wouldn't worry about not having the classes or interfaces defined already - because as he pointed out, simply by writing code referencing classes that don't exist, you will get your first "Red" in the cycle - namely, your code won't compile! That's a perfectly valid test.

TDD can also mean Test Driven Design

Once you embrace this idea, you will find that writing the test first serves less as a simple "is this code correct" and more of a "is this code right" guideline, so you'll find that you actually end up producing production code that is not only correct, but well structured as well.

Now a video showing this process would be super, but I don't have one but I'll make a stab at an example. Note this is a super simple example and ignores up-front pencil and paper planning / real world requirements from business, which will often be the driving force behind your design process.

Anyway suppose we want to create a simple Person object that can store a person's name and age. And we'd like to do this via TDD so we know it's correct.

So we think about it for a minute, and write our first test (note: example using pseudo C# / pseudo test framework)

public void GivenANewPerson_TheirNameAndAgeShouldBeAsExpected()
{
        var sut = new Person();
        Assert.Empty(sut.Name);
        Assert.Zero(sut.Age);
}

Straight away we have a failing test, this won't compile because the Person class doesn't exist. So you use your IDE to auto-create the class for you:

public class Person
{
    public int Age {get;set;}
    public string Name {get;set;}
}

OK, now you have a first passing test. But now as you look at that class, you realise that there is nothing to ensure a person's age is always positive (>0). Let's assert that this is the case:

public void GivenANegativeAgeValue_PersonWillRejectIt()
{
    var sut = new Person();
    Assert.CausesException(sut.Age = -100);
}

Well, that test fails so let's fix up the class:

public class Person
{
    protected int age;
    public int Age 
    {
        get{return age;}
        set{
                if(value<=0) 
                {
                    throw new InvalidOperationException("Age must be a positive number");
                }
                age=value;
            }
        }
        public string Name {get;set;}
}

But now you might say to yourself - OK, since I know that a person's age can never be <=0, why do I even bother creating a writable property - do I always want to have to write two statements, one to create a Person and another to set their Age? What if I forgot to do it in one part of my code? What if I created a Person in one part of my code, and then later on I tried to assign a variable that was negative to Age later on, in another module? Surely, Age must be an invariant of Person so let's fix this up:

public class Person
{
    public Person(int age){
        if (age<=0){
            throw new InvalidOperationException("Age must be a positive number");
        }
        this.Age = age;
    }   
    public int Age {get;protected set;}
    public string Name {get;set;}
}

And of course you have to fix your tests because they are won't compile any more - and if fact now you realise that the second test is redundant and can be dropped!

public void GivenANewPerson_TheirNameAndAgeShouldBeAsExpected() { var sut = new Person(42); Assert.Empty(sut.Name); Assert.42(sut.Age); }

And you will then probably go through a similar process with Name, and so on. Now I know this seems like a terribly long-winded way of creating a class, but consider that you have basically designed this class from scratch with built-in defences against invalid state - for example you will never ever have to debug code like this:

//A Person instance, 6,000 lines and 3 modules away from where it was instantiated
john.Age = x; //Crash because x is -42

or

 //A Person instance, reserialised from a message queue in another process
    var someValue = 2015/john.Age; //DivideByZeroException because we forgot to assign john's age 

For me, this is one of the key benefits of TDD, using it not only as a testing tool but as a design tool that makes you think about the production code you are implementing, and forcing you to consider how the classes you create could end up in invalid, application killing states, and how to guard against this, and helping you to write objects that are easy to use and don't require their consumers to understand how they work, but rather what they do.

Since any modern IDE worth it's salt will provide you with the opportunity to create missing classes / interfaces with a couple of keystrokes or mouse clicks, I believe it's well worth trying this approach.

Upvotes: 1

diabolist
diabolist

Reputation: 4099

TDD and BDD are different things that share a common mechanism. This shared mechanism that is that you write something that 'tests' something before you write the thing that does something. You then use the failures to guide/drive the development.

(BDD two circles diagram

You write the tests by thinking about the problem you are trying to solve, and fleshing out the details by pretending that you have an ideal solution that you can test. You write your test to use your ideal solution. Doing this does all sorts of things like:

  1. Discover names of things you need for your solution
  2. Uncover interfaces for your things to make them easy to use
  3. Experience failures with your things ...

A difference between BDD and TDD is that BDD is much more focused on the 'what' and the 'why', rather than the 'how'. BDD is very concerned about the appropriate use of langauge to describe things. BDD starts at a higher level of abstraction. When you get to areas where the detail overwhelms language then TDD is used as a tool to implement the detail.

This idea that you can choose to think of things and write about them at different levels of abstraction is key.

You write the 'tests' you need by choosing:

  1. the appropriate langauage for your problem
  2. the appropriate level of abstraction to explain your problem simply and clearly
  3. an appropriate mechanism to call your functionality.

Upvotes: 0

Mike Nakis
Mike Nakis

Reputation: 61993

If you are using a real programming language, (you know, with a compiler and all,) then yes, of course you have to write class skeletons or interfaces, otherwise your tests will not even compile.

If you are using a scripting language, then you do not even have to write skeletons or interfaces, because your test script will happily begin to run and will fail on the first non-existent class or method that it encounters.

Upvotes: 1

Carl Manaster
Carl Manaster

Reputation: 40336

You have the essence of it, but I would change one part of your description. You don't write tests before you write code - you write a test before you write code. Then - before writing any more tests - you write just enough code to get your test to pass. When it's passing, you look for opportunities to improve the code, and make the improvements while keeping your tests passing - and then you write your second test. The point is, you're focusing on one tiny bit of functionality at any given time. What is the next thing you want your program to do? Write a test for that, and nothing more. Get that test passing. Clean the code. What's the next thing you want it to do? Iterate until you're happy.

The thing is, if you write tests before writing code, you don't have that focus. It's one test at a time.

Upvotes: 7

Branden Williams
Branden Williams

Reputation: 93

Yes, that is correct. If you check out Michael Hartl's book on Ruby on Rails (free for HTML viewing), you will see how he does this specifically. So to add on to what lared said, let's say your first job is to add a new button to a web page. Your process would look like this:

  1. Write a test to look for the button on the page visually.
  2. Verify that the test fails (there should not be a button present, therefore it should fail).
  3. Write code to place button on the page.
  4. Verify test passes.

TDD will save your bacon when you accidentally do something to your code that breaks an old test. For example, you change the button to a link accidentally. The test will fail and alert you to the problem.

Upvotes: 1

Related Questions