Sunday, July 20, 2008

Unit Testing T4 Text Templates

Text Template Transformation Toolkit, aka T4, is a template-based code generation engine included in Visual Studio 2008 and available for Visual Studio 2005 through the Domain Specific Language (DSL) Tools and the Guidance Automation Extensions (GAX) and Guidance Automation Toolkit (GAT). Text template transformation works with a very simple formula:

TextTemplateFile + Metadata = OutputFile

A text template is a file that contains a mixture of text blocks and control logic. Metadata is provided to the text template as properties defined at its header section. When you transform a text template, the control logic combines the text blocks with the data in a model to produce an output file. Text templates can be used to create any kind of text files, such as source code files and HTML reports. This is similar to some commercial tools such as CodeSmith.

This post from Oleg Sych provides more details about T4 and it includes many links to other resources.

How to Unit Test T4 Templates?

One approach to unit test text template files is to use the Visual Studio Text Templating engine to supply the required data and run the transformation of the template. The Visual Studio Text Templating engine will allow you to see all the errors you might get when processing it.

In order to do this, you will need to install GAX/GAT and add the following references to your project:
  • Microsoft.VisualStudio.TextTemplating
  • Microsoft.Practices.RecipeFramework.VisualStudio.Library
The following sections will describe a sample text template file and a NUnit test for it using the Visual Studio Text Template engine.

The Text Template

The following code represents the template file that we will be testing:

<#@ Template Language="C#" #>
<#@ Assembly Name="System.dll" #>
<#@ Assembly name="TextTemplateTesting.dll" #>
<#@ Import Namespace="System" #>
<#@ Import Namespace="TextTemplateTesting.Data" #>
<#@ Property Processor="PropertyProcessor" Name="EntityName"#>
<#@ Property Processor="PropertyProcessor" Name="Fields" #>
using System;
using System.Collections.Generic;
using System.Text;

namespace BusinessEntities
{
public partial class <#= EntityName #>
{
public <#= EntityName #>()
{
}

<# foreach (Field field in Fields) { #>
private <#= field.Type.Name #> _<#= field.Name #>
public <#= field.Type.Name #> <#= field.Name #>
{
get { return _<#= field.Name #>; }
set { _<#= field.Name #> = value; }
}
<# } #>
}
}

This text template generates a class representing a business entity. Remember that our simple formula is TextTemplateFile + Metadata = OutputFile. The data that is necessary to generate the code is defined by the two processor properties in our template above, and they are:

<#@ Property Processor="PropertyProcessor" Name="EntityName"#>
<#@ Property Processor="PropertyProcessor" Name="Fields" #>

The EntityName processor property is a string that will represent the name of the class in the generated code. The Fields processor property is an array of Field objects representing the properties of the class in the generated code. The Field class is not shown here, but it has two properties: the name of the field and its type. You can download the entire source code from the link at the end of this article.

The Text Template NUnit Test

I am using NUnit to test the text template, but you can use any other compatible test framework. The testing code will be:

using System;
using System.CodeDom.Compiler;
using System.Collections.Generic;
using System.IO;
using System.Text;
using Microsoft.Practices.RecipeFramework.VisualStudio.Library.Templates;
using Microsoft.VisualStudio.TextTemplating;
using NUnit.Framework;
using TextTemplateTesting.Data;

namespace TextTemplateTesting.Tests
{
[TestFixture]
public class TextTemplateFixture
{
[Test]
public void TestTextTemplate()
{
// Determine the root dir of the template files.
// Since the template files are located in
// SolutionDir\Templates and the unit tests
// run inside SolutionDir\bin\debug,
// the root dir is ".\..\..\Templates"
string rootDir = Path.Combine(Directory.GetCurrentDirectory(),
@"..\..\Templates");

// Our template file expects data to be passed.
// The following dictionary will contain
// the properties to be passed to the template file.
IDictionary<string, PropertyData> arguments =
new Dictionary<string, PropertyData>();

// The first property is a string with the business entity name.
arguments["EntityName"] = new PropertyData("Customer", typeof(string));

// The second property is an array of Field objects.
Field[] fields = { new Field("CustomerName", typeof(string)),
new Field("CustomerId", typeof(int)) };
arguments["Fields"] = new PropertyData(fields, typeof(Field[]));

// Create an instance of the template host
TemplateHost host = new TemplateHost(rootDir, arguments);

// Set the template file
host.TemplateFile = Path.Combine(rootDir, "BusinessEntity.tt");
Assert.IsTrue(File.Exists(host.TemplateFile),
"Cannot find " + host.TemplateFile);

// Create an instance of the template engine.
Engine engine = new Engine();

// Transform the text template.
engine.ProcessTemplate(File.ReadAllText(host.TemplateFile), host);

// Check if there are errors.
if (host.Errors.HasErrors)
{
// Build a string with all errors
StringBuilder sb = new StringBuilder();
foreach (CompilerError error in host.Errors)
{
if (!error.IsWarning)
sb.AppendLine(error.ErrorText);
}
Assert.IsFalse(host.Errors.HasErrors,
"There are compiling errors:\n\n" + sb);
}
}
}
}

The transformation is very simple. You need basically two main objects: the host and the engine.

The first main object, the host, will contain information such as your text template file name, the data used for the transformation, and the root path where the transformation will happen. Note that text templates can also include other text templates using relative path, and they are relative to this root path defined on the host constructor.

The data is passed to the host through the arguments variable. This variable is a dictionary type where the keys are the processor property name defined in the template file, and the value is a PropertyData object specifying the value and type of the processor property.

The engine is the second main object which provides the ProcessTemplate method. This method takes a string representing the text template file, and a host object as parameters. The Errors collection of the host object will contain all errors and warnings from the transformation. The test code above only fails if there are errors, it ignores all warnings.

I hope this sample provides an useful way for unit testing your t4 files. Testing manually text templates is a time consuming activity which can be impossible to accomplish depending on the total number of templates you are using. By using automated tests you can ensure that changes in the code are not breaking your templates, increasing the confidence of your developers in their code and the confidence of your customers in your product.

Enjoy it!

Sample Code Download

You will need Visual Studio 2005 (or later), GAX/GAT and NUnit installed in order to run this sample code.

1 comment:

Shou Takenaka 竹中翔 said...

Great!!
Thank you very much for your helpful post.