Click here to Skip to main content
15,896,118 members
Articles / Programming Languages / C#

Using .NET’s System.Reflection to Aid Unit Test Creation – Part 2

Rate me:
Please Sign up or sign in to vote.
4.00/5 (1 vote)
14 Jul 2015CPOL4 min read 8.2K   3   3
Using .NET’s System.Reflection to aid Unit Test Creation – Part 2

In my last post, I showed a way to generate unit tests for existing code where a pattern exists using .NET’s System.Reflection library. The post showed an executable that would take the paths to a DLL and an output file as input parameters where this executable would then tear through the assembly, looking for string properties for which I wanted tests. The EXE would generate stub code to ensure that I wouldn’t miss any of the string properties.

I was discussing this with a colleague who suggested that I consider generating a T4 text template within Visual Studio to do this same work. I had never used the templates before, but have found them to be amazingly simple, yet very useful tools to have in the arsenal. I was able to take the code from the EXE that I described in the last post, incorporate it into a template, and have it generate the same test code in about 5 minutes.

The benefit to using T4 text templates is that they are meta-code that reside within your Visual Studio project. The cool thing is that the meta code regenerates the output file from the template during builds and after edits to the template. Because of this close relationship with code in their project, these templates can be used to auto-generate output files that respond to changes in the code. This can be particularly handy if you are creating schema files, config files, tests, or other forms of output that need to be kept fresh as your code changes.

I took the project that I used in the last post and added the T4 text template. To do this, I simply added a new Text Template:

AddNewTextTemplate

After adding the template, you can see the template in the Solution Explorer window. You can also see the generated code file as a child of the text template, as shown here:

TemplateInSolutionExplorer

When you first generate a text template, it puts header information in the .tt file for you that you have to edit. You’ll notice that code that is to be evaluated is placed inside of <# #> bracketing. The text within these delimiters will not be shown in the output file, but will contain the code needed to output something useful to the file.

XML
<#@ template debug="false" hostspecific="false" language="C#" #>
<#@ assembly name="System.Core" #>
<#@ import namespace="System.Linq" #>
<#@ import namespace="System.Text" #>
<#@ import namespace="System.Collections.Generic" #>
<#@ output extension=".txt" #>

You can see that the default output extension is .txt. I changed that to .cs. You can also see that the template is generated with assembly and import directives. Assembly is like adding a reference to a project and import is analogous to a using directive in a C# file. Since I already knew my dependencies from my prior project, I modified this information to look like this:

XML
<#@ template debug="false" hostspecific="true" language="C#" #>
<#@ output extension=".cs" #>
<#@ assembly name="System"#>
<#@ assembly name="System.Xml"#>
<#@ assembly name="System.Xml.Linq"#>
<#@ import namespace="System" #>
<#@ import namespace="System.Xml.Linq"#>
<#@ import namespace="System.Reflection" #>

This block is code that the template will use when run. It is the list of references (assembly tag) and usings (import tag) that the code I will add later will use.

Next, I will add the using statement block that I want my output file to have. This text will not be inside the <# #> delimiters because I actually want to see this code in my output file. Note that this text is not the usings that the template will need to run. Since it is outside of the delimiters, it is seen as simple text to be output:

C#
using System;
using System.Linq;
using Microsoft.VisualStudio.TestTools.UnitTesting;

Next, since I was writing a unit test file generator, the namespace and class declaration needed to be added. These were in funky functions in executable in my prior post. These functions added “header” and “footer” information, for lack of better terminology. This was awkward, but here it is quite elegant because I am actually writing this one-time-output text as it will be seen in the output.

C#
namespace MyProject.Tests
{
    [TestClass]
    public class MyTests
    {
    }
}

After this, I extracted the code from the executable I built in the prior post and put it into the template. You’ll notice the <#= stuff #> where I wanted to emit text generated from code into the output file. This is the final text template file:

C#
<#@ template debug="false" hostspecific="true" language="C#" #>
<#@ output extension=".cs" #>
<#@ assembly name="System"#>
<#@ assembly name="System.Xml"#>
<#@ assembly name="System.Xml.Linq"#>
<#@ import namespace="System" #>
<#@ import namespace="System.Xml.Linq"#>
<#@ import namespace="System.Reflection" #>
using System;
using System.Linq;
using Microsoft.VisualStudio.TestTools.UnitTesting;

