Peter Goodman bio photo

Peter Goodman

A software engineer and leader living in Auckland building products and teams. Originally from Derry, Ireland.

Twitter Google+ LinkedIn Github

I’ve been meaning to post this entry for a while now so while I wait for a 2010 beta install to complete….

For a long time I thought it wasn’t possible to unit test DSL Tools projects easily. Due to the nature of the environment it is sometimes difficult to see how unit testing is possible. The model elements are put in a partition in a store and all the partial classes, rules etc are very tightly coupled.

It wasn’t until I wanted to write a use-case oriented management layer on top of our multiple DSLs that I first thought of how it was possible. I wanted to a find way to test that the abstracted co-ordination methods on the management layer would create the expected model elements and what I got was an accidental pattern for testing DSL tools.

The main trick lies in creating a model context that will hold your store, model root and handle transactions.

The ModelingTestContext class:

public class ModelingTestContext : IDisposable {

    private Transaction tx;

    private Store store;

    public ModelingTestContext() {

        store = new Store();

 

        Type domainModelType = typeof(MyCompany.DomainModelDslDomainModel);

 

        // Create a new store to deserialize the instance to.

 

        Type[] metaTypes = new Type[]

                               {

                                   typeof (CoreDesignSurfaceDomainModel),

                                   domainModelType

                               };

        // Load these types into the store, so that it knows about them for deserialization

        store.LoadDomainModels(metaTypes);

 

        tx = store.TransactionManager.BeginTransaction("Domain Modeling Test Context");

        domainModelRoot = new MyModelRoot(store);

    }

 

 

    public MyModelRoot DomainModelRoot {

        get { return domainModelRoot; }

    }

 

    public Store Store {

        get { return store; }

    }

 

    public void CommitChanges() {

        tx.Commit();

        tx = store.TransactionManager.BeginTransaction("Domain Modeling Test Context");

    }

 

    public void Dispose() {

        if(tx != null && tx.IsActive) {

            tx.Rollback();

        }

    }

}

This class will create the model root inside the store and manage transactions for us. We next need to start writing a test.

[TestMethod]

public void CreateAssociationSingleMultiplicity() {

    using (ModelingTestContext domainContext = new ModelingTestContext()) {

        domainContext.DomainModelRoot.Namespace = "Aderant.Test.DomainFoo";

 

        DomainComponent clientDc = domainContext.CreateDomainComponent("Client");

 

        DomainComponent addressDc = domainContext.CreateDomainComponent("Address");

 

        AssociationLink link = clientDc.AssociateWith(addressDc, Multiplicity.One);

 

 

        domainContext.CommitChanges();

        Assert.AreEqual(addressDc.Name, link.TargetRoleName);

 

    }

}

We create a context inside a using block so that we can clean up the transaction when the test completes. In this test I have added extension methods to the domain context and the DomainComponent class to support the common scenarios I used in testing like CreateDomainComponent and AssociationWith, allowing me to encapsulate common behavior. These extension methods can then be moved to become behavior of the model or the management API. Therefore over time you provide better API functionality which is tested.

Now some people would call this integration testing instead of unit testing…..fair enough. Also some would say that the extension methods and the test context for that matter are making the tests brittle as DRY does not apply to unit testing, I would disagree as otherwise these tests would become unmanageable.

I hope you find this useful.

Pete