Using a DSL to create a fluent interface for unit testing your domain model

Published 09 January 07 08:52 AM | cjlotz

Martin Fowler's recent post on ExpressionBuilders had me smiling as we ran into a similar problem at work last year.  It is great to see that our solution closely mirrors the ExpressionBuilder solution he writes about.  We even named our classes Builders as well smile_regular  Anyway, I've been wanting to blog about it for quite a while now. 

Problem

The problem we needed to solve was how to effectively unit test our domain model that we created using the principles highlighted in Domain Driven Design.  The domain model is quite big and we needed the domain model to be populated with different test scenarios to run our unit tests to verify all the hundreds of business rules that we have coded.  When I say hundreds I am not joking as we are modelling complex life insurance products.

Solutions

The first approach we considered involved scripting the test cases into SQL and using a tool like DataFresh to load the test cases into our database sandbox, executing the tests and then reverting the database tables back to their original state at test tear down.  We also considered MbUnit's support for running the tests in a COM+ transaction to isolate the database changes. This solution had quite a few problems in our minds:

  • Very cumbersome to create the test cases in SQL script.  It is easy enough to create the script files once you have a populated database using a tool like Apex SQLDiff, but getting the data into the database is the problem. 
  • Slow tests due to integration with the database from within the unit tests
  • Dependence on the database within unit tests
  • Test data brittleness due to domain model changes.  Any change to the domain model could potentially result in the test data being invalidated.  No intellisense or compile time errors to highlight these kind of errors.

We decided against using this approach.

The second approach we considered was using a test pattern called ObjectMother[1].  The ObjectMother is a kind of Test Helper that you code:

"One of the most time-consuming and unfulfilling activities in testing is coding and maintaining test objects. The ObjectMother pattern addresses this issue by proposing a simple, scalable framework whose sole purpose is to construct and facilitate the customizations of test objects. The pattern’s primary responsibilities are: to create test objects, to tailor those objects at any point during the testing process, and to terminate those objects once testing is complete. When the process of test-writing is made easier and the quality of test data more consistent, developers are likely to write more and better tests." [1]

This solution addresses all of the problems of the first solution.  No database dependence, quick running tests, compile time checking of domain model changes due to fact that the ObjectMother is POCO so we decided on using this approach.  This resulted in code looking something like this:

