Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

How to choose TDD starting point in a real world project?

I've read tons of articles, seen tons of screencasts about TDD, but I'm still struggling with using it in real world project. My main issue is I don't know where to start, what test should be the first one. Suppose I have to write client library calling external system's methods (e.g. notification). I want this client to work as follows

NotificationClient client = new NotificationClient("abcd1234"); // client ID
Response code = client.notifyOnEvent(Event.LIMIT_REACHED, 100); // some params of call

There is some translation and message format preparation behind the scenes, so I'd like to hide it from my client apps.

I don't know where and how to start. Should I make up some rough classes set for this library? Should I start with testing NotificationClient as below

public void testClientSendInvalidEventCommand() {
    NotificationClient client = new NotificationClient(...);
    Response code = client.notifyOnEvent(Event.WRONG_EVENT);
    assertEquals(1223, code.codeValue());
}

If so, with such test I'm forced to write complete working implementation at once, with no baby steps as TDD states. I can mock out sosmething in Client but then I have to know this thing to be mocked upfront, so I need some upfront desing to be made.

Maybe I should start from the bottom, test this message formatting component first and then use it in right client test?

What way is the right one to go? Should we always start from top (how to deal with this huge step required)? Can we start with any class realizing tiny part of desired feature (as Formatter in this example)?

If I'd know where to hit with my tests it'd be a lot easier for me to proceed.

like image 928
tomek kos Avatar asked Nov 02 '11 21:11

tomek kos


3 Answers

I'd start with this line:

NotificationClient client = new NotificationClient("abcd1234"); // client ID

Sounds like we need a NotificationClient, which needs a client ID. That's an easy thing to test for. My first test might look something like:

public void testNewClientAbcd1234HasClientId() {
    NotificationClient client = new NotificationClient("abcd1234");
    assertEquals("abcd1234", client.clientId());
}

Of course, it won't compile at first - not until I'd written a NotificationClient class with a constructor that takes a string parameter and a clientId() method that returns a string - but that's part of the TDD cycle.

public class NotificationClient {
    public NotificationClient(string clientId) {
    }
    public string clientId() {
        return "";
    }
}

At this point, I can run my test and watch it fail (because I've hard-coded clientId()'s return to be an empty string). Once I've got my failing unit test, I write just enough production code (in NotificationClient) to get the test to pass:

    public string clientId() {
        return "abcd1234";
    }

Now all my tests pass, so I can consider what to do next. The obvious (well, obvious to me) next step is to make sure that I can create clients whose ID isn't "abcd1234":

public void testNewClientBcde2345HasClientId() {
    NotificationClient client = new NotificationClient("bcde2345");
    assertEquals("bcde2345", client.clientId());
}

I run my test suite and observe that testNewClientBcde2345HasClientId() fails while testNewClientAbcd1234HasClientId() passes, and now I've got a good reason to add a member variable to NotificationClient:

public class NotificationClient {
    private string _clientId;
    public NotificationClient(string clientId) {
        _clientId = clientId;
    }
    public string clientId() {
        return _clientId;
    }
}

Assuming no typographical errors have snuck in, that'll get all my tests to pass, and I can move on to whatever the next step is. (In your example, it would probably be testing that notifyOnEvent(Event.WRONG_EVENT) returns a Response whose codeValue() equals 1223.)

Does that help any?

like image 67
Edmund Schweppe Avatar answered Nov 15 '22 09:11

Edmund Schweppe


Don't confuse acceptance tests that hook into each end of your application, and form an executable specifications with unit tests.

If you are doing 'pure' TDD you write an acceptance test which drives the unit tests that drive the implementation. testClientSendInvalidEventCommand is your acceptance test, but depending on how complicated things are you will delegate the implementation to multiple classes you can unit test separately.

How complicated things get before you have to split them up to test and understand them properly is why it is called Test Driven Design.

like image 24
Garrett Hall Avatar answered Nov 15 '22 09:11

Garrett Hall


You can choose to let tests drive your design from the bottom up or from the top down. Both work well for different developers in different situations. Either approach will force to make some of those "upfront" design decisions but that's a good thing. Making those decisions in order to write your tests is test-driven design!

In your case you have an idea what the high level external interface to the system you are developing should be so let's start there. Write a test for how you think users of your notification client should interact with it and let it fail. This test is the basis for your acceptance or integration tests and they are going to continue failing until the features they describe are finished. That's ok. Now step down one level. What are the steps which need to occur to provide that high level interface? Can we write an integration or unit test for those steps? Do they have dependencies you had not considered which might cause you to change the notification center interface you have started to define? Keep drilling down depth-first defining behavior with failing tests until you find that you have actually reached a unit test. Now implement enough to pass that unit test and continue. Get unit tests passing until you have built enough to pass an integration test and so on. You'll eventually have completed a depth-first construction of a tree of tests and should have a well tested feature whose design was driven by your tests.

like image 45
Jonah Avatar answered Nov 15 '22 08:11

Jonah