2010-01-12 34 views
38
public class Student 
{ 
    public string Name { get; set; } 
    public int ID { get; set; } 
} 

...Làm thế nào để so sánh hai đối tượng trong thử nghiệm đơn vị?

var st1 = new Student 
{ 
    ID = 20, 
    Name = "ligaoren", 
}; 

var st2 = new Student 
{ 
    ID = 20, 
    Name = "ligaoren", 
}; 

Assert.AreEqual<Student>(st1, st2);// How to Compare two object in Unit test? 

Làm thế nào để so sánh hai bộ sưu tập trong Unitest?

+0

các người hỏi gì? – tster

+0

Bạn có yêu cầu so sánh sự bình đẳng của các đối tượng và kiểm tra các đối tượng tương đương không? – Intrigue

Trả lời

0

Có thể bạn cần thêm public bool Equals(object o) vào lớp học.

8

Bạn nên cung cấp một override của Object.EqualsObject.GetHashCode:

public override bool Equals(object obj) { 
    Student other = obj as Student; 
    if(other == null) { 
     return false; 
    } 
    return (this.Name == other.Name) && (this.ID == other.ID); 
} 

public override int GetHashCode() { 
    return 33 * Name.GetHashCode() + ID.GetHashCode(); 
} 

Đối với kiểm tra nếu hai bộ sưu tập đều bình đẳng, sử dụng Enumerable.SequenceEqual:

// first and second are IEnumerable<T> 
Assert.IsTrue(first.SequenceEqual(second)); 

Lưu ý rằng bạn có thể cần phải sử dụng overload rằng chấp nhận một số IEqualityComparer<T>.

+0

Và GetHashCode() ... –

+0

@Michael Haren: Cảm ơn! Silly giám sát trên một phần của tôi. – jason

+3

Vấn đề với trọng số bằng là không phải lúc nào cũng đúng. Việc thực hiện được đăng ở đây ngụ ý ngữ nghĩa của Value Object, có thể đúng đối với các đối tượng giá trị DDD, nhưng không đúng đối với các thực thể DDD (để đặt tên một vài ví dụ chung). –

6

Đây là ràng buộc tùy chỉnh NUnit 2.4.6 mà chúng tôi sử dụng để so sánh các biểu đồ phức tạp. Nó hỗ trợ các bộ sưu tập nhúng, tham chiếu cha mẹ, thiết lập dung sai cho so sánh số, xác định tên trường để bỏ qua (thậm chí sâu bên trong cấu trúc phân cấp), và các kiểu trang trí luôn bị bỏ qua.

Tôi chắc chắn mã này có thể được điều chỉnh để được sử dụng bên ngoài NUnit, phần lớn mã không phụ thuộc vào NUnit.

Chúng tôi sử dụng điều này trong hàng nghìn bài kiểm tra đơn vị.

using System; 
using System.Collections; 
using System.Collections.Generic; 
using System.Reflection; 
using System.Text; 
using NUnit.Framework; 
using NUnit.Framework.Constraints; 

namespace Tests 
{ 
    public class ContentsEqualConstraint : Constraint 
    { 
     private readonly object expected; 
     private Constraint failedEquality; 
     private string expectedDescription; 
     private string actualDescription; 

     private readonly Stack<string> typePath = new Stack<string>(); 
     private string typePathExpanded; 

     private readonly HashSet<string> _ignoredNames = new HashSet<string>(); 
     private readonly HashSet<Type> _ignoredTypes = new HashSet<Type>(); 
     private readonly LinkedList<Type> _ignoredInterfaces = new LinkedList<Type>(); 
     private readonly LinkedList<string> _ignoredSuffixes = new LinkedList<string>(); 
     private readonly IDictionary<Type, Func<object, object, bool>> _predicates = new Dictionary<Type, Func<object, object, bool>>(); 

     private bool _withoutSort; 
     private int _maxRecursion = int.MaxValue; 

     private readonly HashSet<VisitedComparison> _visitedObjects = new HashSet<VisitedComparison>(); 

