Update (9/16/2013): Example source code is now attached, including source for parts 1 and 2.

In the previous post, I proposed a simple example:

You work for Company A, and they want you to write a simple web portal for them. You learn that there is a process in place already that spits out about 30 images as email attachments, and they would much rather see these displayed on a web site. The images will have to be categorized into main, sub, and chart type

In that post, I outlined how one might develop a library for such a site without TDD. Erase that code from your memory. DELETE.

In this post, I will be showing how test driven development can vastly improve the flexibility of the code you write, using the same example. I will be using a 3rd party tool called JustMock from Telerik. I highly recommend at least finding some mocking tool, as this will greatly enhance the TDD process. You can read a previous post for more on the subject of dependency injection, IoC, and mocking. It is through these principles and patterns that TDD becomes a really powerful method for class design. Let’s get started!

Sometimes staring at a blank test class can be rather daunting.

dont-panic-circle

Calm down!

DON’T PANIC!

LET GO!

*SLAP*

Feel better? Me too… I got to slap you!

In all seriousness, think of the test area as your white board. Your API isn’t written yet, but you get to use it already!

What is the most basic thing you need to do? Load charts from somewhere right? What holds things? A container? Perhaps… but, let’s call it a Repository. We know it needs to return an enumerable collection of Chart objects. We also know that something is going to depend on this “Repository” object to load its charts from. Let’s say we haven’t settled on the architecture yet (MVC, WebForms, HTML5 with a WebAPI service). We need to test that SOMETHING can load the charts using a repository just to get started, so let’s call it ChartLoader. I think we have enough to write our first unit test. I typically start by renaming the class and first test method, and inserting my standard AAA comments:

	[TestClass]
    public class when_loading_charts
    {
        [TestMethod]
        public void chart_loader_returns_charts_from_repository()
        {
            // Arrange
            // Act
            // Assert
        }
    }

TDD has a cadence to it. You will:

  1. Write a test.
  2. The you run all tests.
    1. New test fails because the requirement isn’t met yet
  3. Do the simplest thing to make all tests pass again
  4. Refactor
    1. Only after everything passes do you refactor. It may be tempting, but don’t do it earlier! Get a good baseline first.
  5. Repeat for the next requirement

The first test

	[TestMethod]
	public void chart_loader_returns_charts_from_repository()
	{
		// Arrange
		var container = new MockingContainer<ChartLoader>();
		container.Arrange<IChartRepository>(r => r.Charts).Returns(new List<Chart>
		{
			new Chart
			{
				FileName = "file1.png",
				Category = new ChartCategory
				{
					Main = "main",
					Sub = "sub",
					Type = "type"
				}
			}
		});

		// Act
		ChartLoader loader = container.Instance;
		IEnumerable<Chart> result = loader.GetCharts();

		// Assert
		Assert.AreEqual(1, result.Count());
		Assert.AreEqual("file1.png", result.First().FileName);
		Assert.AreEqual("main", result.First().Category.Main);
		Assert.AreEqual("sub", result.First().Category.Sub);
		Assert.AreEqual("type", result.First().Category.Type);
	}

So, we have a test for this fictitious ChartLoader object, and we know it needs to load from a repository, so ChartLoader needs some abstract definition of a repository (IChartRepository), and we know it needs to get charts… IChartRepository.Charts is going to be an IEnumerable, but it’s undefined so far. The Arrange call is essentially setting up a return value from the chart repository, so that when ChartLoader hits it, it will get the list as defined in the .Returns() call. We’ll see that in the next code section. So, the next thing to do is call the method in the ChartLoader to get the charts, and then assert that everything the ChartLoader returned, matched exactly what the repository returned (and you know exactly what it’s going to return, because you mocked it!)

So now there is a test with a bunch of red lines, because nothing is real yet. This is why I love tools like ReSharper, which let me auto generate the classes very quickly. What gets generated:

    public interface IChartRepository
    {
        IEnumerable<Chart> Charts { get; }
    }

    public class ChartLoader
    {
        public IEnumerable<Chart> GetCharts()
        {
            throw new NotImplementedException();
        }
    }

    public class ChartCategory
    {
        public string Type { get; set; }
        public string Main { get; set; }
        public string Sub { get; set; }
    }

    public class Chart
    {
        public ChartCategory Category { get; set; }
        public string FileName { get; set; }
    }

