Implementing a Feature With TDD

A few days ago, I heard somebody ask “How do I start developing a functionality with TDD?” and I thought immediately that it would be a good idea to write about it.

A History

Suppose you are really popular, with thousands of friends in the Facebook, everybody in your neighborhood knows you and enjoys your company. In fact everyone in your city does.

You possess a huge LP collection, ranging from Van Halen to John Coltrane, art pieces, a vast library containing books (some people believe you own an original first edition Shakespeare’s Romeo and Julieta), comics, CDs, DVDs.

João Gilberto en Mexico
João Gilberto en Mexico (Photo credit: Wikipedia)

Of course your friends are always asking to borrow Tom Jobim‘s and B.B. King‘s LPs, rare books, the most beloved CDs, DVDs and even daddy’s 1964 Rolls-Royce, which you lent to one of your closest friend to impress a girl.

But sometimes people forget to give back the books you lent (another version of the legend says that your grandfather lost that Shakespeare’s original first edition to a sheik that never returned it); it’s painful to remember that no one knows who took an amazing João Gilberto LP. Daddy’s 1964 Rolls-Royce is in the garage now… Although its entire left side is scratched.

Most of the time, however, people borrowing your stuff is a very good business. You get tickets for theater, sports, concerts and so on almost every week, are often invited to great parties and there’s that nice friend of yours that is always willing to let you spend holidays in his house at Italian Riviera.

So, how to make the most of this situation?

Just denying everything to everyone may hurt the feelings of good (and more generous) friends, right? A software to help determine if you’ll ever see that Lennon’s pair of socks again and will gain cuban cigars in return is what you need.

What we want to know is how correct are our your friends based on their historic. The application indicates a conservative position to those who have a bad or no historical at all, so the more trouble a friend causes, the worse is his credibility and vice-versa. On the other hand, generous fellows gain “trust points”.

The system will advise you calculating risks and opportunities based on the credibility of your friends and the degree of importance of the items in your collection.

So, where to begin?

Let’s start writing a test for the feature we want: check a friend’s credibility.

// Using JUnit 4
public class ScoreTest {
    @Test public void adviceAboutMyFriends() {
        score.checkCredibilityOf(thisFriend);
    }
}

It’s a good idea to write exactly what we’re testing and what we expect to achieve.

public class ScoreTest {
    @Test public void adviceAboutMyFriendsShouldHelpMeDecideWhetherOrNotToLend() {
        score.checkCredibilityOf(thisFriend);
    }
}

To lend or not to lend what? The advice will vary according to the friend and the item he wants to borrow, hence it’s necessary to consider items too.

public class ScoreTest {
    @Test public void adviceAboutMyFriendsShouldHelpMeDecideWhetherOrNotToLend() {
        score.checkCredibilityOf(thisFriend, thisItem);
    }
}

The word “this” is redundant and is becoming quite annoying, thereby we should avoid it for now on.

What will score#checkCredibilityOf returns? A boolean whose value would mean “go ahead and lend it” if true, or “don’t even think about it” otherwise? How about a class instance representing the general friend’s credibility and responsible to calculate whether someone is reliable enough to take a certain item, then finally returns a boolean? Something like…

public class ScoreTest {
    @Test public void adviceAboutMyFriendsShouldHelpMeDecideWhetherOrNotToLend() {
        boolean reliable = score.checkCredibilityOf(friend).to(item);
    }
}

Interesting, easy to read, but a little stilted for my taste. We just get started, so let’s keep things as simple as we can. Another alternative is to dispense the score instance to rate your fellows and implement this feature directly in Friend or Item class.

        . . .
        item.canBeBorrowedBy(friend); // 1st option
        // or
        friend.canBorrow(item); // 2nd
        . . .

The first option is in the passive voice, which some editors argue that it is more difficult to read and understand. The second one is more concise and describes better what we want, so I’ll pick it (note, however, that it’s an entirely subjective choice). Then we finish implementing the first version of this unit test.