     private static readonly HashSet<string> _globallyIgnoredNames = new HashSet<string>(); 
     private static readonly HashSet<Type> _globallyIgnoredTypes = new HashSet<Type>(); 
     private static readonly LinkedList<Type> _globallyIgnoredInterfaces = new LinkedList<Type>(); 

     private static object _regionalTolerance; 

     public ContentsEqualConstraint(object expectedValue) 
     { 
      expected = expectedValue; 
     } 

     public ContentsEqualConstraint Comparing<T>(Func<T, T, bool> predicate) 
     { 
      Type t = typeof (T); 

      if (predicate == null) 
      { 
       _predicates.Remove(t); 
      } 
      else 
      { 
       _predicates[t] = (x, y) => predicate((T) x, (T) y); 
      } 
      return this; 
     } 

     public ContentsEqualConstraint Ignoring(string fieldName) 
     { 
      _ignoredNames.Add(fieldName); 
      return this; 
     } 

     public ContentsEqualConstraint Ignoring(Type fieldType) 
     { 
      if (fieldType.IsInterface) 
      { 
       _ignoredInterfaces.AddFirst(fieldType); 
      } 
      else 
      { 
       _ignoredTypes.Add(fieldType); 
      } 
      return this; 
     } 

     public ContentsEqualConstraint IgnoringSuffix(string suffix) 
     { 
      if (string.IsNullOrEmpty(suffix)) 
      { 
       throw new ArgumentNullException("suffix"); 
      } 
      _ignoredSuffixes.AddLast(suffix); 
      return this; 
     } 

     public ContentsEqualConstraint WithoutSort() 
     { 
      _withoutSort = true; 
      return this; 
     } 

     public ContentsEqualConstraint RecursingOnly(int levels) 
     { 
      _maxRecursion = levels; 
      return this; 
     } 

     public static void GlobalIgnore(string fieldName) 
     { 
      _globallyIgnoredNames.Add(fieldName); 
     } 

     public static void GlobalIgnore(Type fieldType) 
     { 
      if (fieldType.IsInterface) 
      { 
       _globallyIgnoredInterfaces.AddFirst(fieldType); 
      } 
      else 
      { 
       _globallyIgnoredTypes.Add(fieldType); 
      } 
     } 

     public static IDisposable RegionalIgnore(string fieldName) 
     { 
      return new RegionalIgnoreTracker(fieldName); 
     } 

     public static IDisposable RegionalIgnore(Type fieldType) 
     { 
      return new RegionalIgnoreTracker(fieldType); 
     } 

     public static IDisposable RegionalWithin(object tolerance) 
     { 
      return new RegionalWithinTracker(tolerance); 
     } 

     public override bool Matches(object actualValue) 
     { 
      typePathExpanded = null; 
      actual = actualValue; 
      return Matches(expected, actualValue); 
     } 

     private bool Matches(object expectedValue, object actualValue) 
     { 

      bool matches = true; 

      if (!MatchesNull(expectedValue, actualValue, ref matches)) 
      { 
       return matches; 
      } 
      // DatesEqualConstraint supports tolerance in dates but works as equal constraint for everything else 
      Constraint eq = new DatesEqualConstraint(expectedValue).Within(tolerance ?? _regionalTolerance); 
      if (eq.Matches(actualValue)) 
      { 
       return true; 
      } 

      if (MatchesVisited(expectedValue, actualValue, ref matches)) 
      { 
       if (MatchesDictionary(expectedValue, actualValue, ref matches) && 
        MatchesList(expectedValue, actualValue, ref matches) && 
        MatchesType(expectedValue, actualValue, ref matches) && 
        MatchesPredicate(expectedValue, actualValue, ref matches)) 
       { 
        MatchesFields(expectedValue, actualValue, eq, ref matches); 
       } 
      } 

      return matches; 
     } 