public static class PersonObjectMother { public static Person CreatePerson(Title title, string surname, string firstName, string initials, string idNumber, DateTime dateOfBirth, Gender gender, MaritalStatus maritalStatus, CorrespondenceLanguage preferredLanguage, decimal monthlyIncome) { Person person = new Person(); person.Title = title; person.Surname = surname; person.FirstName = firstName; person.Initials = initials; person.IdNumber = idNumber; person.DateOfBirth = dateOfBirth; person.Gender = gender; person.MaritalStatus = maritalStatus; person.PreferredLanguage = preferredLanguage; person.MonthlyIncome = monthlyIncome; return person; } public static Person CreateHennieLouw() { Person hennieLouw = CreatePerson(Title.Mr, "Louw", "Hennie", "H", "7108070108083", new DateTime(1971, 8, 7), Gender.Male, MaritalStatus.Married, CorrespondenceLanguage.English, 10000M); AttachOccupation(hennieLouw, "Software Developer", "Microsoft Ltd", "Pinelands"); AttachCellNumber(hennieLouw, "+27", "82", "4827"); AttachHomeNumber(hennieLouw, "+27", "21", "9814402"); AttachPostalAddress(hennieLouw, "6 Sinai Close", "Cape Town", "8000"); AttachHomeEmail("hennie.louw@gmail.com"); return hennieLouw; }

The code snippet illustrates a simplified version of a PersonObjectMother that contains some creation methods to create populated Person objects.  It provides a parameterized creation method to allow construction of any custom Person object but also provides a named state reaching method for creating a well-known object instance for a pseudo Hennie Muller person.   This object mother contains some various other attachment methods (not shown here for brevity sake) to create more fully populated Person as illustrated in the CreateHennieLouw method. 

As our domain model grew we inevitably had to refactor some of the ObjectMothers' creation and attachment methods and after a while creating and maintaining these was becoming a real pain.  The granularity of the methods often caused problems with a change to a creation/attachment method flowing throughout all the test cases and all the unit tests that were using them. I started wondering whether there wasn't a more elegant solution for creating test cases for our domain model.

More elegant solution

There has been a lot of focus in the last year or two on Domain Specific Languages (DSL's) and fluent interfaces.  At work we use TypeMock as our mocking framework and it provides a nice fluent interface for setting expectations on the mock objects.    We started thinking whether it was not possible to create a fluent interface to replace our ObjectMothers - a mini DSL that creates test cases for our domain model but is readable and flows.  It would have a lot of small creation/attachment methods as part of the DSL which could be chained together to create the test cases. After some work we ended up with Builder classes that works quite similar to the ExpressionBuilder classes Fowler writes about. smile_regular  To illustrate, here is a sample of using the DSL to create our pseudo HennieLouw Person:

public static Person CreateHennieLouwUsingDSL() { return PersonBuilder.StartRecording() .CreatePerson(Title.Mr, "Hennie", "Louw", new DateTime (1971, 8, 7), Gender.Male) .PrefersToSpeak(CorrespondenceLanguage.English) .WithCitizenshipDetailsOf(EthnicGroup.White, "7108070108083") .WithMaritalStatusOf(MaritalStatus.Married) .WithPrimaryJobOf("Software Developer") .WorksAt("Microsoft Ltd", "Pinelands") .EarnsMonthlyIncomeOf(10000M) .WithCellNumberOf("+27", "82", "2824827") .WithHomeNumberOf("+27", "21", "9814402") .WithPostalAddressOf("6 Sinai Close", "Cape Town", "8000") .WithHomeEmailOf("hennie.louw@gmail.com") .Finish(); }

Here are some code snippets of the PersonBuilder.

public sealed class PersonBuilder { private Person person; #region Constructors public PersonBuilder() { person = new Person(); } #endregion #region Factory Methods public static PersonBuilder StartRecording() { return new PersonBuilder(); } public Person Finish() { return this.Person; } #endregion #region Properties private Person Person { get { return person; } } #endregion

The StartRecording method provides a static factory method that kicks off the whole process of assembling the Person through invoking the constructor.  The Finish method returns the populated Person instance.  The rest of the PersonBuilder contains the various creation/attachment methods that form the mini-DSL.  These can be chained together to "record" the Person instance with the relevant test data.  Here is a code snippet of some of the DSL methods:

#region DSL Methods public PersonBuilder CreatePerson(Title title, string firstName, string surname, DateTime? dateOfBirth, Gender gender) { Person.Title = title; Person.FirstName = firstName; Person.Surname = surname; Person.DateOfBirth = dateOfBirth; Person.Gender = gender; return this; } public PersonBuilder WithCitizenshipDetailsOf(EthnicGroup ethnicGroup, string idNumber) { Person.EthnicGroup = ethnicGroup; Person.IdNumber = idNumber; return this; } public PersonBuilder WithMaritalStatusOf(MaritalStatus maritalStatus) { Person.MaritalStatus = maritalStatus; return this; } private PersonBuilder WithAddress(AddressType type, Language language, string line1, string suburb, string code) { Address address = Person.AddAddress(type, language); address.Line1 = line1; address.Line3 = suburb; address.Code = code; return this; } public PersonBuilder WithStreetAddressOf(string line1, string suburb, string code) { return WithAddress(AddressType.StreetAddress, Language.Unknown, line1, suburb, code); } public PersonBuilder WithPostalAddressOf(string line1, string suburb, string code) { return WithAddress(AddressType.PostalAddress, Language.Unknown, line1, suburb, code); }

Extending the DSL is quick as we just add the additional methods or overloads of existing methods.  We have different builder classes interacting with each other to create complete test cases for our domain.  The test cases are a lot more readable and overall we are a lot more happy with using this approach to the ObjectMother approach.

Filed under: ,

Comments

# Ernst Kuschke said on January 10, 2007 10:13 AM:

These are very useful for testing an existing object-model. When developing from scratch, though, do you follow this approach also?

I find that by developing in a TDD fashion, the need for these approaches are minimized completely.

# cjlotz said on January 10, 2007 07:37 PM:

Ernst

The approach just provides an alternative for creating test objects that are used in the unit tests so I can't see why you would not use it for doing true-blue TDD.   Initially the use of test objects may not be as apparent as the domain may be small, but as the domain grows and more tests are created you will find that you want to factor out all the duplicate code being created for setting up test objects for your domain unit tests.  This is just one way of removing that duplication and organizing your test objects.

Why do you feel it is not suited for doing TDD?

# Armand du Plessis said on January 11, 2007 07:41 AM:

I agree with cjlotz. I can imagine that the use of their mini-DSL in setting up mock objects, especially in a complex environment like theirs, would make reading the intent of the test code much clearer.

Usage of the fluent interface:

.CreatePerson(Title.Mr, "Hennie", "Louw", new DateTime (1971, 8, 7), Gender.Male)               .PrefersToSpeak(CorrespondenceLanguage.English)               .WithCitizenshipDetailsOf(EthnicGroup.White, "7108070108083")               .WithMaritalStatusOf(MaritalStatus.Married)                              .WithPrimaryJobOf("Software Developer")                  .WorksAt("Microsoft Ltd", "Pinelands")                  .EarnsMonthlyIncomeOf(10000M)               .WithCellNumberOf("+27", "82", "2824827")               .WithHomeNumberOf("+27", "21", "9814402")               .WithPostalAddressOf("6 Sinai Close", "Cape Town", "8000")               .WithHomeEmailOf("hennie.louw@gmail.com")

in a test fixture setup is certainly a lot cleaner than sifting through lines and lines of object setup code.

The moving on from that to the assignment of personas like "Hennie Louw" makes it even cleaner.

Apart from the fluent interface I really like the idea of using these pseudo identities. Is there seperate identities representing all the typical entities in the system?

I can imagine having a wallchart with these identities on and a new developer can see at a glance, oh here we've got Hennie Louw, your typical life policy client or whatever. Makes reading and identifying with the tests and code easier.

# cjlotz said on January 11, 2007 12:10 PM:

Armand

> Is there seperate identities representing all the typical entities in the system?

Yes there is.  We actually have quite a few test cases created by using the DSL that mirror the test cases that our testers are using when they test the system via the GUI.  We add these to a Registry of test cases giving each one an intent revealing name like ProductXYZTestCase1.  Everybody who wants to write tests for ProductXYZ knows that TestCase 1 has a fixed structure e.g.:

Recurring Monthly Premium of R2000

2 Life insureds HennieLouw and CarelLotz

HennieLouw has a death benefit of R1 000 000

CarelLotz has waiver of premium and accidental death of R5 000 000 etc.

The developer knows whether the test case provides him/her with the right test objects for writing the his/her tests.  As mentioned, the application is not a simple Order, OrderLines domain as we model complex life insurance products with benefit structures etc.  There are hundreds of business rules that depend on hundreds of combinations of values within the domain objects.  Having these well-known test cases available in a fluent interface makes writing and maintaining the tests a lot easier.

# trumpi said on January 21, 2007 04:49 PM:

These expression builders are really good -- I think I will give them a try.

With the builder object, what are your thoughts on using implicit cast operators to get the instance being built. For example, using an implicit operator to get from the fluent PersonBuilder object to the Person object that it is building?

# cjlotz said on January 22, 2007 08:07 AM:

Trumpi

I general I don't like implicit cast operators - http://pluralsight.com/blogs/fritz/archive/2005/12/12/17423.aspx echoes my sentiments on the topic.  IMO I think the explicit Finish method provides a clearer solution.

# Colin Jack said on July 30, 2007 11:13 AM:

Good post, it raised a few questions though:

1) When moving to this approach did you have to change the tests at all? I see you are returning a real Person so I'm guessing the tests did not need to change.

2) Where do you put methods like CreateHennieLouwUsingDSL?

3) How do state transitions fit in, for example if Hennie Louw needed to be a Person that was in a state of active (as in the state pattern). Do you just add a call to the appropriate state change method within CreateHennieLouwUsingDSL?

