UnitTesting列出<t>自定义对象的List <s>自定义对象的相等性</s> </t>

时间:2014-11-17 16:42:11

标签: c# unit-testing ienumerable iequalitycomparer

我正在为解析器编写一些UnitTests而且我一直在比较两个List<T>,其中T是我自己的一个类,其中包含另一个List<S>

我的UnitTest比较两个列表并失败。 UnitTest中的代码如下所示:

CollectionAssert.AreEqual(list1, list2, "failed");

我写了一个测试场景,应该澄清我的问题:

using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Threading.Tasks;

namespace ComparerTest
{
    class Program
    {
        static void Main(string[] args)
        {
            List<SimplifiedClass> persons = new List<SimplifiedClass>()
            {
                new SimplifiedClass()
                {
                 FooBar = "Foo1",
                  Persons = new List<Person>()
                  {
                    new Person(){ ValueA = "Hello", ValueB="Hello"},
                    new Person(){ ValueA = "Hello2", ValueB="Hello2"},
                  }
                }
            };
            List<SimplifiedClass> otherPersons = new List<SimplifiedClass>()
            {
                new SimplifiedClass()
                {
                 FooBar = "Foo1",
                  Persons = new List<Person>()
                  {
                    new Person(){ ValueA = "Hello2", ValueB="Hello2"},
                    new Person(){ ValueA = "Hello", ValueB="Hello"},
                  }
                }
            };
            // The goal is to ignore the order of both lists and their sub-lists.. just check if both lists contain the exact items (in the same amount). Basically ignore the order

            // This is how I try to compare in my UnitTest:
            //CollectionAssert.AreEqual(persons, otherPersons, "failed");
        }
    }

    public class SimplifiedClass
    {
        public String FooBar { get; set; }
        public List<Person> Persons { get; set; }

        public override bool Equals(object obj)
        {
            if (obj == null) { return false;}

            PersonComparer personComparer = new PersonComparer();
            SimplifiedClass obj2 = (SimplifiedClass)obj;
            return this.FooBar == obj2.FooBar && Enumerable.SequenceEqual(this.Persons, obj2.Persons, personComparer); // I think here is my problem
        }

        public override int GetHashCode()
        {
            return this.FooBar.GetHashCode() * 117 + this.Persons.GetHashCode();
        }
    }

    public class Person
    {
        public String ValueA { get; set; }
        public String ValueB { get; set; }

        public override bool Equals(object obj)
        {
            if (obj == null)
            {
                return false;
            }
            Person obj2 = (Person)obj;
            return this.ValueA == obj2.ValueA && this.ValueB == obj2.ValueB;
        }

        public override int GetHashCode()
        {
            if (!String.IsNullOrEmpty(this.ValueA))
            {
                //return this.ValueA.GetHashCode() ^ this.ValueB.GetHashCode();
                return this.ValueA.GetHashCode() * 117 + this.ValueB.GetHashCode();
            }
            else
            {
                return this.ValueB.GetHashCode();
            }
        }

    }

    public class PersonComparer : IEqualityComparer<Person>
    {
        public bool Equals(Person x, Person y)
        {
            if (x != null)
            {
                return x.Equals(y);
            }
            else
            {
                return y == null;
            }
        }

        public int GetHashCode(Person obj)
        {
            return obj.GetHashCode();
        }
    }
}

这个问题与C# Compare Lists with custom object but ignore order密切相关,但除了将列表包装到另一个对象并使用上面一个级别的UnitTest之外,我找不到区别。

我尝试使用IEqualityComparer

public class PersonComparer : IEqualityComparer<Person>
{
    public bool Equals(Person x, Person y)
    {
        if (x != null)
        {
            return x.Equals(y);
        }
        else
        {
            return y == null;
        }
    }

    public int GetHashCode(Person obj)
    {
        return obj.GetHashCode();
    }
}