     private bool MatchesNull(object expectedValue, object actualValue, ref bool matches) 
     { 
      if (IsNullEquivalent(expectedValue)) 
      { 
       expectedValue = null; 
      } 

      if (IsNullEquivalent(actualValue)) 
      { 
       actualValue = null; 
      } 

      if (expectedValue == null && actualValue == null) 
      { 
       matches = true; 
       return false; 
      } 

      if (expectedValue == null) 
      { 
       expectedDescription = "null"; 
       actualDescription = "NOT null"; 
       matches = Failure; 
       return false; 
      } 

      if (actualValue == null) 
      { 
       expectedDescription = "not null"; 
       actualDescription = "null"; 
       matches = Failure; 
       return false; 
      } 

      return true; 
     } 

     private bool MatchesType(object expectedValue, object actualValue, ref bool matches) 
     { 
      Type expectedType = expectedValue.GetType(); 
      Type actualType = actualValue.GetType(); 

      if (expectedType != actualType) 
      { 
       try 
       { 
        Convert.ChangeType(actualValue, expectedType); 
       } 
       catch(InvalidCastException)    
       { 
        expectedDescription = expectedType.FullName; 
        actualDescription = actualType.FullName; 
        matches = Failure; 
        return false; 
       } 

      } 
      return true; 
     } 

     private bool MatchesPredicate(object expectedValue, object actualValue, ref bool matches) 
     { 
      Type t = expectedValue.GetType(); 
      Func<object, object, bool> predicate; 

      if (_predicates.TryGetValue(t, out predicate)) 
      { 
       matches = predicate(expectedValue, actualValue); 
       return false; 
      } 
      return true; 
     } 

     private bool MatchesVisited(object expectedValue, object actualValue, ref bool matches) 
     { 
      var c = new VisitedComparison(expectedValue, actualValue); 

      if (_visitedObjects.Contains(c)) 
      { 
       matches = true; 
       return false; 
      } 

      _visitedObjects.Add(c); 

      return true; 
     } 

     private bool MatchesDictionary(object expectedValue, object actualValue, ref bool matches) 
     { 
      if (expectedValue is IDictionary && actualValue is IDictionary) 
      { 
       var expectedDictionary = (IDictionary)expectedValue; 
       var actualDictionary = (IDictionary)actualValue; 

       if (expectedDictionary.Count != actualDictionary.Count) 
       { 
        expectedDescription = expectedDictionary.Count + " item dictionary"; 
        actualDescription = actualDictionary.Count + " item dictionary"; 
        matches = Failure; 
        return false; 
       } 

       foreach (DictionaryEntry expectedEntry in expectedDictionary) 
       { 
        if (!actualDictionary.Contains(expectedEntry.Key)) 
        { 
         expectedDescription = expectedEntry.Key + " exists"; 
         actualDescription = expectedEntry.Key + " does not exist"; 
         matches = Failure; 
         return false; 
        } 
        if (CanRecurseFurther) 
        { 
         typePath.Push(expectedEntry.Key.ToString()); 
         if (!Matches(expectedEntry.Value, actualDictionary[expectedEntry.Key])) 
         { 
          matches = Failure; 
          return false; 
         } 
         typePath.Pop(); 
        } 
       } 
       matches = true; 
       return false; 
      } 
      return true; 
     } 

     private bool MatchesList(object expectedValue, object actualValue, ref bool matches) 
     { 
      if (!(expectedValue is IList && actualValue is IList)) 
      { 
       return true; 
      } 

      var expectedList = (IList) expectedValue; 
      var actualList = (IList) actualValue; 

      if (!Matches(expectedList.Count, actualList.Count)) 
      { 
       matches = false; 
      } 
      else 
      { 
       if (CanRecurseFurther) 
       { 
        int max = expectedList.Count; 

        if (max != 0 && !_withoutSort) 
        { 
         SafeSort(expectedList); 
         SafeSort(actualList); 
        } 

        for (int i = 0; i < max; i++) 
        { 
         typePath.Push(i.ToString()); 

         if (!Matches(expectedList[i], actualList[i])) 
         { 
          matches = false; 
          return false; 
         } 
         typePath.Pop(); 
        } 
       } 
       matches = true; 
      } 
      return false; 
     } 

