Thursday, May 13, 2010

TDD kata for building Strategy Pattern in a domain model

TDD katas which use a Calculator or some kind of algorithmic test are valuable for learning red/green/refactor, design-by-test, use of Resharper, keyboard shortcuts, and speed. However, I also like working with katas that:
  • address a new technology (eg. Win Phone 7)
  • test mocking (eg. M-V-P presenter tests or MVC controller tests)
  • design a series of classes in a domain (eg. 5-day TDD kata)
In this case, I wanted to build a kata that specifically addresses the Strategy pattern within a domain model, and how to build it intuitively using design-by-test.

NOTE   The code for this kata is available here on github.

Without Strategy pattern (and more generally, without refactoring to maximum granularity), domain entities tend to grow fat, full of algorithmic business logic which they would be better to call as multiple domain service classes than to possess internally.

In this kata, the Treatment entity represents a treatment for a pet at the veterinarians' office, such as prescription, hydration, dietary advice, referral, etc. The type of treatment is implemented as a TreatmentType property member off of the Treatment class.

In a typical Strategy Pattern before-and-after scenario, the TreatmentType property would originally have encapsulated a switch statement, with different treatment type logic for each case, creating a fat entity.
  • NOTE A switch statement in itself is not bad; the point is that it should primarily be used for a very "thin" activity: generating the subclasses of the Strategy hierarchy.
In this TDD kata, we skip the before scenario and instead design directly to the working scenario, where we build the treatment types outside of the Treatment entity in the form of Strategy hierarchy. Upon completion, it should look something like this:

NOTE This kata will test a bit more than just Strategy. For entity classes in the above domain model (Pet and Treatment), we will also test for identity/equality by creating a base class (DomainEntity) which will handle these concerns. While entity identity is not central to Strategy, it's fundamental to the domain. So let's begin.

TDD Kata for Strategy
1) Create a Visual Studio solution with two projects/assemblies:
* Kata.Domain
* Kata.Tests.Unit (add the appropriate references to this project for the testing framework you prefer)

2) In Kata.Tests.Unit, create a test class, DomainEntityTests, and write a test for each of the following:
    a) that 2 instances of DomainEntity whose Id property is the same are equal

[Test]
public void TwoInstances_SameIdPropertySet_AreEqual()
{
    const int commonId = 32753;
    var sut1 = new DomainEntity {Id = commonId};
    var sut2 = new DomainEntity {Id = commonId};

    sut1.ShouldEqual(sut2);
}

    b) that 2 instances of DomainEntity whose Id property is different are not equal

[Test]
public void TwoInstances_DifferentIdPropertySet_AreNotEqual()
{
    var sut1 = new DomainEntity { Id = 4275 };
    var sut2 = new DomainEntity { Id = 3214 };

    sut1.ShouldNotEqual(sut2);
}


3) Create a test class, PetTests, which tests the following:
    a) that Pet is an instance of DomainEntity

[Test]
public void Constructor_NoInputParams_IsInstanceOfDomainEntity()
{
    var sut = new Pet();
    sut.ShouldBeInstanceOf<DomainEntity>();
}

    b) that Pet.Treatments association has 0 rows at first.

[Test]
public void TreatmentsProperty_Get_HasCountOf0Initially()
{
    var sut = new Pet();
    sut.Treatments.Count.ShouldEqual(0);
}

    c) that Pet.AddTreatment(treatment) increments the count by 1

[Test]
public void AddTreatmentMethod_PetInputParam_IncremantsTreatmentsCount()
{
    var sut = new Pet();
    sut.Treatments.Count.ShouldEqual(0);
    sut.AddTreatment(new Treatment());
    sut.Treatments.Count.ShouldEqual(1);
}


4) Create a test class, TreatmentTests, which tests the following:
    a) that Treatment is an instance of DomainEntity.

[Test]
public void Constructor_NoInputParams_IsInstanceOfDomainEntity()
{
    var sut = new Treatment();
    sut.ShouldBeInstanceOf<DomainEntity>();
}

We'll come back to add additional tests later.

5) Create a test class, TreatmentTypeTests, which tests the following:
    a) that TreatmentType has no public constructors (use LINQ)

[Test]
public void Constructors_Public_ShouldBeNone()
{
typeof(TreatmentType).GetConstructors()
.Where(constructorInfo => constructorInfo.IsPublic).Count().ShouldEqual(0);
}

NOTE This is because the base class of a Strategy hierarchy must use a creation method to control class instantiation, to only create subclasses of the base class.
    b) that the CreateTreatmentType creation method accepts a TreatmentTypeEnum enumeration, and returns an instance of the subclass specified by name in the enumeration. (For the first test, use Surgery).