public class ScoreTest {
    @Test public void adviceAboutMyFriendsShouldHelpMeDecideWhetherOrNotToLend() {
        Friend friend = new Friend();
        Item item = new Item();
        boolean reliable = friend.canBorrow(item);
        assertTrue(reliable); // static import of org.junit.Test.Assert.assertTrue
    }
}

This code doesn’t even compiles…

… therefore we begin implementing it creating Friend and Item classes, then the #canBorrow method.

1) First

public class Friend {
}

2) Then

public class Item {
}

3) At long last

public class Friend {

    public boolean canBorrow(Item item) {
        return true;
    }
}

Run the test and you’ll see that it works fine (the JUnit green bar appears). However it only expects reliable friends so far, disconsidering the unreliable ones.

The application, obviously, must distinguish well both kind of friends

public class ScoreTest {
    @Test public void adviceAboutMyFriendsShouldHelpMeDecideWhetherOrNotToLend() {
        Item item = new Item();
        Friend reliable = new Friend();
        assertTrue(reliable.canBorrow(item));
        Friend unreliable = new Friend();
        assertFalse(unreliable.canBorrow(item));
    }
}

Run it again. The test fails on the line 7. Up to now, our system is unable to say who is or isn’t trustworthy. What’s the simplest way possible to implement #canBorrow and make the test green? (By the way, “make the test green” or “get the test from red to green” are examples of expressions often used meaning “make the test works”. Again, green and red are references to the JUnit bar, which appears green when everything is ok or red when some test fail. But you already knew that. ;))

Here goes our first attempt.

public class Friend {
    private final boolean reliable;

    public Friend(boolean reliable) {
        this.reliable = reliable;
    }
    public boolean canBorrow(Item item) {
        return reliable;
    }
}

Fix the test which was broken by the changes in Friend.

public class ScoreTest {
    @Test public void adviceAboutMyFriendsShouldHelpMeDecideWhetherOrNotToLend() {
        Item item = new Item();
        Friend reliable = new Friend(true);
        assertTrue(reliable.canBorrow(item));
        Friend unreliable = new Friend(false);
        assertFalse(unreliable.canBorrow(item));
    }
}

Yes! Green bar, baby! What? How so? Do you think we could move faster towards a final implementation without these tiny steps?

A little pause to think about tiny steps

Borrowing Kent Beck words (p. 9)[1]:

Do these steps seem to small to you? Remember, TDD is not about taking teeny-tiny steps, it’s about being able to take teeny-tine steps. Would I code day-to-day with steps this small? No. But when things get the least bit weird, I’m glad I can. Try teeny-tyne steps with an example of your own choosing. If you can make steps too small, you can certainly make steps the right size. If you only take larger steps, you’ll never know if smaller steps are appropriate.

It’s time to think about the algorithm a little more carefully

The algorithm has to take into account both friends and items. There are many ways to calculate a score based on historical data, foreseeing risks and opportunities. You could call a specialist but you’d rather to do it by yourself and improve it as necessary.

A good start is to define the degree of important of your stuff and rank your friends. Items are classified as disposable, ordinary, day-to-day use, cool stuff, expensive, proud of the family, piece of art and eighth wonder of the world.

public class Item {

    public enum Importance {
        DISPOSABLE, ORDINARY, DAY_TO_DAY_USE,
        COOL_STUFF, EXPENSIVE,
        PROUD_OF_THE_FAMILY, PIECE_OF_ART, EIGHTH_WONDER_OF_THE_WORLD;
    }

    private final Importance importance;

    public Item(Importance importance) {
        this.importance = importance;
    }

    /*
     * This class isn't designed to be a JavaBean, so you can omit the 'get'
     * prefix if you want. It's a matter of taste.
     */
    public Importance importance() {
        return importance;
    }
}

Surely you don’t call your friend names, but it’s perfectly acceptable to rank their attitudes, maybe from 0 (pleasant yet understands the verb “lend” as “give”) to 100 (Dalai Lama).

public class Friend {
    private final int rank; // ranked from 0 (Zé Ruela) to 100 (Dalai Lama)

