2009/09/01

Equality Assertions in MbUnit v3

As .NET developers, we all know that the notion of object equality is not as simple as it looks first. Object equality is in fact probably as fundamental and difficult to understand as the famous concept of pointers in the good old C language. The fact is that writing unit tests, with Gallio/MbUnit or with any other existing framework is mostly about making equality assertions on the output of various code components. Thus chances are high you be stuck soon or later by an equality assertion which should obviously pass, but unexpectedly fails because your type does not implement any reliable equality mechanism. That's why I would like to review in that article, some features of MbUnit v3.1 which may help you using properly the powerful equality assertions.

Let's start with a simple example.

[TestFixture]
public class SolarSystemTest
{
[Test]
public void Number_of_planets_in_the_solar_system()
{
var repository = new StarSystemRepository();
var solarSystem = repository.GetLocalSystem();
int count = solarSystem.CountPlanets();
Assert.AreEqual(8, count);
}
}
We use here the well-known Assert.AreEqual to verify that the actual number of planets found in our solar system is 8, as expected. Of course, the equality assertion knows how to compare System.Int32 values. Basically, it knows how to compare any primitive like System.String or System.Double. But what happens while asserting on non-primitive types?

By default, MbUnit relies on the result returned by the overridable Object.Equals method. And by default, that method simply consists of a referential equality. Thus it returns true only if the 2 objects compared represent the same instance (EDIT: OK, let's ignore the case where objects are null for the moment.). That's why, given our implementation of the class Planet, the following test miserably fails.

public class Planet
{
public string Name
{
get;
private set;
}

public Planet(string name)
{
Name = name;
}
}

[TestFixture]
public class SolarSystemTest
{
[Test]
public void Mercury_is_the_closest_planet_to_the_sun()
{
var repository = new StarSystemRepository();
var solarSystem = repository.GetLocalSystem();
IPlanet actual = solarSystem.GetClosestPlanetToTheStar();
Assert.AreEqual(new Planet("Mercury"), actual); // Fail!?
}
}
So how should we write the assertion to get the expected result? There are several solutions.

  • Asserting the inner object properties.

    The most obvious solution is indeed to verify the inner properties of the actual object. We could just replace the failing assertion by:
    Assert.AreEqual("Mercury", actual.Name);
    For a simple scenario such as in our example, this is surely enough. But what if Planet is in fact a complex entity, with a dozen of properties, each hiding a complex tree of entities and value objects? At best, you would end up with a very large number of unmaintainable assertions. That's why you should avoid that solution if you get more than 3 or 4 assertions.
  • Supporting equality.

    The second possibility consists in implementing a dedicated equality mechanism for the evaluated type. The standard way of doing it is to implement the IEquatable<T> interface.
    public class Planet : IEquatable<Planet>
    {
    public string Name
    {
    get;
    private set;
    }

    public Planet(string name)
    {
    Name = name;
    }

    public bool Equals(Planet other)
    {
    return (other != null) && (Name == other.Name);
    }

    public override int GetHashCode()
    {
    return Name.GetHashCode();
    }

    public override bool Equals(object obj)
    {
    return Equals(obj as Planet);
    }
    }
    This may sound like a perfectly reasonable solution. In fact, if you are a lucky developer, chances are good that your class already implements such an equality mechanism. It is perhaps a needed feature of your code base. If yes, then look no further, and just use it. The initial assertion will pass. Congratulations! You made it.

    But if no, then Planet has no equality mechanism probably because it does not need of any. It means that you are about to add an unnecessary feature to your code base, just to make your tests easier to write. Remember YAGNI? You ain't gonna need it! If Planet does not need to be equatable, then why making it equatable? Your answer should never be: "To make it more testable". This is a short way to the dark side of the force, I assure you. Adding unnecessary code that will only eventually be used by your unit tests is certainly a bad practice.

  • Using the comparison delegate.

    Most of the MbUnit equality assertions take a third optional parameter of the type EqualityComparison<T>. The equality comparison delegate is a function which takes two instances of the same type, and returns true if they are equal. If your scenario is simple enough, you can provide such a function to specify to the assertion how to compare the objects. With the lambda syntax, it looks very elegant.
    Assert.AreEqual(new Planet("Mercury"), actual, (x, y) => x.Name == y.Name);
    And if the object has a reasonable number of properties, that's still a good solution.
    Assert.AreEqual(new Planet("Mercury"), actual, (x, y) => 
    x.Name == y.Name &&
    x.Weight = y.Weight &&
    x.DistanceToStar == y.DistanceToStar);
    But again, if the type has too many properties, or if these properties are not more equatable than their parent, you will get a complete mess.
  • Using the structural equality comparer.

    MbUnit v3.1 comes with a fantastic built-in feature which deserves to be better known (but that's the point of this post anyway). Basically, the structural equality comparer (StructuralEqualityComparer<T>) provides to the assertions a convenient way to determine whether two instances of a type are equal or not; while the type itself does not implement any relevant equality mechanism.
    Assert.AreEqual(new Planet("Mercury"), actual, 
    new StructuralEqualityComparer<Planet>
    {
    { x => x.Name }
    });
    Well, it does not look so impressive, does it? The comparer instance is populated with one comparison criterion that says to the assertion engine to use the property Name to compare two Planet instances. The syntax is not much more complicated when you have several properties.
    Assert.AreEqualnew Planet("Mercury"), actual, 
    new StructuralEqualityComparer<Planet>
    {
    { x => x.Name },
    { x => x.Weight },
    { x => x.DistanceToSun }
    });
    The true power appears when you know that each equality criterion is easily customizable, either with a comparison delegate, or with a new inner comparer.
    Assert.AreEqual(new Planet("Mercury"), actual, 
    new StructuralEqualityComparer<Planet>
    {
    { x => x.Name, (a, b) => a.Equals(b, StringComparison.OrdinalIgnoreCase) },
    { x => x.Weight },
    { x => x.DistanceToSun },
    { x => x.Revolution, (a, b) => a.Period == b.Period }
    });
    As you see, each criterion is able to define its own comparison rules. You can also nest structural equality comparers and define them as a comparison rule for an inner criterion.
    Assert.AreEqual(new Planet("Mercury"), actual, 
    new StructuralEqualityComparer<Planet>
    {
    { x => x.Name, (a, b) => a.Equals(b, StringComparison.OrdinalIgnoreCase) },
    { x => x.Weight },
    { x => x.DistanceToSun },
    { x => x.Revolution, new StructuralEqualityComparer<Revolution> { { x => x.Period } } }
    });
    The comparer works very well with enumerations too. It provides a similar result to what do Assert.AreElementsEqual and Assert.AreElementsEqualIgnoringOrder. Suppose that Planet has now a property named Satellites which returns an instance of the type IEnumerable<Satellite>. Adding them into the overall comparison structure is easy. The comparer also supports some options to ignore the order of the child elements.
    Assert.AreEqual(new Planet("Mercury"), actual, 
    new StructuralEqualityComparer<Planet>
    {
    { x => x.Name, (a, b) => a.Equals(b, StringComparison.OrdinalIgnoreCase) },
    { x => x.Weight },
    { x => x.DistanceToSun },
    { x => x.Revolution, (a, b) => a.Period == b.Period }
    { x => x.Satellites, new StructuralEqualityComparer<Satellite>
    {
    { x.Name },
    { x.DistanceToPlanet }
    }, StructuralEqualityComparerOptions.IgnoreEnumerableOrder
    }
    });
    As already explained, the structural equality comparer can be used with most of the equality assertions. It is particularly useful with the equality assertions for collections.
    Assert.AreElementsEqualIgnoringOrder(
    new[] { new Satellite("Deimos"), new Satellite("Phobos") },
    mars.Satellites,
    new StructuralEqualityComparer<Satellite> { { x => x.Name } });
See you next time!

2 comments:

Kevin McFarlane said...

Hi Yann, these posts on MbUnit features are very good. I'm fairly new to MbUnit and think it's a great framework.

Yann Trevin said...

Thank you Kevin, for your blog article.