     private void MatchesFields(object expectedValue, object actualValue, Constraint equalConstraint, ref bool matches) 
     { 
      Type expectedType = expectedValue.GetType(); 

      FieldInfo[] fields = expectedType.GetFields(BindingFlags.Instance | BindingFlags.NonPublic); 

      // should have passed the EqualConstraint check 
      if (expectedType.IsPrimitive || 
       expectedType == typeof(string) || 
       expectedType == typeof(Guid) || 
       fields.Length == 0) 
      { 
       failedEquality = equalConstraint; 
       matches = Failure; 
       return; 
      } 

      if (expectedType == typeof(DateTime)) 
      { 
       var expectedDate = (DateTime)expectedValue; 
       var actualDate = (DateTime)actualValue; 

       if (Math.Abs((expectedDate - actualDate).TotalSeconds) > 3.0) 
       { 
        failedEquality = equalConstraint; 
        matches = Failure; 
        return; 
       } 
       matches = true; 
       return; 
      } 

      if (CanRecurseFurther) 
      { 
       while(true) 
       { 
        foreach (FieldInfo field in fields) 
        { 
         if (!Ignore(field)) 
         { 
          typePath.Push(field.Name); 
          if (!Matches(GetValue(field, expectedValue), GetValue(field, actualValue))) 
          { 
           matches = Failure; 
           return; 
          } 
          typePath.Pop(); 
         } 
        } 
        expectedType = expectedType.BaseType; 
        if (expectedType == null) 
        { 
         break; 
        } 
        fields = expectedType.GetFields(BindingFlags.Instance | BindingFlags.NonPublic); 
       } 
      } 
      matches = true; 
      return; 
     } 

     private bool Ignore(FieldInfo field) 
     { 
      if (_ignoredNames.Contains(field.Name) || 
       _ignoredTypes.Contains(field.FieldType) || 
       _globallyIgnoredNames.Contains(field.Name) || 
       _globallyIgnoredTypes.Contains(field.FieldType) || 
       field.GetCustomAttributes(typeof (IgnoreContentsAttribute), false).Length != 0) 
      { 
       return true; 
      } 

      foreach(string ignoreSuffix in _ignoredSuffixes) 
      { 
       if (field.Name.EndsWith(ignoreSuffix)) 
       { 
        return true; 
       } 
      } 

      foreach (Type ignoredInterface in _ignoredInterfaces) 
      { 
       if (ignoredInterface.IsAssignableFrom(field.FieldType)) 
       { 
        return true; 
       } 
      } 
      return false; 
     } 

     private static bool Failure 
     { 
      get 
      { 
       return false; 
      } 
     } 

     private static bool IsNullEquivalent(object value) 
     { 
      return value == null || 
        value == DBNull.Value || 
        (value is int && (int) value == int.MinValue) || 
        (value is double && (double) value == double.MinValue) || 
        (value is DateTime && (DateTime) value == DateTime.MinValue) || 
        (value is Guid && (Guid) value == Guid.Empty) || 
        (value is IList && ((IList)value).Count == 0); 
     } 

     private static object GetValue(FieldInfo field, object source) 
     { 
      try 
      { 
       return field.GetValue(source); 
      } 
      catch(Exception ex) 
      { 
       return ex; 
      } 
     } 

     public override void WriteMessageTo(MessageWriter writer) 
     { 
      if (TypePath.Length != 0) 
      { 
       writer.WriteLine("Failure on " + TypePath); 
      } 

      if (failedEquality != null) 
      { 
       failedEquality.WriteMessageTo(writer); 
      } 
      else 
      { 
       base.WriteMessageTo(writer); 
      } 
     } 
     public override void WriteDescriptionTo(MessageWriter writer) 
     { 
      writer.Write(expectedDescription); 
     } 

     public override void WriteActualValueTo(MessageWriter writer) 
     { 
      writer.Write(actualDescription); 
     } 