    public Friend(int rank) {
        // validate(rank) omitted for brevity
        this.rank = rank;
    }
    public boolean canBorrow(Item item) {
        // validate(item) omitted for brevity
        return true; // just returns false for now
    }
}

Pause to think about invariants

Don’t forget to unit test and implement Friend class’s invariants: rank cannot be less than 0 or greater than 100, while item cannot be null.

We’re back

These last changes broke the test. Next step is to make it work again.

public class ScoreTest {
    @Test public void adviceAboutMyFriendsShouldHelpMeDecideWhetherOrNotToLend() {
        Item item = new Item(Importance.EXPENSIVE);
        Friend reliable = new Friend(98);
        assertTrue(reliable.canBorrow(item));
        Friend unreliable = new Friend(10);
        assertFalse(unreliable.canBorrow(item));
    }
}

The test code above says that a friend with 98 points can borrow expensive items, but those with 10 cannot. As we’re testing the rule for expensive items, it would be better to clearly state what we’re doing.

public class ScoreTest {
    @Test public void canLendExpensiveItemsOnlyToFriendsRankedWith98OrMorePoints() {
        Item art = new Item(Importance.EXPENSIVE);
        Friend reliable = new Friend(98);
        assertTrue(reliable.canBorrow(art));
        Friend unreliable = new Friend(10);
        assertFalse(unreliable.canBorrow(art));
    }
}

Ok, it’s compiling and red (failing). We’re doing like Kent Back said (p. 7)[1]:

  1. Run a little test
  2. Run all test and fail
  3. Make a little change
  4. Run the tests and succeed
  5. Refactor to remove duplication.

A possible solution to make the test green again is to change Friend as follows:

public class Friend {
    private final int rank;

    public Friend(int rank) {
        this.rank = rank;
    }
    public boolean canBorrow(Item item) {
        // This fix the test
        return (item.importance() == Item.Importance.EXPENSIVE) && (rank >= 98);
    }
}

The rule for disposable items is extremely easy to implement, since you don’t mind lend them to any one – unfortunately (or fortunately, depending how you see it) there’s not much of these items in your collection. Besides that, nobody wants to borrow them.

    . . .
    // add test for disposable items
    @Test public void canLendTrashToEveryOne() {
        Item trash = new Item(Importance.DISPOSABLE);
        Friend reliable = new Friend(98);
        assertTrue(reliable.canBorrow(trash));
        Friend unreliable = new Friend(10);
        assertTrue(unreliable.canBorrow(trash));
    }

The cycle repeats. #canLendTrashToEveryOne is red. You need to implement Friend#canBorrow in order to make both tests succeed as well as programming the other rules:

  1. Friends with 43 or more points may borrow ordinary items
  2. With 75 or greater, they’re able to take home day-to-day use
  3. Cool-stuff, only who has 82 points or greater
  4. After the scratch in daddy’s car, no one can grab “proud of the family” and “piece of arts” (at least for a while)
  5. Wonders never come out from where they’re hidden (since your grandpapa lost that book, says the legend).

Everything pretty straightforward to implement…

… and yet not completely useful. We’ve been ranking your friends manually, therefore your next step will probably focus on the machinery that estimates friends’ score. There are other facts you may consider as well, like:

  1. who already picked objects, and what kind, when asking you to lend something
  2. those who are generous with you probably deserve extra points in their scores
  3. the app can even suggest you to offer another nice stuff when a very good friend of yours ask for something…

References

  1. ^Back, Kent. Test-Driven Development: by Example. Boston, MA: Addison-Wesley, 2003. ISBN: 0321146530.
Advertisements

Leave a Reply

Fill in your details below or click an icon to log in:

WordPress.com Logo

You are commenting using your WordPress.com account. Log Out / Change )

Twitter picture

You are commenting using your Twitter account. Log Out / Change )

Facebook photo

You are commenting using your Facebook account. Log Out / Change )

Google+ photo

You are commenting using your Google+ account. Log Out / Change )

Connecting to %s