8

I basically have the gist of TDD. I'm sold that it's useful and I've got a reasonable command of the MSTEST framework. However, to date I have not been able to graduate to using it as a primary development method. Mostly, I use it as a surrogate for writing console apps as test drivers (my traditional approach).

The most useful thing about it for me is the way it absorbs the role of regression testing.

I have not yet built anything yet that specifically isolates various testable behaviors, which is another big part of the picture I know.

So this question is to ask for pointers on what the first test(s) I might write for the following development task: I want to produce code that encapsulates task execution in the fashion of producer/consumer.

I stopped and decided to write this question after I wrote this code (wondering if I could actually use TDD for real this time)

Code:

interface ITask
{
    Guid TaskId { get; }
    bool IsComplete { get; }
    bool IsFailed { get; }
    bool IsRunning { get; }
}

interface ITaskContainer
{
    Guid AddTask(ICommand action);
}

interface ICommand
{
    string CommandName { get; }
    Dictionary<string, object> Parameters { get; }
    void Execute();
}
3
  • You should have written the tests first and THEN the interfaces! The whole idea is that TDD is for your API.
    – user1249
    Apr 11, 2012 at 22:15
  • 1
    How does one write tests for interfaces that don't yet exist? They won't even compile. Apr 11, 2012 at 23:15
  • 6
    That's your first failing test.
    – cori
    Apr 12, 2012 at 19:48

1 Answer 1

10

Starting with this concept:
1) Start with the behavior that you desire. Write a test for it. See test fail.
2) Write enough code to get the test to pass. See all tests pass.
3) Look for redundant / sloppy code -> refactor. See tests still pass. Goto 1

So on #1, let's say that you want to create a new command (I'm stretching to how the command would work, so bear with me). (Also, I'll be a bit pragmatic rather than extreme TDD)

The new command is called MakeMyLunch, so you first create a test to instantiate it and get the command name:

@Test
public void instantiateMakeMyLunch() {
   ICommand command = new MakeMyLunchCommand();
   assertEquals("makeMyLunch",command.getCommandName());
}

This fails, forcing you to create the new command class and have it return its name (purist would say this is two rounds of TDD, not 1). So you create the class and have it implement the ICommand interface, including returning the command name. Running all tests now shows all pass, so you proceed to look for refactoring opportunities. Probably none.

So next you want it to implement execute. So you have to ask: how do I know that "MakeMyLunch" successfully "made my lunch". What changes in the system because of this operation? Can I test for this?

Suppose it is easy to test for:

@Test
public void checkThatMakeMyLunchIsSuccessful() {
   ICommand command = new MakeMyLunchCommand();
   command.execute();
   assertTrue( Lunch.isReady() );
}

Other times, this is more difficult, and what you really want to do is test the responsibilities of the subject-under-test (MakeMyLunchCommand). Perhaps the responsibility of MakeMyLunchCommand is to interact with Fridge and Microwave. So to test it you can use a mock Fridge and mock Microwave. [two sample mock frameworks are Mockito and nMock or look here.]

In which case you would do something like the following pseudo code:

@Test
public void checkThatMakeMyLunchIsSuccessful() {
   Fridge mockFridge = mock(Fridge);
   Microwave mockMicrowave = mock(Microwave);
   ICommand command = new MakeMyLunchCommand( mockFridge, mockMicrowave );
   command.execute();
   mockFramework.assertCalled( mockFridge.removeFood );
   mockFramework.assertCalled( microwave.turnon );
}

The purist says test the responsibility of your class - its interactions with other classes (did the command open the fridge and turn on the microwave?).

The pragmatist says test for a group of classes and test for the outcome (is your lunch ready?).

Find the right balance that works for your system.

(Note: consider that perhaps you arrived at your interface structure too early. Perhaps you can let this evolve as you write your unit tests and implementations, and in step #3 you "notice" the common interface opportunity).

4
  • if i hadn't pre written my interface, what question would have led to the creation of the Execute() method - some of my initial attempts to TDD stalled when I didn't have a "step" to stimulate additional functionality - i get the feeling theres a latent chicken/egg issue that has to be sidestepped Apr 11, 2012 at 22:11
  • 1
    Good question! If the only command that you made was "MakeMyLunchCommand" the method may have started with ".makeMyLunch()". Which would have been fine. Then you make another command ("NapCommand.takeNap()"). Still no issue with different methods. Then you start to use it in your ecosystem, which is likely where you are forced to generalize into the ICommand interface. In general, you often delay generalization until the last responsible moment, because YAGNI [en.wikipedia.org/wiki/You_ain't_gonna_need_it ] =) Other times, you know you are going to get there so you start with it.
    – jayraynet
    Apr 11, 2012 at 22:15
  • (Also of assumption here is that you are using a modern IDE that makes refactoring things like method names trivial)
    – jayraynet
    Apr 11, 2012 at 22:19
  • 1
    thanks again for the advice, it's kind of a milestone for me to finally see all the pieces and how they fit - and yes, quick refactor is in my toolbox Apr 11, 2012 at 22:24

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service and acknowledge that you have read and understand our privacy policy and code of conduct.

Not the answer you're looking for? Browse other questions tagged or ask your own question.