     private string TypePath 
     { 
      get 
      { 
       if (typePathExpanded == null) 
       { 
        string[] p = typePath.ToArray(); 
        Array.Reverse(p); 
        var text = new StringBuilder(128); 
        bool isFirst = true; 
        foreach(string part in p) 
        { 
         if (isFirst) 
         { 
          text.Append(part); 
          isFirst = false; 
         } 
         else 
         { 
          int i; 
          if (int.TryParse(part, out i)) 
          { 
           text.Append("[" + part + "]"); 
          } 
          else 
          { 
           text.Append("." + part); 
          } 
         } 
        } 
        typePathExpanded = text.ToString(); 
       } 
       return typePathExpanded; 
      } 
     } 

     private bool CanRecurseFurther 
     { 
      get 
      { 
       return typePath.Count < _maxRecursion; 
      } 
     } 

     private static bool SafeSort(IList list) 
     { 
      if (list == null) 
      { 
       return false; 
      } 

      if (list.Count < 2) 
      { 
       return true; 
      } 

      try 
      { 
       object first = FirstNonNull(list) as IComparable; 
       if (first == null) 
       { 
        return false; 
       } 

       if (list is Array) 
       { 
        Array.Sort((Array)list); 
        return true; 
       } 
       return CallIfExists(list, "Sort"); 
      } 
      catch 
      { 
       return false; 
      } 
     } 

     private static object FirstNonNull(IEnumerable enumerable) 
     { 
      if (enumerable == null) 
      { 
       throw new ArgumentNullException("enumerable"); 
      } 
      foreach (object item in enumerable) 
      { 
       if (item != null) 
       { 
        return item; 
       } 
      } 
      return null; 
     } 

     private static bool CallIfExists(object instance, string method) 
     { 
      if (instance == null) 
      { 
       throw new ArgumentNullException("instance"); 
      } 
      if (String.IsNullOrEmpty(method)) 
      { 
       throw new ArgumentNullException("method"); 
      } 
      Type target = instance.GetType(); 
      MethodInfo m = target.GetMethod(method, new Type[0]); 
      if (m != null) 
      { 
       m.Invoke(instance, null); 
       return true; 
      } 
      return false; 
     } 

     #region VisitedComparison Helper 

     private class VisitedComparison 
     { 
      private readonly object _expected; 
      private readonly object _actual; 

      public VisitedComparison(object expected, object actual) 
      { 
       _expected = expected; 
       _actual = actual; 
      } 

      public override int GetHashCode() 
      { 
       return GetHashCode(_expected)^GetHashCode(_actual); 
      } 

      private static int GetHashCode(object o) 
      { 
       if (o == null) 
       { 
        return 0; 
       } 
       return o.GetHashCode(); 
      } 

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

       if (obj.GetType() != typeof(VisitedComparison)) 
       { 
        return false; 
       } 

       var other = (VisitedComparison) obj; 
       return _expected == other._expected && 
         _actual == other._actual; 
      } 
     } 

     #endregion 

     #region RegionalIgnoreTracker Helper 

     private class RegionalIgnoreTracker : IDisposable 
     { 
      private readonly string _fieldName; 
      private readonly Type _fieldType; 

      public RegionalIgnoreTracker(string fieldName) 
      { 
       if (!_globallyIgnoredNames.Add(fieldName)) 
       { 
        _globallyIgnoredNames.Add(fieldName); 
        _fieldName = fieldName; 
       } 
      } 

      public RegionalIgnoreTracker(Type fieldType) 
      { 
       if (!_globallyIgnoredTypes.Add(fieldType)) 
       { 
        _globallyIgnoredTypes.Add(fieldType); 
        _fieldType = fieldType; 
       } 
      } 

      public void Dispose() 
      { 
       if (_fieldName != null) 
       { 
        _globallyIgnoredNames.Remove(_fieldName); 
       } 
       if (_fieldType != null) 
       { 
        _globallyIgnoredTypes.Remove(_fieldType); 
       } 
      } 
     } 