Now, the test will fail, but at least everything compiles. This is where you go to step 4 in the process. Do the simplest thing to make all the tests pass. In this case it’s 1 test, but get in the habit of running all tests… find the keyboard shortcut for doing this and learn it! Optionally, if you’re really into it, you can get a tool that automatically runs your tests like NCrunch

Here, there is a certain pattern that IoC containers work with: Your class constructor should take parameters for all of its dependencies. Here is the implementation of ChartLoader I came up with to get the tests to pass:

    public class ChartLoader
    {
        private readonly IChartRepository _chartRepository;

        public ChartLoader(IChartRepository chartRepository)
        {
            _chartRepository = chartRepository;
        }

        public IEnumerable<Chart> GetCharts()
        {
            return _chartRepository.Charts;
        }
    }

Now typically, you will do whatever refactoring that is necessary, which includes moving classes into appropriate folders and projects, etc, because all of the generated code I’ve shown is at the bottom of the test file. That would be out of the scope of this post, though, and sometimes it’s ok to leave them inline while you’re still writing tests.

This might actually be a good time to create a fake IChartRepository implementation on the front end site and actually give the business something to look at!

Hah! Did you think TDD was slow? How much quicker did we just deliver something to business and get feedback? This is VERY early on, and it is invaluable, because business requirements change when the people using a system get to play with it. You can and should show them something as soon as possible.

Back to TDD. The second test:

We have an object that can get charts, but we still don’t have anything loading from disk. Let’s write another test (because in TDD we always write a test before a feature):
I typically start a new test class when I’m dealing with a new system under test, but for this example, I won’t bother.

        [TestMethod]
        public void chart_repository_returns_charts_from_directory()
        {
            // Arrange
            var container = new MockingContainer<DirectoryChartRepository>();
            container.Arrange<IDirectory>(d => d.GetFiles()).Returns(new List<string>
            {
                "file1.png",
                "file2.png"
            });

            // Act
            DirectoryChartRepository repository = container.Instance;
            IEnumerable<Chart> result = repository.Charts;

            // Assert
            Assert.AreEqual(2, result.Count());
            Assert.AreEqual("file1.png", result.ElementAt(0).FileName);
            Assert.AreEqual("file2.png", result.ElementAt(1).FileName);
        }

Here I just want to prove that charts can load from a directory. Now, what really makes this a special scenario (and why I chose this example) is that there is an inherent dependency on the file system in the requirement: Charts will be loaded off the hard drive. This is about class design and controlling inputs in order to test things properly, though. I don’t want a hard drive to have to spin up in order to run a unit test. Tests should execute in milliseconds and be self contained units. The arrange section of a unit test HAS TO contain all of the dependency mocking, or it is going to be a brittle test that requires too much setup. Furthermore, System.IO.Directory.GetFiles(path) is static and can’t be overridden, so it’s impossible to mock (without a major effort or something that can fiddle with system libraries at runtime).

So, now let’s do some more class generation.

    public interface IDirectory
    {
        IEnumerable<string> GetFiles();
    }

    public class DirectoryChartRepository : IChartRepository
    {
        public IEnumerable<Chart> Charts { get; }
    }

So now everything compiles, but the new test still fails, because the DirectoryChartRepository object doesn’t actually use IDirectory and isn’t building any chart objects from the directory. So we have to do more to get it to pass:

    public class DirectoryChartRepository : IChartRepository
    {
        private readonly IDirectory _directory;

        public DirectoryChartRepository(IDirectory directory)
        {
            _directory = directory;
        }

        public IEnumerable<Chart> Charts
        {
            get
            {
                // TODO: I'm going to need a way to inject the path
                return _directory.GetFiles().Select(f => new Chart
                {
                    FileName = f
                });
            }
        }
    }

Now both tests should pass, and we can move on. How do charts get categories? NEW TEST! Seeing the pattern now? (Good, because I’m going to go faster):