4) How does the DSL make the tests easier to refactor?

Ta,

Colin

# cjlotz said on August 12, 2007 10:33 PM:

Colin

Sorry for the delayed response - somehow didn't get notified about your comment.

> When moving to this approach did you have to change the tests at all?

No changes were required

> Where do you put methods like CreateHennieLouwUsingDSL

We created a PersonTestCase class to wrap a PersonBuilder or an already constructed Person instance.  We then created a Registry that owns the known PersonTestCase instances, i.e. PersonTestCaseRegistry.HennieLouw would return a PersonTestCase populated with Hennie Louw's details.

> How do state transitions fit in

We added the ability to transition between states to the PersonTestCase wrapper class, i.e. you could say return me a test case for a Person in a specific state.

> How does the DSL make the tests easier to refactor

It forces me to break up my test construction logic in small, cohesive parts that are identifiable by a method with a name that matches the business terms.  

One of the problems we had with the ObjectMother approach was the methods did too many things. When we wanted to add additonal logic, we found that we broke a lot of tests that were dependant on the same logic for a different reason. I know we could have solved the problem by breaking these up into smaller chunks, but the DSL made this more apparent as it forced us into a design were we created a fluent interface consisting out of a lot of small test construction methods.

I've been wanting to do a follow up post to cover some topics I mentioned like different states and test case registry, so perhaps the time has arrived :-)

Thanks

Carel

# Colin Jack said on August 13, 2007 09:14 PM:

Interesting stuff, I'll certainly look out for your posts on the topic and thanks for taking the time to reply.

# cjlotz said on November 19, 2007 06:50 AM:

Also see blog.eleutian.com/.../FluentFixtures.aspx for a another good example of doing something similar.

Leave a Comment

(required) 
(required) 
(optional)
(required) 

Enter the numbers above: