Thursday, January 29, 2009

Test Utilities using extension methods

So, I was working with Jeff McWherter on some TDD stuff in C# today, and I came upon a trick that I really like. When I used to do C#, I was working in the 2.0 version of the .net framework. This meant that, while I had cool things like generics and anonymous methods to help me out, I didn't have extension methods. Well, Jeff and I were working in .net 3.0, so I had the benefits.

We were writing some tests that need a couple instances of a data object. Well, my first test new-ed up the instance and added it to the collection. This instance needed to have the due date property set, so I did something like this:



Well, there's a bit of annoying duplication there, so I changed it to:



with a method later on that looks like this



Okay, a bit better.

A little bit later, we had another test fixture that used the data object in a similar way, so I was looking for a way to bring the thing() method into a scope that was available for the other fixture, as well. When I was doing 2.0, I would have probably built a base class for my test fixtures and subclasses it in both fixtures. Yuk! Those who know me know that I have a special, black, cold place in my heart for subclassing like this.

Enter extension methods


I want a way to have some utility methods that I can use whenever I have a fixture that needs to build things. Aha! Let's make an interface:

public interface TestsThings {}

And, add an extension method to it:



Cool. Now, my test fixtures can implement TestsThings and get the method, so the construction of the lists will now look like this:



Unnecessary Construction Parameters? Yuk!



Well, not happy to leave a bad thing bad, I decided to take it one step further. I want to be able to write



How to do that? Hmm.... Ah, here we go. Let's change ThingTestingUtilities to look like this:



Works much better. But, there is still a matter of duplication in the DateTime.Now... stuff. So, let's take this a step further. What if I could write this:


That looks much better. Yeah, if I was in a different language, I'd do something like:
this.NewThings().WithAnOffsetDueDate(1.days);
[Update: Steven Harman alerted me that you can do this. I'm too lazy right now to implement it, though. Maybe later]
[Update2: Leon Gersin added a comment showing how to extend it like 1.days. Check it out.]

So, let's add a helper method. The utility class looks like this now:



Cool, compiles, runs, now my construction looks like this:



Extension methods can definitely help do some neat stuff. I don't have to subclass, I can add methods by simply implementing an interface. This also gives me the ability to create interfaces that are focused on different tasks that my test fixtures might need, so I don't have to load up subclasses with unnecessary mixes of unrelated methods.

5 comments:

  1. Harman's right, you can do something like
    +++++++++
    public static class ThingTestingUtilities {
    public static DateTime days(this int input) {
    return DateTime.Now.AddDays(input);
    }
    }
    +++++++++

    Which will give you the sugar you want. Otherwise, C#'s fake "composition" is extension methods. my only hold up is that it's halfway to what you'd REALLY want to write and maintain. It's a good technique that I'm using more and more to eliminate inheritance hierarchies.

    Since inheritance is flawed in C# (I would argue completely fucking brokenzorz) extension methods, for composition, is the best that you're going to get in C#. Looks good man. Now come to Dayton and we'll go nutz with this shit.

    ReplyDelete
  2. I'm not feeling it.
    You're first snippet is not "fluent" but it is clear, nonetheless.

    While your last snippet is "fluent", it isn't any more clear _and_ you've introduced a ThingsTestingUtilities class that holds two methods to do what you originally did in one line.

    ReplyDelete
  3. @Mark

    I'm finding, as I talk to more and more people, that "clear" is often a matter of time and experience with new things (good example is foreach loops vs iterator methods). I would much prefer to continue the implementation with the technique that @fallenrogue outlines in his comment, but I'm a bit lazy on that respect.

    I don't tend to judge things based on the number of lines of code vs the number of classes. Instead, I strive to eliminate duplication. Often times, this leads to more classes in order to hold the extraction.

    ReplyDelete
  4. @Mark

    I'd be very interested what you mean when you say that the last snippet isn't "clear." I'm finding that different people have different meanings for their usage.

    For me, something like
    NewThing().WithAnOffsetDueDateInDays(5)
    is very clear: it is creating a new thing and setting the due date to an offset. Perhaps a name change, though, would make it clearer.
    NewThings().WithADueDateOffsetInDays(5)
    Hmm.... I think I like that better. It makes it even more clear that it is setting the DueDate that way.
    Perhaps even
    .WithADueDateOffsetFromTodayInDays(5)
    Now that I think about it, I think I like that even better.

    ReplyDelete
  5. Corey,
    I think the last snippet is very clear, but I'm not convinced it's an improvement (admittedly per my definition of clarity) over the first snippet, which is already very clear.

    One way to get better code clarity is with simple descriptive methods, like what you've got within the utility class. An entire codebase can also have a sense of clarity to it. I wonder if you've sacrificed the clarity of the codebase as a whole for a too small improvement to the clarity of the first snippet.

    ReplyDelete