Going faster. The third test:

	[TestMethod]
	public void chart_repository_categorizes_charts_from_categorizer()
	{
		// Arrange
		var container = new MockingContainer<DirectoryChartRepository>();
		container.Arrange<IDirectory>(d => d.GetFiles()).Returns(new List<string>
		{
			"file1.png"
		});

		container.Arrange<IChartCategorizer>(c => c.GetChartCategory(Arg.IsAny<Chart>()))
			.Returns(new ChartCategory
		{
			Main = "main",
			Sub = "sub",
			Type = "type"
		});

		// Act
		DirectoryChartRepository repository = container.Instance;
		IEnumerable<Chart> result = repository.Charts;

		// Assert
		Assert.AreEqual("main", result.ElementAt(0).Category.Main);
		Assert.AreEqual("sub", result.ElementAt(0).Category.Sub);
		Assert.AreEqual("type", result.ElementAt(0).Category.Type);
	}

    public interface IChartCategorizer
    {
        ChartCategory GetChartCategory(Chart chart);
    }

Tests compile but fail again. Add a dependency on IChartCategorizer to the DirectoryChartRepository, and use it. New DirectoryChartRepository class:

    public class DirectoryChartRepository : IChartRepository
    {
        private readonly IDirectory _directory;
        private readonly IChartCategorizer _chartCategorizer;

        public DirectoryChartRepository(IDirectory directory,
            IChartCategorizer chartCategorizer)
        {
            _directory = directory;
            _chartCategorizer = chartCategorizer;
        }

        public IEnumerable<Chart> Charts
        {
            get
            {
                return _directory.GetFiles().Select(f =>
                {
                    var chart = new Chart
                    {
                        FileName = f
                    };
                    chart.Category = _chartCategorizer.GetChartCategory(chart);
                    return chart;
                });
            }
        }
    }

Tests pass. Things are really starting to come together at this point. We still want to load categories from that XML file perhaps, but who’s going to maintain the XML file? What if they want to be able to manage categories themselves? Things are really starting to get going now, and with this class design, to implement any categorization requirements, it’s simply an implementation of IChartCategorizer. XMLChartCategorizer or SQLChartCategorizer? Well for now let’s do DictionaryChartCategorizer since we’re going to be the only ones maintaining the categories for now.

Fourth test:

	[TestMethod]
	public void dictionary_categorizer_loads_categories_from_dictionary()
	{
		// Arrange
		var container = new MockingContainer<DictionaryChartCategorizer>();
		container.Arrange<IChartCategoryDictionaryProvider>(d => d.GetCategories()).Returns(new Dictionary<string, ChartCategory>
		{
			{
				"file1.png",
				new ChartCategory
				{
					Main = "main",
					Sub = "sub",
					Type = "type"
				}
			}
		});

		// Act
		var result = container.Instance.GetChartCategory(new Chart
		{
			FileName = "file1.png"
		});

		// Assert
		Assert.AreEqual("main", result.Main);
		Assert.AreEqual("sub", result.Sub);
		Assert.AreEqual("type", result.Type);
	}

    public interface IChartCategoryDictionaryProvider
    {
        Dictionary<string, ChartCategory> GetCategories();
    }

    public class DictionaryChartCategorizer : IChartCategorizer
    {
        public ChartCategory GetChartCategory(Chart chart)
        {
            throw new NotImplementedException();
        }
    }

Partway through writing the Arrange section, I realize that I need a way to control the dictionary of categories, and this is a good time to make a new IChartCategoryDictionaryProvider object, because I can implement that interface for XML, SQL, a Code Only version, or this mock version! We have a compiling test that fails due to exceptions. Let’s fix that:

    public class DictionaryChartCategorizer : IChartCategorizer
    {
        private readonly IChartCategoryDictionaryProvider _chartCategoryDictionaryProvider;

        public DictionaryChartCategorizer(IChartCategoryDictionaryProvider chartCategoryDictionaryProvider)
        {
            _chartCategoryDictionaryProvider = chartCategoryDictionaryProvider;
        }

        public ChartCategory GetChartCategory(Chart chart)
        {
            var categories = _chartCategoryDictionaryProvider.GetCategories();
            ChartCategory category;
            if (categories.TryGetValue(chart.FileName, out category))
            {
                return category;
            }
            return null;
        }
    }

