Improving test code readability using delegates

During the development lifecycle, there are many different automated tests which should be wrote, each style of test has different priorities and different levels of maintenance required. For example, TDD helps with designing the code your just about to write, they are small and focused. As such, when your code changes, your tests change too. Where as acceptance tests have a much longer life-time, there role is to ensure the application works and nothing is regressed as developers continue to make changes. These tests need to be maintainable enough to cope with application changes, readable enough to identity the story feature they are testing with reusableflexible sections to help readability and maintainability.

In an attempt for my tests to meet these goals, recently I’ve been using delegates in certain scenarios to improve my test code. Delegates are great for reusing sections of code, it allows you to have static sections of code but be flexible to cope with minor changes and different scenarios the tests might cover.

For example, below is an acceptance test which must connect to a server based on a project file which has been saved to disk.  Remember, acceptance tests should cover the system in an end-to-end fashion.

In order to test the application, the code needs to do a lot.  It needs to create and configure a project, save it to disk, create the service and finally it gets to the point of what it is testing – connecting to the service. This code, while it is flexible it is not very maintainable. If we need to change something related to the project or how we connect to the service we would have to change a lot of code to take this into account.

public class Project_Execute_Tests_Standard
{
    [Fact]
    public void Project_Can_Login_And_Can_Connect()
    {
        string path = System.IO.Path.GetRandomFileName();

        Service service = null;
        try
        {
            Project project = new Project();
            project.Server = “Test”;
            project.Username = “Admin”;
            project.Password = “Password”;
            project.Save(path);

            service = new Service();
            service.Start();

            service.LoadProject(path);

            bool connect = service.Connect();
            Assert.True(connect);
        }
        finally
        {
            if(service != null && service.Started)
                service.Stop();
            File.Delete(path);
        }
    }
}

The first logical step would be to use helper methods.  We can extract the creation of the project and gaining access to the service into separate methods which we can reuse throughout our test code. However, these methods aren’t very flexible, if we need to add some more configuration to the project we would have to create a different method and as such losing some of our maintainability.

[Fact]
public void Project_Can_Login_And_Can_Connect()
{
    string path = string.Empty;
    Service service = null;
    try
    {
        path = CreateProject();

        service = GetService(path);
        bool connect = service.Connect();
        Assert.True(connect);
    }
    finally
    {
        if (service != null && service.Started)
            service.Stop();
        File.Delete(path);
    }
}

This is why I like delegates. You have the advantage of helper methods, but the flexibility of having the code inline. Below I’ve created a helper class, this abstracts away from my actual implementation and manages the state, such as the file path for the project. Within the delegate for the project, I’m setting all the details based on the requirements of the test and different configurations, however the rest is abstracted away.

public class Project_Execute_Tests_Delegate
{
    [Fact]
    public void Project_Can_Login_And_Can_Connect()
    {
        ServiceTester tester = new ServiceTester();
        tester.CreateProject(delegate(Project project)
                                        {
                                            project.Server = “Test”;
                                            project.Username = “Admin”;
                                            project.Password = “Password”;
                                        });

        bool connect = tester.ConnectToService();
        Assert.True(connect);
    }
}

While I was writing this example, DevExpress popped up and told me it can shorten my delegate to a lambda expression. As a result, the delegate could be this.

tester.CreateProject(p =>
                            {
                                p.Server = “Test”;
                                p.Username = “Admin”;
                                p.Password = “Password”;
                            });

Within my ServiceTester helper class, my CreateProject method looks like this:

public delegate void ProjectDelegate (Project project);
public void CreateProject(ProjectDelegate ProjectSettings)
{
     ProjectPath = System.IO.Path.GetRandomFileName();
     Project project = new Project();

     ProjectSettings(project);
     project.Save(ProjectPath);
}

Given the right scenario, I think this could really improve your test code.

Download complete code sample: http://blog.benhall.me.uk/Code/Test/TestCodeDelegates.txt

Technorati Tags: ,

7 thoughts on “Improving test code readability using delegates”

  1. Inner functions for readability and modularity. Welcome to the the world of Python. 😉

    We’ve been doing this for fifteen years, but it’s great club so some on it…

    (Mind you – pretty much everything we’ve been doing was being done in Lisp and Smalltalk 15 years before that…)

  2. Looking at dynamic languages and how they can improve testability is on my list of things to do 🙂 Especially if items can then be brought back into C#

    Might have a closer look at Inner functions, only used them within methods, it would be interesting to see how far this could be extended.

  3. Thanks for the tip Mike, this would be better if your test project is using 3.5.

    Sadly, when I came up with using delegates I was stuck on 20052.0, which is why I used the other approach.

    Thanks

    Ben

  4. Ahh sorry my mistake! I thought Action was part of 3.5 – it is in fact 2.0. Thanks 🙂

    MSDN:
    public delegate void Action< T >(T obj)
    Member of System

    Summary:
    Encapsulates a method that takes a single parameter and does not return a value.

Leave a Reply

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