[Test]
public void CreationMethod_SurgeryTreatmentEnumInput_ReturnsInstanceOfSurgery()
{
    var surgeryTreatment = TreatmentType.CreateTreatment(Treatment.TreatmentEnum.Surgery);
    surgeryTreatment.ShouldBeInstanceOf<Surgery>();
    surgeryTreatment.ShouldBeInstanceOf<TreatmentType>();
}

    c) recreate this test identically for every subclass type, including:
  • Dietary
  • Hydration
  • Prescription
  • Referral
  • Surgery
When you are done, the creation method of the TreatmentType Strategy base class should look like this:

public static TreatmentType CreateTreatment(Treatment.TreatmentEnum treatmentEnum)
{
    switch(treatmentEnum)
    {
        case Treatment.TreatmentEnum.Dietary:
            return new Dietary();
            break;
        case Treatment.TreatmentEnum.Hydration:
            return new Hydration();
            break;
        case Treatment.TreatmentEnum.Prescription:
            return new Prescription();
            break;
        case Treatment.TreatmentEnum.Referral:
            return new Referral();
            break;
        case Treatment.TreatmentEnum.Surgery:
            return new Surgery();
            break;
    }
    return null;
}


6) Now we come to the CourseOfAction property, which is the first property of the TreatmentType base class, and which is about to bring the Strategy hierarchy into full bloom. The default test will simply assert a value of CourseOfAction on the base class. Therefore:
     a) Test that a new property of TreamentType, string::CourseOfAction returns a string that says "do something".

[Test]
public void CourseOfActionProperty_SurgeryTreamentEnumInput_ReturnsSurgeryCourseOfAction()
{
    var sut = TreatmentType.CreateTreatment(Treatment.TreatmentEnum.Surgery);
    sut.CourseOfAction.ShouldEqual("Do something.");
}


7) Having a single value in the base class, "Do something", is kind of pointless, other than to introduce the property concept. What we really want is polymorphic differentiation for this property, where the output varies for each subclass. To do this, first modify the CourseOfAction property to be abstract:

public abstract string CourseOfAction { get; }

This immediately has two impacts:
    a) the compiler forces you to "correct" the class, making it abstract as well.

public abstract class TreatmentType
{
     ...
}

    a) the compiler generates errors, telling you that the abstract property must be implemented in each subclass. This is perfect, as it completes the Strategy. Correct this now:

[Test]
public void CourseOfActionProperty_DietaryTreamentEnumInput_ReturnsDietaryCourseOfAction()
{
var sut = TreatmentType.CreateTreatment(Treatment.TreatmentEnum.Dietary);
sut.CourseOfAction.ShouldEqual("Give dietary advice.");
}

[Test]
public void CourseOfActionProperty_HydrationTreamentEnumInput_ReturnsHydrationCourseOfAction()
{
var sut = TreatmentType.CreateTreatment(Treatment.TreatmentEnum.Hydration);
sut.CourseOfAction.ShouldEqual("Hydrate the pet.");
}
...etc...


8) Finally, we are ready to link TreatmentType up to Treatment. We will fully encapsulate the Strategy pattern by assigning the TreatmentEnum in the constructor of Treatment. The TreatmentType property will then simply return the correct subclass and subclass property values.
     a) Therefore, test that treatment.TreatmentType.CourseOfAction property results from a simple chain, given that the constructor is assigned a TreatmentEnum param.

[Test]
public void Constructor_TreatmentEnumParam_ReturnsCorrectCourseOfActionProperty()
{
    var sut = new Treatment(Treatment.TreatmentEnum.Dietary);
    sut.TreatmentType.CourseOfAction.ShouldEqual("Give dietary advice.");
}


9) Note that changing the Treatment entity constructor will break a few places in your code. Fix each one by assigning an enum to the constructor.

Congratulations! You have now implemented the complete Strategy Pattern as a domain service, completely encapsulated by Treatment. If you wish to compare your code, have a look at the implementation here, on github.

Saturday, May 01, 2010

How to write unit tests for your FluentNHibernate class maps

I've been building a spike to learn FluentNHibernate, with the constraint that each step I take, I start with a test. Since FluentNHibernate uses code rather than XML to specify the class mapping (using ClassMap), one of my goals has been to figure out how to test these code-based mappings.

The FluentNHibernate (source on github) includes extensive unit tests, one of which, PersistenceSpecificationTester, performs detailed tests using PersistenceSpecification<T> to validate class maps.

For the spike, I've tried to simplify this down as much as possible, working with a single entity, a base class (DomainEntity, to handle equality) and a static utility method for building an ISessionSource containing any entity class.

Use the following to build your class mapping test:

1) Create a domain model with two entities:
  • Product
  • DomainEntity, a common base class for handling identity/equality