     #endregion 

     #region RegionalWithinTracker Helper 

     private class RegionalWithinTracker : IDisposable 
     { 
      public RegionalWithinTracker(object tolerance) 
      { 
       _regionalTolerance = tolerance; 
      } 

      public void Dispose() 
      { 
       _regionalTolerance = null; 
      } 
     } 

     #endregion 

     #region IgnoreContentsAttribute 

     [AttributeUsage(AttributeTargets.Field)] 
     public sealed class IgnoreContentsAttribute : Attribute 
     { 
     } 

     #endregion 
    } 
    public class DatesEqualConstraint : EqualConstraint 
    { 
     private readonly object _expected; 

     public DatesEqualConstraint(object expectedValue) : base(expectedValue) 
     { 
      _expected = expectedValue; 
     } 

     public override bool Matches(object actualValue) 
     { 
      if (tolerance != null && tolerance is TimeSpan) 
      { 
       if (_expected is DateTime && actualValue is DateTime) 
       { 
        var expectedDate = (DateTime) _expected; 
        var actualDate = (DateTime) actualValue; 
        var toleranceSpan = (TimeSpan) tolerance; 

        if ((actualDate - expectedDate).Duration() <= toleranceSpan) 
        { 
         return true; 
        } 
       } 
       tolerance = null; 
      } 
      return base.Matches(actualValue); 
     } 
    } 
} 
+1

Chỉ cần sao chép tất cả điều này vào một lớp mới trong VisualStudio10, 'tolerance' không được định nghĩa. Điều đó đã được gỡ bỏ trong bản phát hành Nunit mới hơn hay một cái gì đó tương tự? – TankorSmash

+0

@TankorSmash, vâng, họ thay đổi API cho NUnit rất nhiều. Nếu bạn hoàn toàn sử dụng các ràng buộc tích hợp mà bạn thường không chú ý, nhưng khi bạn tạo các tùy chỉnh, nó sẽ ảnh hưởng đến các tùy chỉnh gần như mọi bản phát hành. –

45

Điều bạn đang tìm kiếm là gì trong số xUnit Test Patterns được gọi là Test-Specific Equality.

Mặc dù đôi khi bạn có thể chọn ghi đè phương thức Bằng, điều này có thể dẫn đến Equality Pollution vì việc triển khai bạn cần cho thử nghiệm có thể không đúng đối với loại nói chung.

Ví dụ, Domain-Driven Design phân biệt giữa EntitiesValue Objects, và những người có ngữ nghĩa bình đẳng rất khác nhau.

Trong trường hợp này, bạn có thể viết so sánh tùy chỉnh cho loại được đề cập.

Nếu bạn cảm thấy mệt mỏi khi làm điều này, lớp học Likeness của AutoFixture cung cấp mục đích chung cho mục đích thử nghiệm cụ thể. Với lớp Học sinh của bạn, điều này sẽ cho phép bạn viết một bài kiểm tra như thế này:

[TestMethod] 
public void VerifyThatStudentAreEqual() 
{ 
    Student st1 = new Student(); 
    st1.ID = 20; 
    st1.Name = "ligaoren"; 

    Student st2 = new Student(); 
    st2.ID = 20; 
    st2.Name = "ligaoren"; 

    var expectedStudent = new Likeness<Student, Student>(st1); 

    Assert.AreEqual(expectedStudent, st2); 
} 

Điều này không yêu cầu bạn ghi đè Bằng bình đẳng cho sinh viên.

Tính tương thích thực hiện so sánh ngữ nghĩa, do đó, nó cũng có thể so sánh hai loại khác nhau miễn là chúng tương tự ngữ nghĩa.

+0

Tôi chưa bao giờ nghe nói về AutoFixture trước đây, đó là một dự án hấp dẫn! Đối với ô nhiễm bình đẳng, tôi thấy quan điểm của bạn, nhưng theo quan điểm của tôi, chính xác vì sự bình đẳng có thể mơ hồ, nó đáng giá (đau đớn) rõ ràng trong các bài kiểm tra. – Mathias