With that new implementation of DictionaryChartCategorizer, everything passes again. So, we have the basic structure of our API down that satisfies all of the requirements, but STILL no call to System.IO.Directory.GetFiles() and no loading charts from XML? What gives? We’ve reached the point where we need real implementations in order to call this done, but we could easily skip that and write the whole front end website. As noted, we could have done that a long time ago as soon as we had the ChartLoader object.

Now let’s finish this thing out

The test for this is basic, because there’s not much to arrange or assert:

	[TestMethod]
	public void filesystem_directory_does_not_throw_exception()
	{
		// Arrange
		var directory = new FileSystemDirectory("C:\");

		// Act
		var result = directory.GetFiles();

		// Assert
		// Pass if no exceptions
	}

    public class FileSystemDirectory : IDirectory
    {
        private readonly string _path;

        public FileSystemDirectory(string path)
        {
            _path = path;
        }

        public IEnumerable<string> GetFiles()
        {
            return System.IO.Directory.GetFiles(_path);
        }
    }

You’ll notice I didn’t mock anything, because this class doesn’t have a mockable dependency. It goes directly to a static method to get files off the disk, and that’s exactly what we didn’t want polluting our classes making testing them hard. It has to be done at some point, though, and at least we have a single class to handle it and it’s tucked away neatly in an abstraction. As a side note, this opens up the possibility of having a non filesystem directory (store image byte arrays in SQL?), which is a pretty neat concept, and no code would have to change.

Almost done!

	[TestMethod]
	public void xml_categorizer_loads_categories_from_xdoc()
	{
		// Arrange
		var document =
			new XDocument(new XElement("charts",
				new XElement("chart", new XAttribute("maincategory", "main"), new XAttribute("subcategory", "sub"),
					new XAttribute("type", "type"), new XAttribute("filename", "file1.png"))));

		var categorizer = new XmlChartCategorizer(document);

		// Act
		var category = categorizer.GetChartCategory(new Chart
		{
			FileName = "file1.png"
		});

		// Assert
		Assert.AreEqual("main", category.Main);
		Assert.AreEqual("sub", category.Sub);
		Assert.AreEqual("type", category.Type);
	}

    public class XmlChartCategorizer : IChartCategorizer
    {
        private readonly XDocument _document;

        public XmlChartCategorizer(XDocument document)
        {
            _document = document;
        }

        public ChartCategory GetChartCategory(Chart chart)
        {
            var chartElement = _document.Descendants("chart").FirstOrDefault(x => x.Attribute("filename").Value == chart.FileName);
            if (chartElement != null)
            {
                return new ChartCategory
                {
                    Main = chartElement.Attribute("maincategory").Value,
                    Sub = chartElement.Attribute("subcategory").Value,
                    Type = chartElement.Attribute("type").Value
                };
            }
            return null;
        }
    }

That’s it. We have everything we need, and the library has tests for all business functionality. The design is a LOT different, no? You might not see the value in such a design pattern at first, but consider how flexible it is to change. I’ve already outlined some changes that would be very simple, but more than just simple changes, even complex changes are not going to damage the integrity of the rest of the library. Changing the Categorizer implementation to a SQLChartCategorizer does not have anything to do with where the files come from on disk. Files could come from cloud blob storage, and categories could come from an infinite monkey array… the rest of the code won’t care.

If you implement the infinite monkey array, though, I want to see the code.

Attachments

  1. Example Source Code (1.2 MiB)
    Example source code for parts 1 and 2

2 thoughts on “Test driven development (TDD) should drive your class design (Part 2 of 2): The right way

  1. Pingback: Test driven development (TDD) should drive your class design (Part 1 of 2): The wrong way | Geek Powers

  2. Pingback: Test Driven Development vs. Traditional Testing | Geek Powers

Leave a Reply

Your email address will not be published. Required fields are marked *