Product.cs

public class Product : DomainEntity
{
    // Location is an immutable value object containing Aisle and Shelf properties;
    // included to demonstrate FluentNHibernate's mapping of an entity component
    public Location Location { get; set; }
    public decimal Price { get; set; }
    public string Name { get; set; }
}


DomainEntity.cs

public class DomainEntity
{
    public int Id { get; set; }

    public override bool Equals(object obj)
    {
        var other = obj as DomainEntity;

        return other != null 
            && other.Id > 0
            && other.Id.Equals(this.Id);
    }

    public override int GetHashCode()
    {
        return this.Id.GetHashCode();
    }
}


2) Create a mapping file using FluentNHibernate's ClassMap<T>.

ProductMap.cs

public class ProductMap : ClassMap<Product>
{
    public ProductMap()
    {
        Id(x => x.Id);
        Map(x => x.Name);
        Map(x => x.Price);

        Component(x => x.Location, m =>
        {
            m.Map(x => x.Aisle);
            m.Map(x => x.Shelf);
        });
    }
}


3) Create a static class/method that accepts any domain entity class (i.e. which extends DomainEntity) and returns a mock in-memory NHibernate session containing an instance of the entity (which we will use next to test the mappings).

FluentNHibernateMappingTester.cs

using FluentNHibernate;
using Gaddzeit.Spike.Domain.Entities;
using NHibernate;
using Rhino.Mocks;

namespace Gaddzeit.Spike.Tests.Unit
{
    public static class FluentNHibernateMappingTester
    {
        public static ISessionSource GetNHibernateSessionWithWrappedEntity<T>(T tMappedEntityWithinSession) where T : DomainEntity
        {
            var transaction = MockRepository.GenerateStub();
            var session = MockRepository.GenerateStub();
            session.Stub(s => s.BeginTransaction()).Return(transaction);
            session.Stub(s => s.Get<T>(null)).IgnoreArguments().Return(tMappedEntityWithinSession);
            session.Stub(s => s.GetIdentifier(tMappedEntityWithinSession)).Return(tMappedEntityWithinSession.Id);

            var sessionSource = MockRepository.GenerateStub();
            sessionSource.Stub(ss => ss.CreateSession()).Return(session);
            return sessionSource;
        }
    }
}


4. To test the entity mapping, you need a comparer class that implements IEqualityComparer, and can test both the equality of the entity itself, as well as the equality of any of its properties.

ProductComparer.cs

public class ProductComparer : IEqualityComparer
{
    public bool Equals(object x, object y)
    {
        if (x is Product && y is Product)
            return ((Product)x).Id == ((Product)y).Id;
        if (x is string && y is string)
            return x.ToString().Equals(y.ToString());
        if (x is decimal && y is decimal)
            return Convert.ToDecimal(x).Equals(Convert.ToDecimal(y));
        throw new EqualityComparerUnhandledComparisonException();
    }

    public int GetHashCode(object obj)
    {
        if (obj is Product) 
            return ((Product) obj).GetHashCode();
        if (obj is string) 
            return obj.ToString().GetHashCode();
        if (obj is decimal)
            return Convert.ToDecimal(obj).GetHashCode();
        throw new EqualityComparerUnhandledComparisonException();
    }
}


5. Finally: create a test for the mapping of Product, which calls to the static method to setup the Product instance in the mocked in-memory NHibernate Session (returned as ISessionSource), and then passes ISessionSource and the above ProductMapper into PersistenceSpecification in order to perform validation on any mapping specified. In the test below, we test only one of the property mappings, Product.Name by calling the PersistenceSpecification.CheckProperty() method.

ProductMappingTests.cs

using FluentNHibernate.Testing;
using Gaddzeit.Spike.Domain.DomainServices;
using Gaddzeit.Spike.Domain.Entities;
using NUnit.Framework;

namespace Gaddzeit.Spike.Tests.Unit
{
    [TestFixture]
    public class ProductMappingTests
    {
        [Test]
        public void NameProperty_IsMapped_Correctly()
        {
            var product = new Product
            {
                Id = 35,
                Location = new Location(35, 27),
                Name = "Hammer",
                Price = 35.99M
            };

            var identicalProduct = new Product
            {
                Id = 35,
                Location = new Location(35, 27),
                Name = "Hammer",
                Price = 35.99M
            };
            var sessionSource = FluentNHibernateMappingTester.GetNHibernateSessionWithWrappedEntity(identicalProduct);
            var spec = new PersistenceSpecification(sessionSource, new ProductComparer());
            spec.CheckProperty(p => p.Name, product.Name).VerifyTheMappings();
        }

    }
}


That's it! Your test should pass, and you have verified that your entity is mapped correctly, without setting up a real connection to a database.