Archive for the ‘Agile’ Category

TDD test drive number 3

Thursday, September 23rd, 2010

On two prior occasions, I have given TDD a test drive. Neither attempt convinced me that it was a useful way to design a system. To me, TDD has always stood for Top Down Design.

On the first attempt way back in 2006, I added a step to the Red Green Refactor cadence. I Redrew the design diagram before Green. My observation was that tests are no substitute for visualizing the solution.

On the second attempt in January 2009, I used TDD to design and construct a web navigation framework. It had three intersecting axes: URL mapping, security, and menu hierarchy. It ended up being extremely well tested, but incredibly complex. It worked, and continues to work, but it defies understanding.

Last month I began attending Dallas Geek Night at ThoughtWorks. This is a weekly meetup where open-source developers pair up and work on each other’s projects. I’ve used this opportunity to improve the tests for both Update Controls and Correspondence.

All of the work is done in pairs, and all of it is test driven.

Question and answer

On that first day, I paired with Richard Jensen, one of the organizers of the event. He asked questions about Update Controls. We answered those questions with tests.

For example, does Update Controls notify when something doesn’t actually change?

[TestMethod]
public void WhenSortOrderIsChangedButNotDifferentWeShouldNotGetACollectionChanged()
{
    ContactViewModel defaultOrder = _viewModel.Contacts.First();
    _viewModel.SortOrder = ContactListSortOrder.FirstName;

    ContactViewModel firstByFirstName = _viewModel.Contacts.First();
    _viewModel.SortOrder = ContactListSortOrder.FirstName;

    Assert.AreEqual(1, _collectionChangedCount);

}

At first this test failed. Then we added this code to make it pass:

public ContactListSortOrder SortOrder
{
    get
    {
        _indSortOrder.OnGet();
        return _sortOrder;
    }
    set
    {
        if (_sortOrder != value) _indSortOrder.OnSet();
        _sortOrder = value;
    }
}

I could have just answered the question, but this helped keep the discussion measureable. A simple answer would have just been based on faith, and could even have been wrong.

Finding bugs

On the second day, I paired with Dhruv Chandna. We were testing Correspondence synchronizing between two machines. He asked some good questions, too.

For example, what would happen if I added a fact to the opposite community:

[TestMethod]
public void WhenLogOnToOtherMachine_ShouldThrow()
{
    Machine playerOneMachine = _playerOneCommuniy.AddFact(new Machine());
    User playerOne = _playerOneCommuniy.AddFact(new User("one"));

    try
    {
        _playerTwoCommuniy.AddFact(new LogOn(playerOne, playerOneMachine));
        Assert.Fail("AddFact did not throw.");
    }
    catch (CorrespondenceException ex)
    {
        Assert.AreEqual("A fact cannot be added to a different community than its predecessors.", ex.Message);
    }
}

This error should have been caught. It was not. So after writing this failing test, Dhruv dug into the code base and fixed it.

foreach (RoleMemento role in prototype.PredecessorRoles)
{
    PredecessorBase predecessor = prototype.GetPredecessor(role);
    if (predecessor.Community != null && predecessor.Community != _community)
        throw new CorrespondenceException("A fact cannot be added to a different community than its predecessors.");
}

There were a few other changes required to make this work, but they did not significantly impact the design.

TDD as communication

Dan North says TDD is not about testing, it’s all about design. My experience tells me otherwise. Whenever I’ve let my design emerge through tests, I’ve ended up with an overly complex mess. It’s like pouring concrete into a mold. It takes the right shape because it is forced to. But the results are not as pretty as a sculpture.

Instead, I’m beginning to see TDD as a form of communication. This helps a pair to discuss a problem in tangible terms. It gives them something to point to. And it gives you a specific goal so that the conversation doesn’t get derailed. You are done when the test passes.

So I will continue to construct top-down designs. I will continue to use unit tests to verify my implementation of those designs. But when I pair, we will communicate through TDD.

AiS 41: Scrum Doesn’t Suck

Friday, March 28th, 2008

Listen Now

Agile is Relative by Scott Ambler.
The Agile Manifesto.
Scrum on Wikipedia.

AiS 26: The Case for Proven Design

Tuesday, May 22nd, 2007

Listen Now

If you’ve taken a course in analysis of algorithms, then you’ve learned how to prove that an algorithm will have a certain result. You’ve proven that quicksort will terminate, that it will actually sort the list, and that it will complete in n log n time. But in the real world, all of the sorting and searching algorithms are already written. We no longer need to prove algorithms. Now we need to prove designs.

Cycle of discovery
Programmers tend to work in a code-test cycle. We’ll write some code, then we’ll see if it does what it should. Then we’ll go back and change the code and test again. The cycle continues until the feature is complete. This is a cycle of discovery. This is how we explore new things. This is the scientific process. This is not how other engineering professions work. Sure, it gets the job done, but it’s not the most efficient way to do it.

Agile development techniques put new labels on the cycle of discovery. Now instead of code-test we have red-green-refactor. But it’s still the same cycle. We don’t know ahead of time that the code we are about to write is correct. We just keep typing until it works. In this world you don’t know how close you are to the finish line until you cross it. How are we ever to estimate our work? Or mitigate risk?

Would you buy a house from someone who says, “let’s just keep nailing boards together until it looks like a building”? Of course not. But most software is more complex than a house. It takes longer to build. It has more moving parts. It’s more expensive. We should be doing at least as much as a home builder to ensure that we will deliver the right product, at high quality, on time, and on budget.

We can do that by proving our design.

Cycle of proof
In my day-to-day work, I follow a different cycle. I’ll learn the requirements of a feature, design the feature, code it once, and then fix bugs. I don’t spin in a tight code-test cycle. Once I know what the design should be, I just sit down and write it all out. Sure, I may have bugs and typos, but they tend to be easy to fix. I may go days between compiles, but in the end I know that it will work. I know because I’ve proven it.

AiS 24: Refactoring

Wednesday, May 2nd, 2007

Listen Now

Refactoring is the process of changing the structure of code without changing its behavior. Martin Fowler coined the term when he wrote the book on the subject. Not only does the book define refactoring and describe when to use it, but it also provides a catalog of recipes for refactoring code. By iteratively applying these transformations to a system, Fowler says that you can evolve the design of existing code.

I use refactoring in my day-to-day coding, but not in the way that Fowler recommends. I design my software systems up front. I don't expect the design to evolve into a good solution. However, I will often use the automated refactoring built into tools like Eclipse as a way to edit code. The tool is a faster typist than I am, and it won't make a mistake due to a copy/paste fumble.

Refactoring, as most agile development techniques, works best when you have a small team of talented developers working in uncharted territory. Agile techniques move the methodology out of their way and allow them to be talented. However, if you know where you are going, it is much better to draw up a plan to get there.

http://www.refactoring.com/
http://www.eclipse.org/
http://www.jetbrains.com/resharper/