UnitTesting List <T>自定义对象的List <S>自定义对象的相等性

cit*_*nas 1 c# ienumerable unit-testing iequalitycomparer

我正在写一个解析器的一些单元测试,我被困在比较两个List<T>地方T是一类我自己的,包含其他List<S>.

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

CollectionAssert.AreEqual(list1, list2, "failed");
Run Code Online (Sandbox Code Playgroud)

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

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();
        }
    }
}
Run Code Online (Sandbox Code Playgroud)

这个问题与C#比较列表与自定义对象强烈相关但忽略了顺序,但除了我将列表包装到另一个对象并使用上面的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();
    }
}
Run Code Online (Sandbox Code Playgroud)

之后我尝试实现了"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;
}
Run Code Online (Sandbox Code Playgroud)

我希望在简化我的问题时我没有犯错.我认为主要问题是:

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

Sco*_*ain 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;
    }
}
Run Code Online (Sandbox Code Playgroud)

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

  • +1 - 注意,基于代码显示2个列表的等价实际上是实际代码的一部分,而不是单元测试.确保不要在测试之外使用`CollectionAssert`(`Enumerable.Except`可能是要使用的那个). (2认同)