2

I've been working on some MSTest automated test infrastructure, that is testing a tool that merges data sets into SQL Server database tables. The basic structure of the test is to:

  1. Define the incoming dataset using anonymous types
  2. Apply the data using the reconcile tool
  3. Read records from the output tables
  4. Compare result rows to input data, column by column

Example input data:

public class InputData : List<dynamic> {} // Inspired by Massive etc

InputData input = new InputData()
{
    new { ExternalID = 1, PropertyName = "hello", Agent = "test" },
    new { ExternalID = (Int64)2, PropertyName = "fred", Agent = "test" } // Fixes the problem with a cast
};

The issue I'm dealing with, is that the type inferred on my anonymous objects will be an Int32, but the corresponding column in my target table is a bigint, and hence the record will have an Int64 value. As a result, when I use Assert.AreEqual across each column, it fails on any int fields:

Assert.AreEqual failed. Expected:<1 (System.Int32)>. Actual:<1 (System.Int64)>

You can see I have cast the int on my second anonymous object, this can be used to fix the issue. The primary aim of these tests is to make the sample data as slim and easy to read/write as possible, and I'd prefer to avoid the visual noise of all those casts.

I'm thinking about the best way to deal with the assertions. It seems like I should use dedicated assertions based on type. I guess the real question is, how aggressive should I be in converting between types automatically?

  • 2
    Why not set the type on your anonymous object to long (ie: define ExternalID = 1L) – Michael Sep 11 '13 at 8:53
  • Very nice, that is a much less noisy way to imply type for this case. I probably need a set of tests where I explicitly try different incoming data types. But most scenarios will be best suited to the implied types as described in my question. – eddie.sholl Sep 11 '13 at 23:29
5

Assert.AreEqual is basically just sugar for Assert.IsTrue(object.Equals(...)). If there isn't a known comparison operator for the two argument types, they'll compare false.

dynamic is pretty smart about this, so, for example, (dynamic)(int)1 == (decimal)1.0 evaluates to true. That only works if you use the == operator, though; it won't work if you try to use object.Equals, Assert.AreEqual, ((dynamic)x).Equals(...) or anything similar. Simply casting to dynamic won't work, you need to actually use its == operator. The good news is, the dynamic == allows any operand, which means you can get this to compile in a general-purpose assertion; if the comparison is invalid (e.g. comparing int to string, then it will fail at runtime with a RuntimeBinderException.

The next best thing, if you know they're going to be numeric types, would be to cast them all to Decimal or Double, although note that if you use Double you'll have to take into account the possibility of floating-point rounding error, and you should define equality based on some threshold, typically a multiple of Double.Epsilon. Decimal is a lot simpler unless you know for certain that you'll be dealing with floating point.

I don't think that you really have a lot of other choices. I personally would not recommend string conversion because lots of classes don't override ToString, so you end up comparing two instances of something like System.Collections.Generic.List``1[System.Int32], and your assertion declares that these are equal because they have the same string representation when in fact they are not only different instances but contain completely different elements. So don't do that. String coercion can easily mask serious errors in your tests. Stick to non-lossy, all-or-nothing conversions as above.

0

Convert to strings is what I think. Strings are underestimated in test cases. Then you can write a method thats like

void AssertEqual(Object a, Object b)
{
  Assert.AreEqual(a.ToString(), b.ToString();
}

and maybe add a parameter that is the fail message.

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service, privacy policy and cookie policy

Not the answer you're looking for? Browse other questions tagged or ask your own question.