之后我尝试实现了“IComparable”接口,允许对象进行排序。 (基本上是这样的:https://stackoverflow.com/a/4188041/225808) 但是,我不认为我的对象可以被赋予自然顺序。因此,我认为这是一个黑客,如果我想出一个随机的方式来排序我的课程。

public class Person : IComparable<Person>
public int CompareTo(Person other)
{
  if (this.GetHashCode() > other.GetHashCode()) return -1;
  if (this.GetHashCode() == other.GetHashCode()) return 0;
  return 1;
}

我希望在简化问题时我没有犯过任何错误。我认为主要问题是:

  1. 如何允许我的自定义对象具有可比性,并在SimplifiedClass中定义相等性,它依赖于子类的比较(例如列表中的Person,如List<Person>)。我认为应该用其他东西替换Enumerable.SequenceEqual,但我不知道是什么。
  2. 我的UnitTest中CollectionAssert.AreEqual是正确的方法吗?

1 个答案:

答案 0 :(得分:5)

Equals上的

List<T>只会检查列表本身之间的引用相等性,它不会尝试查看列表中的项目。正如你所说,你不想使用SequenceEqual因为你不关心订购。在这种情况下,您应该使用CollectionAssert.AreEquivalent,它的行为与Enumerable.SequenceEqual类似,但它并不关心两个集合的顺序。

对于可以在代码中使用的更通用的方法,它会稍微复杂一些,这是Microsoft在其assert方法中所做的重新实现的版本。

public static class Helpers
{
    public static bool IsEquivalent(this ICollection source, ICollection target)
    {
        //These 4 checks are just "shortcuts" so we may be able to return early with a result
        // without having to do all the work of comparing every member.
        if (source == null != (target == null))
            return false; //If one is null and one is not, return false immediately.
        if (object.ReferenceEquals((object)source, (object)target) || source == null)
            return true; //If both point to the same reference or both are null (We validated that both are true or both are false last if statement) return true;
        if (source.Count != target.Count)
            return false; //If the counts are different return false;
        if (source.Count == 0)
            return true; //If the count is 0 there is nothing to compare, return true. (We validated both counts are the same last if statement).

        int nullCount1;
        int nullCount2;

        //Count up the duplicates we see of each element.
        Dictionary<object, int> elementCounts1 = GetElementCounts(source, out nullCount1);
        Dictionary<object, int> elementCounts2 = GetElementCounts(target, out nullCount2);

        //It checks the total number of null items in the collection.
        if (nullCount2 != nullCount1)
        {
            //The count of nulls was different, return false.
            return false;
        }
        else
        {
            //Go through each key and check that the duplicate count is the same for 
            // both dictionaries.
            foreach (object key in elementCounts1.Keys)
            {
                int sourceCount;
                int targetCount;
                elementCounts1.TryGetValue(key, out sourceCount);
                elementCounts2.TryGetValue(key, out targetCount);
                if (sourceCount != targetCount)
                {
                    //Count of duplicates for a element where different, return false.
                    return false;
                }
            }

            //All elements matched, return true.
            return true;
        }
    }

    //Builds the dictionary out of the collection, this may be re-writeable to a ".GroupBy(" but I did not take the time to do it.
    private static Dictionary<object, int> GetElementCounts(ICollection collection, out int nullCount)
    {
        Dictionary<object, int> dictionary = new Dictionary<object, int>();
        nullCount = 0;
        foreach (object key in (IEnumerable)collection)
        {
            if (key == null)
            {
                ++nullCount;
            }
            else
            {
                int num;
                dictionary.TryGetValue(key, out num);
                ++num;
                dictionary[key] = num;
            }
        }
        return dictionary;
    }
}

它的作用是从两个集合中创建一个字典,计算重复项并将其存储为值。然后它比较两个字典以确保重复计数匹配双方。这可以让您知道{1, 2, 2, 3}{1, 2, 3, 3}不相等Enumerable.Execpt会告诉您他们在哪里。