namespace MyProject.Tests
{
	[TestClass]
	public class MyTests
	{
		<#
		var a = Assembly.LoadFrom(@"c:\temp\ProjectToTest\ProjectToTest.dll");
        foreach (var type in a.GetTypes())
        {
            // get all public static properties of MyClass type
            var propertyInfos = type.GetProperties();

            // sort properties by name
            Array.Sort(propertyInfos,
                delegate(PropertyInfo propertyInfo1, PropertyInfo propertyInfo2)
                {
                    return propertyInfo1.Name.CompareTo(propertyInfo2.Name);
                });

            // write property names
            foreach (var propertyInfo in propertyInfos)
            {
                if (propertyInfo.PropertyType == typeof(string))
				{

		#>
[TestMethod]
		public void MyTests_<#=  type.Name.Replace("`", "") #>_<#= propertyInfo.Name #>()
		{
			// Put in code here for your test
		}
		<#
				}
            }
        } 
		#>
	}
}

If you’ll notice, the “[TestMethod]” indention looks a little nutty in the text template file. I found that I had to play a little to get the indention right. Also, you can see that I have hard-coded the path to the DLL that I wanted to reflect. This could be made better via a project variable somehow.

After the final updates to the template file, Visual Studio automatically generated this output when I saved the template:

C#
using System;
using System.Linq;
using Microsoft.VisualStudio.TestTools.UnitTesting;

namespace MyProject.Tests
{
	[TestClass]
	public class MyTests
	{
		[TestMethod]
		public void MyTests_Class1_String1()
		{
			// Put in code here for your test
		}
		[TestMethod]
		public void MyTests_Class1_String2()
		{
			// Put in code here for your test
		}
		[TestMethod]
		public void MyTests_Class1_String3()
		{
			// Put in code here for your test
		}
		[TestMethod]
		public void MyTests_Class1_String4()
		{
			// Put in code here for your test
		}
		[TestMethod]
		public void MyTests_Class1_String5()
		{
			// Put in code here for your test
		}
		[TestMethod]
		public void MyTests_Class1_String6()
		{
			// Put in code here for your test
		}
		[TestMethod]
		public void MyTests_Class2_String1()
		{
			// Put in code here for your test
		}
		[TestMethod]
		public void MyTests_Class2_String2()
		{
			// Put in code here for your test
		}
		[TestMethod]
		public void MyTests_Class2_String3()
		{
			// Put in code here for your test
		}
		[TestMethod]
		public void MyTests_Class2_String4()
		{
			// Put in code here for your test
		}
		[TestMethod]
		public void MyTests_Class2_String5()
		{
			// Put in code here for your test
		}
		[TestMethod]
		public void MyTests_Class2_String6()
		{
			// Put in code here for your test
		}
		[TestMethod]
		public void MyTests_Class3_String1()
		{
			// Put in code here for your test
		}
		[TestMethod]
		public void MyTests_Class3_String2()
		{
			// Put in code here for your test
		}
		[TestMethod]
		public void MyTests_Class3_String3()
		{
			// Put in code here for your test
		}
		[TestMethod]
		public void MyTests_Class3_String4()
		{
			// Put in code here for your test
		}
		[TestMethod]
		public void MyTests_Class3_String5()
		{
			// Put in code here for your test
		}
		[TestMethod]
		public void MyTests_Class3_String6()
		{
			// Put in code here for your test
		}
			}
}

If I had some severely repetitious checks to make, I could make this file regenerate and change with every change to my code. This is interesting for unit testing, but really has cool applications in other areas, like schema generation, documentation, etc.

Much thanks to my colleague for pointing T4 text templates out to me! This is another cool tool to keep handy.

License

This article, along with any associated source code and files, is licensed under The Code Project Open License (CPOL)


Written By
Technical Lead
United States United States
I'm a learner/coder/leader who is curious about how technologies and people work together to solve interesting problems. I have a passion for software and doing what I can to improve the lives of the people who create and use it.

Comments and Discussions

 
SuggestionAnother way of doing that! Pin
WuffProjects15-Jul-15 7:34
professionalWuffProjects15-Jul-15 7:34 
GeneralRe: Another way of doing that! Pin
Keith Holloway15-Jul-15 9:54
professionalKeith Holloway15-Jul-15 9:54 
GeneralRe: Another way of doing that! Pin
WuffProjects15-Jul-15 10:27
professionalWuffProjects15-Jul-15 10:27 
Thank you! I'm very happy you like it and I'm looking forward to read your next article!

General General    News News    Suggestion Suggestion    Question Question    Bug Bug    Answer Answer    Joke Joke    Praise Praise    Rant Rant    Admin Admin   

Use Ctrl+Left/Right to switch messages, Ctrl+Up/Down to switch threads, Ctrl+Shift+Left/Right to switch pages.