Monday, May 18, 2009

Spike: Using T4 to Generate Unit Tests From a Specification

Inspired by Rob Conery's recent video on Behavior-Driven-Design, I decided to do a quick 'spike' to try out the concepts.  I like MSpec, but still find the syntax a little scary.  It definitely makes sense more after Rob's video, but I'd like to see something even more approachable and discoverable.  I got to thinking - what if this approach has it backwards?  MSpec, etc. generate readable specs from tests, but maybe a better approach would be to generate tests from readable specs?  You could then generate even more readable specs if you needed. 

Continuing with Rob's examples, I wrote the spec out first, to a file called Accounts.spec:

Transferring between from account and to account
*Should debit the from account by the amount transferred
*Should credit the to account by the amount transferred

Now, what is the best way to go from this to some useful code?  T4 code-gen of course!  After brushing up on my T4, I came up with this, which I saved as a .tt file:

<#@ template language="C#" hostspecific="True" #> 
<#@import namespace="System.Collections.Generic" #>
<#@import namespace="System.IO" #>
<#
DirectoryInfo currentDir = new FileInfo(Host.TemplateFile).Directory;
FileInfo[] specFiles = currentDir.GetFiles("*.spec");
 
#>
 
<#foreach(FileInfo spec in specFiles){
StreamReader reader = spec.OpenText();
bool eof = false;
string currentClassName = "";
bool endOfSpec = false;
#>
using System;
using NUnit.Framework;
 
//<#=spec.Name#> Specifications
<#do{
    string line = reader.ReadLine();
    eof = reader.EndOfStream;    
    if(!line.StartsWith("*") && line != String.Empty && line != Environment.NewLine){    
        currentClassName = line.Replace(" ","_");
#>
[TestFixture]
public partial class <#=currentClassName#>{
    
    [SetUp]
    public void Setup()
    {
        Setup_<#=currentClassName#>();    
    }
    
    partial void Setup_<#=currentClassName#>();    
        
<#        do{
            line = reader.ReadLine();
            endOfSpec = (line == String.Empty ||  line == Environment.NewLine || reader.EndOfStream);
            if (!endOfSpec){
#>
    [Test]
    public void <#=line.Replace(" ","_").Replace("*","").Replace("-","")#>()
    {
        Assert_<#=line.Replace(" ","_").Replace("*","").Replace("-","")#>();
    }
        
    partial void Assert_<#=line.Replace(" ","_").Replace("*","").Replace("-","")#>();
    
<#            
            }        
        }while(!endOfSpec); #>
}
 
<#    
    }
 }while(!eof); #>
 
<# } #>

This code works by looping through every .spec file in the current directory and generating an NUnit partial class with partial methods for each condition in the specification.  This T4 could have just of easily generated MSpec or any other type of testing code.  In fact, I'm pretty sure it could generate better NUnit code.  As it stands now, though, this is what it generates for the above spec:

[TestFixture]
public partial class Transferring_between_from_account_and_to_account{
    
    [SetUp]
    public void Setup()
    {
        Setup_Transferring_between_from_account_and_to_account();    
    }
    
    partial void Setup_Transferring_between_from_account_and_to_account();    
        
    [Test]
    public void Should_debit_the_from_account_by_the_amount_transferred()
    {
        Assert_Should_debit_the_from_account_by_the_amount_transferred();
    }
        
    partial void Assert_Should_debit_the_from_account_by_the_amount_transferred();
    
    [Test]
    public void Should_credit_the_to_account_by_the_amount_transferred()
    {
        Assert_Should_credit_the_to_account_by_the_amount_transferred();
    }
        
    partial void Assert_Should_credit_the_to_account_by_the_amount_transferred();
    
}

The idea is that the tester has only to 'fill in the blanks' to build the test:

partial class Transferring_between_from_account_and_to_account
{
    private Account toAccount;
    private Account fromAccount;
 
    partial void Setup_Transferring_between_from_account_and_to_account()
    {
        this.toAccount = new Account();
        toAccount.Balance = 50M;
        fromAccount = new Account();
        fromAccount.Balance = 50M;
 
        this.fromAccount.TransferTo(this.toAccount, 20);
    }
 
    partial void Assert_Should_credit_the_to_account_by_the_amount_transferred()
    {
        Assert.AreEqual(this.toAccount.Balance, 70);
    }
 
    partial void Assert_Should_debit_the_from_account_by_the_amount_transferred()
    {
        Assert.AreEqual(this.fromAccount.Balance, 30);
    }
   
}

As with any test, ReSharper lets you right click-and-run the specification and outputs a fairly readable approximation with pass-fail for each bullet point.

I'm not ready to push this 'Spike' into production process, but I think it's interesting enough to blog about.  I think with a little tweaking of the code gen, this could be pretty useful stuff.  I'm not stuck on the testing framework, but ideally, I'd like to stick with a very simple, 'traditional' class where the test writers 'fill in the blanks'.  I'd like it to have intellisense to help discover the methods to implement or override, and ideally throw a 'Not Implemented Exception' for any feature not yet implemented.  And finally, I want the nice HTML report MSpec generates.  So, we'll see where this develops.  Let me know if you have any suggestions or questions on the code!

3 comments:

Alex Oliveira said...

Really nice idea! Great job Daniel!
A little suggestion:
How about to put some TODO markers in order to easy the "fill in the blanks" process"?

Daniel Root said...

Thanks- I have actually reworked the generated code some since this post, and add some //TODOs. Look for a new post on this soon.

cvillalobosm said...
This comment has been removed by the author.