Test Driven Development with DSL Tools
Saturday, November 17, 2007 – 11:08 PMWhen we were writing Service Factory: Modeling Edition we tried really hard to do some Test Driven Development (TDD) or at the very least write some unit tests around our code. In some cases the approaches we took led to some shortcomings or the code coverage wasn’t as high as I’d have liked to see. Recently I’ve been working on another DSL and have started to come up with some guidelines I use to make DSL development more amendable to TDD:
Externalize as much code as possible out of the Dsl and DslPackage projects. This forces you to be much clearer about your dependencies. Because the DSLs make extensive use of partial classes and the double derived pattern it’s very easy to write code that’s highly coupled to the internals of your DSL model.
Where possible implement a Model-View-Presenter (MVP) pattern. I’ll be blogging about this some more when I’ve completely figured it all out. Currently it’s a work in progress in the DSL I’m developing. I’m trying very hard to separate code and make everything more testable.
During development deploy your assemblies to Visual Studio’s PublicAssemblies folder. This allows you to run code coverage really easily, which usually isn’t possible with GAC’ed assemblies. I like to run code coverage periodically just to make sure my tests didn’t miss anything significant. Do NOT deploy your final application this way! Installing in PublicAssemblies is a bad idea, it gives you no side-by-side versioning between releases for one thing. Your DSL assemblies are placed in the GAC and the DSL Team recommend you GAC any additional dependent assemblies.
The DSL API is big and quite complex. You may find yourself writing custom code for your DSL inside the partial classes for your model elements and shapes. I found that this is the best way to get started in some cases but the moment I had something working I tried to rewrite the functionality within an external decoupled class that is called from the partial class.
Here’s a typical test written to test my TargetElementValidator class. The [Fact] attribute is the xUnit.net test decoration:
[Fact] public void TargetWithInvalidDependsOnTargetsStringFailsValidation() { // Arrange... Store store = new Store( new MockServiceProvider(), typeof(CoreDesignSurfaceDomainModel), typeof(VisualBuildDesignerDomainModel)); using (Transaction transaction = store.TransactionManager.BeginTransaction()) { TargetElement t = CreateBasicModel(store, "TargetA"); t.DependsOnTargetsString = "NoTarget"; ValidationContext context = new ValidationContext(ValidationCategories.Load, t); TargetElementValidator target = new TargetElementValidator(context); // Act... target.Validate(t); // Assert... Assert.Equal<int>(1, context.CurrentViolations.Count); Assert.Equal<string>("V0004", context.CurrentViolations[0].Code); transaction.Rollback(); } }
Note how the test still uses the arrange, act, assert pattern but with some additional complexity.
Most of these additions are around the arrange step. Firstly any modifications to the store must be wrapped in a transaction. It’s possible to create a DSL Store object, you just have to provide a mock implementation of IServiceProvider which never actually gets called for any services. Secondly there’s quite a lot of work required to setup a simple model, about a dozen lines of code (not show here as it’ll be DSL model specific). I’ve refactored that into CreateBasicModel and call it in each test, not in a setup method or a base class so I avoid the inherited or hidden test anti-pattern keeping my test is reasonably clear. CreateBasicModel creates the simplest useful model possible and returns the model element I’m going to be testing. My test tweaks it for it’s individual purposes. Lastly I create the target for my test, a TargetElementValidator object.
So that’s arrange step. Act is trivial. I validate my TargetElement. Finally I assert that the Validate method correctly updated the ValidationContext object and added a validation violation to it. I also rollback the transaction I created just to tidy up the store.
This isn’t an ideal test. It’s getting on the longish side with too much setup – a sure smell for coupled classes. However the DSLs don’t entirely lend themselves to a really decoupled approach. I’m still investigating this.
Sorry, comments for this entry are closed at this time.