I wrote this partly as a learning exercise in TDD. Could I please have feedback on both how to improve the quality of the code and the completeness of the tests?I used LinqPad as my IDE for this, the code has no external dependencies (the test framework is included).void Main(){    var tests = new SimpleClassTests();    tests.RunTests();}// Define other methods and classes hereclass JsonContains{    // start off with simplest case - object with a simple (leaf) value    public bool Contains(IDictionary<string, object> tree, string key, object val )    {        var pathFound  = tree.ContainsKey(key);        if (!pathFound) return false;        var actual = tree[key];        if (actual != null)            return (actual.Equals(val));        else             return (val == null);    }    // and equally simple case - array with a simple (leaf) value    public bool Contains(object[] tree, int key, object val )    {        var pathFound  = key < tree.Length;        if (!pathFound) return false;        var actual = tree[key];        if (actual != null)            return (actual.Equals(val));        else             return (val == null);    }    // let's add depth, part 1 - objects with paths    public bool Contains(IDictionary<string, object> tree, IList<object> path, object val )    {        var first = path.First();        if(first == null || !(first is string))            throw new ArgumentException(string.Format(Expected string as next item in path but got {0}., first));        var key = (string)first;                var tail = new List<object>(path.Skip<object>(1));        // if there's only one step in the path, call the Key version        if (tail == null || tail.Count == 0)        {            return Contains(tree, key, val);        }        // so does our non-null tree contain an item for our key?        var pathFound  = tree.ContainsKey(key);        if (!pathFound)         {            return false;        }        // Helter skelter time - let's recurse!        var nextContext = tree[key];        if (nextContext is System.Dynamic.ExpandoObject)            return Contains((System.Dynamic.ExpandoObject)nextContext, tail, val);        else            return Contains((object[])nextContext, tail, val);    }    // let's add depth, part 2 - arrays with paths    public bool Contains(object[] array, IList<object> path, object val )    {        var first = path.First();        if(!(first is int))            throw new ArgumentException(string.Format(Expected int as next item in path but got {0}., first));        int key = (int)first;        var tail = new List<object>(path.Skip<object>(1));        // if there's only one step in the path, call the Key version        if (tail == null || tail.Count == 0)        {            return Contains(array, key, val);        }        // so does our non-null tree contain an item for our key?        var pathFound = key < array.Length;        if (!pathFound)         {            string.Format(couldn't find item for {0}, key).Dump();            return false;        }        // Helter skelter time - let's recurse...        var nextContext = array[key];        if (nextContext is System.Dynamic.ExpandoObject)            return Contains((System.Dynamic.ExpandoObject)nextContext, tail, val);        else            return Contains((object[])nextContext, tail, val);    }    // slightly more complex case - array to array    public bool Contains(object[] container, object[] containee)    {        for(var n = 0; n < containee.Length; n++)        {            // string.Format(About to compare '{0}' with '{1}' for item '{2}':, container[n], containee[n], n).Dump();            // if the two objects are trees, ensure we use the tree overload            if(container[n] is System.Dynamic.ExpandoObject && containee[n] is System.Dynamic.ExpandoObject)            {                if(!Contains((System.Dynamic.ExpandoObject)container[n], (System.Dynamic.ExpandoObject)containee[n]))                return false;            }            // if the tree objects are arrays, ensure we use the array overload            else if(container[n] is object[] && containee[n] is object[])            {                if(!Contains((object[])container[n], (object[])containee[n]))                return false;            }            // anything else, treat as simple objects            else if(!Contains(container[n], containee[n]))                return false;        }        return true;    }    // slightly more complex case - tree to tree    public bool Contains(IDictionary<string, object> container, IDictionary<string, object> containee)    {        foreach(var key in containee.Keys)        {            // string.Format(About to compare '{0}' with '{1}' for key '{2}':, container[key], containee[key], key).Dump();            // if the two objects are trees, ensure we use the tree overload            if(container[key] is System.Dynamic.ExpandoObject && containee[key] is System.Dynamic.ExpandoObject)            {                if(!Contains((System.Dynamic.ExpandoObject)container[key], (System.Dynamic.ExpandoObject)containee[key]))                return false;            }            // if the tree objects are arrays, ensure we use the array overload            else if(container[key] is object[] && containee[key] is object[])            {                if(!Contains((object[])container[key], (object[])containee[key]))                return false;            }            // anything else, treat as simple objects            else if(!Contains(container[key], containee[key]))                return false;        }        return true;    }    // final case - object to object    public bool Contains(object container, object containee)    {        // string.Format(About to compare '{0}' with '{1}':, container, containee).Dump();        if (container == null)            return (containee == null);        else             return container.Equals(containee);    }   }class SimpleClassTests : UnitTestBase{    [Test]    public void ArrayOfTreeDoesNotContainArrayOfTree()    {        var jsonContains = new JsonContains();        dynamic tree1 = new System.Dynamic.ExpandoObject();        var container = new dynamic[]{ tree1 };        container[0].leaf = red;        container[0].stem = brown;        container[0].height = 72;        dynamic tree2 = new System.Dynamic.ExpandoObject();        var containee = new dynamic[]{ tree2 };        containee[0].height = 72;        containee[0].stem = brown;        containee[0].leaf = green;        var result = jsonContains.Contains(container, containee);        Assert.IsFalse(result);    }    [Test]    public void ArrayOfTreeContainsArrayOfTree()    {        var jsonContains = new JsonContains();        dynamic tree1 = new System.Dynamic.ExpandoObject();        var container = new dynamic[]{ tree1 };        container[0].leaf = red;        container[0].stem = brown;        container[0].height = 72;        dynamic tree2 = new System.Dynamic.ExpandoObject();        var containee = new dynamic[]{ tree2 };        containee[0].height = 72;        containee[0].stem = brown;        containee[0].leaf = red;        var result = jsonContains.Contains(container, containee);        Assert.IsTrue(result);    }    [Test]    public void ArrayOfTreeContainsItself()    {        var jsonContains = new JsonContains();        dynamic tree = new System.Dynamic.ExpandoObject();        var container = new dynamic[]{ tree };        container[0].leaf = red;        container[0].stem = brown;        container[0].height = 72;        var result = jsonContains.Contains(container, container);        Assert.IsTrue(result);    }    [Test]    public void TreeDoesNotContainTree()    {        var jsonContains = new JsonContains();        dynamic container = new System.Dynamic.ExpandoObject();        container.hello = World;        container.answer = 42;        container.hope = false;        container.end = null;        dynamic containee = new System.Dynamic.ExpandoObject();        containee.end = null;        containee.hope = true;        containee.answer = 42;        containee.hello = World;        var result = jsonContains.Contains(container, containee);        Assert.IsFalse(result);    }    [Test]    public void TreeContainsTree()    {        var jsonContains = new JsonContains();        dynamic container = new System.Dynamic.ExpandoObject();        container.hello = World;        container.answer = 42;        container.hope = true;        container.end = null;        dynamic containee = new System.Dynamic.ExpandoObject();        containee.end = null;        containee.hope = true;        containee.answer = 42;        containee.hello = World;        var result = jsonContains.Contains(container, containee);        Assert.IsTrue(result);    }    [Test]    public void ArrayDoesNotContainArray()    {        var jsonContains = new JsonContains();        var container = new dynamic[]{ a, 1, true, z };        var containee = new dynamic[]{ a, 1, true, null };        var result = jsonContains.Contains(container, containee);        Assert.IsFalse(result);    }    [Test]    public void ArrayContainsArray()    {        var jsonContains = new JsonContains();        var container = new dynamic[]{ a, 1, true, null, z };        var containee = new dynamic[]{ a, 1, true, null };        var result = jsonContains.Contains(container, containee);        Assert.IsTrue(result);    }    [Test]    public void ArrayContainsTreeValue()    {        var jsonContains = new JsonContains();        dynamic tree = new System.Dynamic.ExpandoObject();        var hedge = new dynamic[]{ tree };        hedge[0].leaf = red;        var path = new List<object>(){0, leaf};        var result = jsonContains.Contains(hedge, path, red);        Assert.IsTrue(result);    }    [Test]    public void ArrayContainsStringValue()    {        var jsonContains = new JsonContains();        var tree = new dynamic[]{AA, BB, CC};        var result = jsonContains.Contains(tree, 0, AA);        Assert.IsTrue(result);    }    [Test]    public void ArrayContainsNumericValue()    {        var jsonContains = new JsonContains();        // box the double into objects        var tree = new dynamic[]{3.14, 42, 999};        var result = jsonContains.Contains(tree, 0, 3.14);        Assert.IsTrue(result);    }    [Test]    public void ArrayDoesNotContainStringValue()    {        var jsonContains = new JsonContains();        var tree = new string[]{AA, BB, CC};        var result = jsonContains.Contains(tree, 0, xx);        Assert.IsFalse(result);    }    [Test]    public void ArrayRejectsNonIntKey()    {        var jsonContains = new JsonContains();        try        {            dynamic tree = new System.Dynamic.ExpandoObject();            var hedge = new dynamic[]{ tree };            hedge[0].leaf = red;            var path = new List<object>(){zero, leaf};            var result = jsonContains.Contains(hedge, path, red);        }        catch(Exception e)        {            Assert.IsTrue(e is ArgumentException);        }    }    [Test]    public void TreeRejectsNonStringKey()    {        var jsonContains = new JsonContains();        try        {            dynamic tree = new System.Dynamic.ExpandoObject();            var hedge = new dynamic[]{ tree };            hedge[0].leaf = red;            var path = new List<object>(){0, 0};            var result = jsonContains.Contains(hedge, path, red);        }        catch(Exception e)        {            Assert.IsTrue(e is ArgumentException);        }    }    [Test]    public void SimpleTreeContainsStringValue()    {        var jsonContains = new JsonContains();        var tree = new Dictionary<string, object>();        tree.Add(key, val);        var result = jsonContains.Contains(tree, key, val);        Assert.IsTrue(result);    }    [Test]    public void SimpleTreeDoesNotContainStringValue()    {        var jsonContains = new JsonContains();        var tree = new Dictionary<string, object>();        tree.Add(key, val);        var result = jsonContains.Contains(tree, key, xxx);        Assert.IsFalse(result);    }    [Test]    public void SimpleTreeContainsFloatValue()    {        var jsonContains = new JsonContains();        var tree = new Dictionary<string, object>();        tree.Add(key, 3.14);        var result = jsonContains.Contains(tree, new List<object>(){key}, 3.14);        Assert.IsTrue(result);    }    [Test]    public void SimpleTreeContainsTrueValue()    {        var jsonContains = new JsonContains();        var tree = new Dictionary<string, object>();        tree.Add(key, true);        var result = jsonContains.Contains(tree, key, true);        Assert.IsTrue(result);    }    [Test]    public void SimpleTreeContainsFalseValue()    {        var jsonContains = new JsonContains();        var tree = new Dictionary<string, object>();        tree.Add(key, false);        var result = jsonContains.Contains(tree, key, false);        Assert.IsTrue(result);    }    [Test]    public void SimpleTreeContainsNullValue()    {        var jsonContains = new JsonContains();        var tree = new Dictionary<string, object>();        tree.Add(key, null);        var result = jsonContains.Contains(tree, key, null);        Assert.IsTrue(result);    }    [Test]    public void SimpleTreeDoesNotContainKey()    {        var jsonContains = new JsonContains();        var tree = new Dictionary<string, object>();        tree.Add(key, val);        var result = jsonContains.Contains(tree, xxx, xxx);        Assert.IsFalse(result);    }    [Test]    public void ExpandoObjectContainsValue()    {        var jsonContains = new JsonContains();        dynamic tree = new System.Dynamic.ExpandoObject();        tree.root = new System.Dynamic.ExpandoObject();        tree.root.key = val;        IList<object> path = new List<object>(){root, key};        var result = jsonContains.Contains(tree, path, val);        Assert.IsTrue(result);    }    [Test]    public void ExpandoObjectContainsNull()    {        var jsonContains = new JsonContains();        dynamic tree = new System.Dynamic.ExpandoObject();        tree.root = new System.Dynamic.ExpandoObject();        tree.root.key = null;        IList<object> path = new List<object>(){root, key};        var result = jsonContains.Contains(tree, path, null);        Assert.IsTrue(result);    }    [Test]    public void ExpandoObjectDoesNotContainKey()    {        var jsonContains = new JsonContains();        dynamic tree = new System.Dynamic.ExpandoObject();        tree.root = new System.Dynamic.ExpandoObject();        tree.root.key = val;        IList<object> path = new List<object>(){root, xxx};        var result = jsonContains.Contains(tree, path, val);        Assert.IsFalse(result);    }    [Test]    public void ExpandoObjectDoesNotContainValue()    {        var jsonContains = new JsonContains();        dynamic tree = new System.Dynamic.ExpandoObject();        tree.root = new System.Dynamic.ExpandoObject();        tree.root.key = val;        IList<object> path = new List<object>(){root, key};        var result = jsonContains.Contains(tree, path, xxx);        Assert.IsFalse(result);    }}// test framework initially based on http://www.youtube.com/watch?feature=player_detailpage&list=PL3D3F4B7C71FF6AA0&v=hayjhjIKSwA[AttributeUsage(AttributeTargets.Method)]class SetupAttribute : Attribute{}[AttributeUsage(AttributeTargets.Method)]class TestAttribute : Attribute{}[AttributeUsage(AttributeTargets.Method)]class TeardownAttribute : Attribute{}class Assert{    public bool Passed { get; set; }    public string Message { get; private set; }    public Assert()    {        Reset();    }    public void Reset()    {        Passed = true;        Message = string.Empty;    }    public void AreEqual(double expected, double actual)    {        if(!expected.Equals(actual))        {            Passed = false;            Message = string.Format(Expected {0}, but was {1}., expected, actual);        }    }    public void AreEqual(bool expected, bool actual)    {        if(!expected.Equals(actual))        {            Passed = false;            Message = string.Format(Expected {0}, but was {1}., expected, actual);        }    }    public void IsTrue(bool actual)    {        AreEqual(true, actual);    }    public void IsFalse(bool actual)    {        AreEqual(false, actual);    }    public void WriteResults(string methodName)    {        if(Passed)            Console.WriteLine(Success: {0}, methodName);        else            Console.WriteLine(Failed: {0} - {1}, methodName, Message);    }}abstract class UnitTestBase{    protected Assert Assert {get; private set;}    public UnitTestBase()    {        Assert = new Assert();    }    public void RunTests()    {        // run Setup methods        var methods = this.GetType().GetMethods();        foreach (var method in methods.Where(m => m.IsDefined(typeof(SetupAttribute), false)))        {            this.GetType().InvokeMember(method.Name, BindingFlags.InvokeMethod, null, this, null);        }        // run Test methods        foreach (var method in methods.Where(m => m.IsDefined(typeof(TestAttribute), false)))        {            // clear results            Assert.Reset();            // run the test            this.GetType().InvokeMember(method.Name, BindingFlags.InvokeMethod, null, this, null);            // report results            Assert.WriteResults(method.Name);        }        // run Teardown methods        foreach (var method in methods.Where(m => m.IsDefined(typeof(TeardownAttribute), false)))        {            this.GetType().InvokeMember(method.Name, BindingFlags.InvokeMethod, null, this, null);        }    }}
A C# class to check if one JSON tree (de-serialised as ExpandoObject / Array etc) contains another
c#;json;tdd
Not sure I should be answering my own question, but having slept on it and reviewed the code my perspective has moved on.I've reviewed my use cases, and it turns out that I only need the three two-argument overloads, which I wrote last, and can ditch the four other, supposedly simpler, overloads and their associated tests.I'm not sure whether to draw the lesson that I shouldn't have started with tests which made assumptions about how I was going to implement the solution, or that this just reflects the evolutionary nature of the TDD process. Either way, the code is now smaller and simpler, and I'm happy.