Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

Microsoft.VisualStudio.TestTools.UnitTesting.Assert generic method overloads behavior

Tags:

c#

In the Microsoft.VisualStudio.TestTools.UnitTesting namespace, there is the handy static class Assert to handle the assertions going on in your tests.

Something that has got me bugged is that most methods are extremely overloaded, and on top of that, they have a generic version. One specific example is Assert.AreEqual which has 18 overloads, among them:

Assert.AreEqual<T>(T t1, T t2)

What is the use of this generic method? Originally, I thought this was a way to directly call the IEquatable<T> Equals(T t) method, but that is not the case; it will always call the non-generic version object.Equals(object other). I found out the hard way after coding quite a few unit tests expecting that behavior (instead of examining the Assert class definition beforehand like I should have).

In order to call the generic version of Equals, the generic method would had to be defined as:

Assert.AreEqual<T>(T t1, T t2) where T: IEquatable<T>

Is there a good reason why it wasn't done this way?

Yes, you loose the generic method for all those types that don't implement IEquatable<T>, but it's not a great loss anyway as equality would be checked through object.Equals(object other), so Assert.AreEqual(object o1, object o2) is already good enough.

Does the current generic method offer advantages I'm not considering, or is it just the case that no one stopped to think about it as it's not that much of a deal? The only advantage I see is argument type safety, but that seems kind of poor.

Edit: fixed an error where I kept referring to IComparable when I meant IEquatable.

like image 888
InBetween Avatar asked Sep 03 '14 15:09

InBetween


1 Answers

The method having that constraint would be non-ideal because of the oft-faced problem of constraints not being part of the signature.

The issue would be that for any T that is not covered by its own specific overload, the compiler would choose the generic AreEqual<T> method as the best fit during overload resolution, as it would indeed by an exact match. In a different step of the process, the compiler would evaluate that T passes the constraint. For any T that does not implement IEquatable<T>, this check would fail and the code would not compile.

Consider this simplified example of the unit testing library code and a class that might exist in your library:

public static class Assert
{
    public static void AreEqual(object expected, object actual) { }
    public static void AreEqual<T>(T expected, T actual) where T : IEquatable<T> { }
}

class Bar { } 

Class Bar does not implement the interface in the constraint. If we were then to add the following code to a unit test

Assert.AreEqual(new Bar(), new Bar());

The code would fail to compile because of the unsatisfied constraint on the method that is the best candidate. (Bar substitutes for T, which makes it a better candidate than object.)

The type 'Bar' cannot be used as type parameter 'T' in the generic type or method 'Assert.AreEqual<T>(T, T)'. There is no implicit reference conversion from 'Bar' to 'System.IEquatable<Bar>'.

In order to satisfy the compiler and allow our unit test code to compile and run, we would have to cast at least one input to the method to object so that the non-generic overload can be chosen, and this would be true for any given T that might exist in your own code or code you consume that you wish to use in your test cases that does not implement the interface.

Assert.AreEqual((object)new Bar(), new Bar());

So the question must be asked -- would that be ideal? If you were writing a unit testing library, would you create such a method with such an unfriendly limitation? I suspect you would not, and the implementers of the Microsoft unit testing library (whether it was for this reason or not) did not either.

like image 142
Anthony Pegram Avatar answered Oct 20 '22 06:10

Anthony Pegram