views:

414

answers:

5

Let's say I have a complex .NET class, with lots of arrays and other class object members. I need to be able to generate a deep clone of this object - so I write a Clone() method, and implement it with a simple BinaryFormatter serialize/deserialize - or perhaps I do the deep clone using some other technique which is more error prone and I'd like to make sure is tested.

OK, so now (ok, I should have done it first) I'd like write tests which cover the cloning. All the members of the class are private, and my architecture is so good (!) that I haven't needed to write hundreds of public properties or other accessors. The class isn't IComparable or IEquatable, because that's not needed by the application. My unit tests are in a separate assembly to the production code.

What approaches do people take to testing that the cloned object is a good copy? Do you write (or rewrite once you discover the need for the clone) all your unit tests for the class so that they can be invoked with either a 'virgin' object or with a clone of it? How would you test if part of the cloning wasn't deep enough - as this is just the kind of problem which can give hideous-to-find bugs later?

+1  A: 

I'd just write a single test to determine if the clone was correct or not. If the class isn't sealed, you can create a harness for it by extending it, and then exposing all your internals within the child class. Alternatively, you could use reflection (yech), or use MSTest's Accessor generators.

You need to clone your object and then go through every single property and variable that your object has and determine if it was copied correctly or cloned correctly.

Will
+2  A: 

I'm not sure why reflection is "(yech)" - it actually seems like I good idea that I hadn't thought of, so thanks for that. Now you say it I remember seeing a lot of reflection going on in the MSTest stuff, on the one time I played with it.

To me it seems much less "yech" than exposing all the innards of the class merely for the benefit of the test harness (whether that's through properties or protected fields). Maybe I should relax more about that, but it doesn't feel right to me.

Will Dean
+2  A: 

You method of testing will depend on the type of solution you come up with. If you write some custom cloning code and have to manually implement that in each cloneable type then you should really test the cloning of each one of those types. Alternatively, if you decide to go a more generic route (where the aforementioned reflection would likely fit in), your tests would only need to test the specific scenarios that you cloning system will have to deal with.

To answer your specific questions:

Do you write (or rewrite once you discover the need for the clone) all your unit tests for the class so that they can be invoked with either a 'virgin' object or with a clone of it?

You should have tests for all the methods that can be performed on both the original and cloned objects. Note that it should be pretty easy to set up a simple test design to support this without manually updating the logic for each test.

How would you test if part of the cloning wasn't deep enough - as this is just the kind of problem which can give hideous-to-find bugs later?

It depends on the cloning method you choose. If you have to manually update the cloneable types then you should test that each type is cloning all (and only) the members you expect. Whereas, if you are testing a cloning framework I would create some test cloneable types to test each scenario you need to support.

akmad
+1  A: 

I like to write unit tests that use one of the builtin serializers on the original and the cloned object and then check the serialized representations for equality (for a binary formatter, I can just compare the byte arrays). This works great in cases where the object is still serializable, and I'm only changing to a custom deep clone for perf reasons.

Furthermore, I like to add a debug mode check to all of my Clone implementations using something like this

[Conditional("DEBUG")]
public static void DebugAssertValueEquality<T>(T current, T other, bool expected, 
                                               params string[] ignoredFields) {
    if (null == current) 
    { throw new ArgumentNullException("current"); }
    if (null == ignoredFields)
    { ignoredFields = new string[] { }; }

    FieldInfo lastField = null;
    bool test;
    if (object.ReferenceEquals(other, null))
    { Debug.Assert(false == expected, "The other object was null"); return; }
    test = true;
    foreach (FieldInfo fi in current.GetType().GetFields(BindingFlags.Instance)) {
        if (test = false) { break; }
        if (0 <= Array.IndexOf<string>(ignoredFields, fi.Name))
        { continue; }
        lastField = fi;
        object leftValue = fi.GetValue(current);
        object rightValue = fi.GetValue(other);
        if (object.ReferenceEquals(null, leftValue)) {
            if (!object.ReferenceEquals(null, rightValue))
            { test = false; }
        }
        else if (object.ReferenceEquals(null, rightValue))
        { test = false; }
        else {
            if (!leftValue.Equals(rightValue))
            { test = false; }
        }
    }
    Debug.Assert(test == expected, string.Format("field: {0}", lastField));
}

This method relies on an accurate implementation of Equals on any nested members, but in my case anything that is cloneable is also equatable

Andrew
+1  A: 

There's a really obvious solution that doesn't take nearly as much work:

  1. Serialize the object into a binary format.
  2. Clone the object.
  3. Serialize the clone into a binary format.
  4. Compare the bytes.

Assuming that serialization works - and it better because you are using it to clone - this should be easy to maintain. In fact, it will be encapsulated from changes to the structure of your class completely.