Data-Driven Code Generation of Unit Tests Part 4: C#, MSBuild, T4, MS Unit Test
Data-Driven Code Generation of Unit Tests
Published: 2017-07-05
Data-Driven Code Generation of Unit Tests Part 4: C#, MSBuild, T4, MS Unit Test

This post is part 4/5 of my Data-Driven Code Generation of Unit Tests series.

This blog post explains how I used C#, MSBuild, T4 Text Templates, and the Microsoft Unit Test Framework for Managed Code to perform data-driven code generation of unit tests for a financial performance analytics library. If you haven’t read it already, I recommend starting with Part 1: Background.

As mentioned in Part 2: C++, CMake, Jinja2, Boost, all performance analytics metadata is stored in a single file called metadata.csv. This file drives all code generation and is what helps ensure inter-platform consistency.

I must admit, I was pleasantly surprised when I discovered that Microsoft provides a template-based code generation engine (T4) out-of-the-box with Visual Studio. Because of this, supporting code generation within a Visual Studio project is as easy as creating a file within your project with the extension .tt. The key part to making it work is that the file must be marked as using the TextTemplatingFileGenerator Custom Tool, which Visual Studio does for you automatically.

I decided the easiest thing for me to do was to create a single .tt file that parses metadata.csv and generates a single C# file with all unit tests for all calculations. I also found it rather convenient to include utility functions within the template itself using the `` stanza.

The template file I created looked something like:

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
<#@ import namespace="System.Collections.Generic" #>
<#@ import namespace="System.IO" #>
<#@ import namespace="System.Linq" #>

using ...

<#
    string fileName = this.Host.ResolvePath("..\\..\\..\\metadata.csv");
    var lines = File.ReadLines(fileName);
    var header = lines.First().Split(",');
    // Notice how this for loop will run once per calculation in metdata.csv
    foreach (var line in lines.Skip(1)) {
        // Create a dictionary with the calculation's attributes for
        // use by the code generator
        var arr = line.Split(",');
        Dictionary<string, string> dict = new Dictionary<string, string>();
        for (int i = 0; i < header.Length; ++i) {
            dict[header[i]] = arr[i];
        }
#>

namespace PerformanceAnalyticsUnitTest
{
    [ExcludeFromCodeCoverage]
    [TestClass]
    public class <#= UnderscoreToPascalCase(dict["function_name"]) #>Test {
        [TestMethod]
        public void Test<#= UnderscoreToPascalCase(dict["function_name"]) #>ArrayUnannualized() {
            ...
        }

        ...
    }
}

<#
    }
#>

<#+
    public string UnderscoreToPascalCase(string str) {
        ...
    }
#>

I also made sure that the generated files were excluded from source control by adding them to the .gitignore file – as a reminder, generated source is output, not source code, and should not be checked in to source control.

I ran into a few minor annoyances, such as the source code sometimes not being generated at the proper time in the build cycle, but that was about it. Integrating code generation into a Visual Studio project is about as easy as it gets!