+1

Có, nhưng bạn vẫn cần phải cân bằng nó với khả năng sử dụng và bảo trì, và so sánh hai đồ thị sâu cho bình đẳng là quá đau đớn cho thị hiếu của tôi. Điều tốt đẹp về Likeness là sử dụng nó rất rõ ràng, vẫn rất nhẹ khi bạn biết nó là gì. –

+2

Tôi đã tìm thấy bài viết trên blog @MarkSeemann về [sử dụng Likeness] (http://blog.ploeh.dk/2010/06/29/IntroducingAutoFixtureLikeness.aspx) rất hữu ích. – Boggin

3

http://www.infoq.com/articles/Equality-Overloading-DotNET

Bài viết này có thể hữu ích, tôi giải quyết vấn đề này chỉ sử dụng refcetion đổ tất cả đệ ra; Sau đó, chúng ta chỉ cần so sánh hai chuỗi.

Mã đây:

/// <summary> 
    /// output all properties and values of obj 
    /// </summary> 
    /// <param name="obj"></param> 
    /// <param name="separator">default as ";"</param> 
    /// <returns>properties and values of obj,with specified separator </returns> 
    /// <Author>ligaoren</Author> 
    public static string Dump(object obj, string separator) 
    { 
     try 
     { 
      if (obj == null) 
      { 
       return string.Empty; 
      } 
      if (string.IsNullOrEmpty(separator)) 
      { 
       separator = ";"; 
      } 
      Type t = obj.GetType(); 
      StringBuilder info = new StringBuilder(t.Name).Append(" Values : "); 
      foreach (PropertyInfo item in t.GetProperties()) 
      { 
       object value = t.GetProperty(item.Name).GetValue(obj, null); 
       info.AppendFormat("[{0}:{1}]{2}", item.Name, value, separator); 
      } 
      return info.ToString(); 
     } 
     catch (Exception ex) 
     { 
      log.Error("Dump Exception", ex); 
      return string.Empty; 
     } 
    } 
7

Nó trông có vẻ thích chân dung AutoFixture là những gì tôi cần cho vấn đề này (nhờ Đánh dấu Seeman) tuy nhiên nó không hỗ trợ so sánh các yếu tố thu cho sự giống nhau (có một vài vấn đề mở về vấn đề này nhưng chúng chưa được giải quyết).

tôi thấy CompareObjects bởi Kellerman Software hiện các trick:

http://comparenetobjects.codeplex.com/

+0

Có lẽ [câu trả lời này] (http://stackoverflow.com/questions/11718761/applying-autofixture-semanticcomparison-oflikeness-to-sequences-collections/11719316#11719316) có thể hữu ích khi so sánh các mục trong bộ sưu tập. Nó sử dụng tính năng Likeness của [AutoFixture] (https://github.com/AutoFixture/AutoFixture) cũng thể hiện khả năng proxy nhẹ của nó. –

2

Bạn cũng có thể sử dụng NFluent với cú pháp này để sâu so sánh hai đối tượng mà không cần thực hiện bình đẳng cho các đối tượng của bạn. NFluent là một thư viện cố gắng đơn giản hóa việc viết mã kiểm tra có thể đọc được.

Check.That(actual).IsDeepEqualTo(expected); 

Phương pháp này không có ngoại lệ chứa tất cả các khác biệt thay vì thất bại ở trường hợp đầu tiên. Tôi thấy tính năng này là một điểm cộng.

+0

Bạn làm thế nào với phiên bản mới nhất của NFluent? Phương thức 'IsDeepEqualTo' dường như không còn tồn tại! –

+0

Có vẻ như phương thức nên là 'HasFieldsWithSameValues' thay vì' IsDeepEqualTo' ...? –

0

Đây là những gì tôi làm:

public static void AreEqualXYZ_UsageExample() 
{ 
    AreEqualXYZ(actual: class1UnderTest, 
     expectedBoolExample: true, 
     class2Assert: class2 => Assert.IsNotNull(class2), 
     class3Assert: class3 => Assert.AreEqual(42, class3.AnswerToEverything)); 
} 

public static void AreEqualXYZ(Class1 actual, 
    bool expectedBoolExample, 
    Action<Class2> class2Assert, 
    Action<Class3> class3Assert) 
{ 
    Assert.AreEqual(actual.BoolExample, expectedBoolExample); 

    class2Assert(actual.Class2Property); 
    class3Assert(actual.Class3Property); 
} 

HTH ..

7

Nếu so sánh các thành viên cộng đồng là đủ cho use-case của bạn, chỉ cần giơ đối tượng của bạn thành JSON và so sánh các chuỗi kết quả:

var js = new JavaScriptSerializer(); 
Assert.AreEqual(js.Serialize(st1), js.Serialize(st2)); 

JavaScriptSerializer Class

Ưu

  • Yêu cầu mã số tối thiểu, không nỗ lực, và không có thiết lập sơ bộ
  • Xử lý cấu trúc phức tạp với các đối tượng lồng nhau
  • Không làm ô nhiễm các loại của bạn với đơn vị mã kiểm tra cụ thể, như Equals

Nhược điểm

  • Chỉ các thành viên công khai được tuần tự hóa mới được coi là (không cần chú thích thành viên của bạn, bạn gh)
  • Không xử lý tài liệu tham khảo tròn
1

Đánh dấu câu trả lời Seeman của bao gồm các vấn đề chung: đó là thử nghiệm bình đẳng là một mối quan tâm riêng biệt, vì thế mã nên được bên ngoài để các lớp riêng của mình. (Tôi chưa từng thấy "sự ô nhiễm bình đẳng" trước đây, nhưng điều đó). Ngoài ra, nó là một mối quan tâm được phân lập cho bạn dự án đơn vị kiểm tra. Thậm chí tốt hơn, nó là trong nhiều trường hợp một "vấn đề giải quyết": có bất kỳ số lượng các thư viện khẳng định có sẵn mà sẽ cho phép bạn kiểm tra bình đẳng trong bất kỳ số lượng các cách tùy ý.Ông đề nghị một, mặc dù có nhiều người đã nổi lên hoặc trở thành trưởng thành hơn nhiều trong những năm can thiệp.

Để kết thúc, hãy để tôi đề xuất Fluent Assertions. Nó có nhiều khả năng cho mọi phương thức xác nhận. Trong trường hợp này nó sẽ rất đơn giản:

st1.ShouldBeEquivalentTo(st2); 
-1
obj1.ToString().Equals(obj2.ToString()) 
0

Nếu bạn sử dụng NUnit bạn có thể sử dụng cú pháp này và chỉ định một IEqualityComparer đặc biệt cho kiểm tra:

[Test] 
public void CompareObjectsTest() 
{ 
    ClassType object1 = ...; 
    ClassType object2 = ...; 
    Assert.That(object1, Is.EqualTo(object2).Using(new MyComparer())); 
} 

private class MyComparer : IEqualityComparer<ClassType> 
{ 
    public bool Equals(ClassType x, ClassType y) 
    { 
     return .... 
    } 

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

Xem thêm ở đây: Equal Constraint (NUnit 2.4/2.5)

-1
  1. Xin chào trước hết hãy thêm dự án thử nghiệm của bạn Newtonsoft.Json với Nuget PM

    PM> Install-Package Newtonsoft.Json -version 10.0.3

  2. Sau đó thêm tập tin thử nghiệm

    sử dụng Newtonsoft.Json;

  3. Cách sử dụng:

    Assert.AreEqual (JsonConvert.SerializeObject (dự kiến), JsonConvert.SerializeObject (thực tế));

2

Tôi chỉ:

Assert.AreEqual(Newtonsoft.Json.JsonConvert.SerializeObject(object1), 
       Newtonsoft.Json.JsonConvert.SerializeObject(object2)); 
Các vấn đề liên quan