Rethinking the use of TDD

December 26 · 8 mins read

This is what Jimmy Bogard said in the conclusion of his talk, “Crafting Wicked Domain Models” (NDC 2012):

DDD is all about building domain models that encapsulate the behavior.

So I’m not hiding behavior. I’m just saying it’s up to the domain model itself to perform all these operations itself, and its gonna take care of its own. It defines its boundaries — it does not let anyone do whatever it wants. It wraps up everyting nicely in a nice neat bowl.

This is how I was able to eliminate bugs in our application a lot more easily than just writing a whole bunch of tests. Writing a bunch of tests still requires me to know how to write those tests, but in this case the domain model is offering me the right path.

That last sentence makes me rethink the use of TDD in creating (almost) bug-free software.

If you watch the video, Jimmy Bogard moved the business rules (or domain logic) from the AssignOffer() method into the entity classes.

The original AssignOffer() method (at about 16:00 mins in the video) looks like this:

public void AssignOffer(Guid memberId, Guid offerTypeId)
{
    var member = _memberRepository.GetById(memberId);
    var offerType = _offerTypeRepository.GetById(offerTypeId);

    var value = _offerValueCalculator.CalculateValue(member, offerType);

    DateTime dateExpiring
    switch (offerType.ExpirationType)
    {
        case ExpirationType.Assignment:
            dateExpiring = DateTime.Now.AddDays(offerType.DaysValid);
            break;
        case ExpirationType.Fixed:
            if (offerType.BeginDate != null)
                dateExpiring = 
                    offerType.BeginDate.Value.AddDays(offerType.DaysValid);
            else
                throw new InvalidOperationException();
            break;
        default:
            throw ArgumentOutOfRangeException();
    }

    var offer = new Offer
    {
        MemberAssigned = member,
        Type = offerType,
        Value = value,
        DateExpiring = dateExpiring
    };
    member.AssignedOffers.Add(offer);
    member.NumberOfActiveOffers++;

    _offerRepository.Save(offer);
}

Then it is transformed into this very simple method:

public void AssignOffer(Guid memberId, Guid offerTypeId)
{
    var member = _memberRepository.GetById(memberId);
    var offerType = _offerTypeRepository.GetById(offerTypeId);

    member.AssignOffer(offerType, _offerValueCalculator);
}

Wow! Very easy to read! (Please note that it was not easy to write — the transformation was not easy to do, I think, except maybe when you are a master already, or a codesmith at least. :smile:)

And the business rules have been moved to the entity (or the domain model) classes.

And the resulting entity classes look good! And they look testable! (Except, I think, on the part which uses DateTime.Now.)

Wow!

But he was not writing tests to guide him when transforming the entities! And, worse, he said that what he did was easier than writing a whole bunch of tests before doing any transformations!

Oh no! Was Jimmy Bogard opposed to TDD??

Maybe he was not… because the original code did not seem to have been written using TDD, because, first, he did not say that it has existing tests, and second, because it has a DateTime.Now in it…

    dateExpiring = DateTime.Now.AddDays(offerType.DaysValid);

… That would not be easy to test, because of the use of a static method (or a static readonly property in this case).

… I think if TDD was used to write the original code, the DateTime.Now should not have been there. Instead, there will be something like a DateService object where we can get the dates from, like this:

    dateExpiring = 
        _dateService.GetDateTimeNow().AddDays(offerType.DaysValid);

So maybe Jimmy Bogard was not opposed to TDD…

Maybe he was opposed only to writing tests after-the-fact, that is, writing tests after the production code is already written, because that is hard to do (unless you are Michael Feathers :smile:).

Okay! Great!

But if we can still come up with a good design even when we are not using TDD, maybe we do not need TDD at all! (Except maybe when teaching good design)

Hmmmm…

Or maybe not… Maybe we still need TDD…

Because TDD has other uses, such as giving us confidence when cleaning our code base, and having examples to help others understand our code base.

Kent Beck also, in his debate with DHH, said that the first reason why he writes tests first was to have fast feedback on whether what he is doing is correct or not. That is one of the advantages of writing the tests first: fast feedback loop.

In the end, I think, whether TDD is useful or not depends on the values being adopted by the members of a software team.

Buy Me A Coffee