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

...¿Cómo comparar dos objetos en la prueba unitaria?

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? 

cómo comparar dos recogida en UniTest?

+0

Qué preguntas? – tster

+0

¿Está pidiendo comparar igualdad de objetos y probar que los objetos son equivalentes? – Intrigue

Respuesta

0

Quizás necesite agregar un public bool Equals(object o) a la clase.

8

Debe proporcionar una override de Object.Equals y Object.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(); 
} 

En cuanto a comprobar si dos colecciones son iguales, utilice Enumerable.SequenceEqual:

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

Tenga en cuenta que puede que tenga que utilizar el overload que acepta un IEqualityComparer<T>.

+0

y GetHashCode() ... –

+0

@Michael Haren: ¡Gracias! Tonta supervisión de mi parte. – jason

+3

El problema con anular Iguales es que no siempre es lo correcto. La implementación publicada aquí implica la semántica Value Object, que puede ser correcta para los objetos de valor DDD, pero no para las entidades DDD (por nombrar algunos ejemplos generales). –

6

Aquí hay una restricción personalizada NUnit 2.4.6 que utilizamos para comparar gráficos complejos. Admite colecciones integradas, referencias principales, configuración de tolerancia para comparaciones numéricas, identificación de nombres de campo para ignorar (incluso en lo más profundo de la jerarquía) y tipos de decoración que siempre se ignorarán.

Estoy seguro de que este código se puede adaptar para ser utilizado fuera de NUnit, la mayor parte del código no depende de NUnit.

Usamos esto en miles de pruebas unitarias.

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

Acabo de copiar todo esto en una nueva clase en VisualStudio10, 'tolerance' no está definido. ¿Ha sido eliminado en versiones más nuevas de Nunit o algo similar? – TankorSmash

+0

@TankorSmash, sí, cambian mucho la API para NUnit. Si está utilizando estrictamente restricciones integradas, por lo general no se da cuenta, pero cuando crea configuraciones personalizadas, afecta a las personalizadas en casi todas las versiones. –

45

Lo que se busca es lo que se llama en xUnit Test PatternsTest-Specific Equality.

Aunque a veces puede optar por anular el método Equals, esto puede llevar a Equality Pollution porque la implementación que necesita para la prueba puede no ser la correcta para el tipo en general.

Por ejemplo, Domain-Driven Design distingue entre Entidades y Objetos valor y, a los que tienen muy diferentes semántica de igualdad.

Cuando este es el caso, puede escribir una comparación personalizada para el tipo en cuestión.

Si se cansa de hacer esto, la clase de Me gusta de AutoFixture ofrece igualdad específica de prueba de propósito general. Con su clase del estudiante, esto permitirá escribir una prueba como esta:

[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); 
} 

Esto no requiere que para anular Igual en el estudiante.

La semejanza realiza una comparación semántica, por lo que también puede comparar dos tipos diferentes, siempre que sean semánticamente similares.

+0

Nunca antes había oído hablar de AutoFixture, ¡es un proyecto intrigante! En cuanto a la contaminación por igualdad, entiendo su punto, pero desde mi punto de vista, precisamente porque la igualdad puede ser ambigua, vale la pena (dolorosamente) ser explícita en las pruebas. – Mathias

+1

Sí, pero aún necesita equilibrarlo con la facilidad de uso y el mantenimiento, y comparar dos gráficos profundos para determinar la igualdad es demasiado doloroso para mi gusto. Lo bueno de Likeness es que usarlo es muy explícito, sigue siendo muy liviano una vez que sabes lo que es. –

+2

Encontré el artículo del blog @MarkSeemann en [usando Likeness] (http://blog.ploeh.dk/2010/06/29/IntroducingAutoFixtureLikeness.aspx) muy útil. – Boggin

3

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

Este artículo puede ser útil, puedo solucionar este problema simplemente usando refcetion volcar todas ellas presentadas a cabo; Entonces solo necesitamos comparar dos cadenas.

Código

aquí:

/// <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

Se veía le gusta semejanza de AutoFixture es lo que necesitaba para este problema (gracias Marcos Seeman) sin embargo no soporta la comparación de elementos de la colección de semejanza (hay un par de cuestiones abiertas sobre la materia pero no han sido resueltos).

He encontrado CompareObjects por Kellerman Software hace el truco:

http://comparenetobjects.codeplex.com/

+0

Quizás [esta respuesta] (http://stackoverflow.com/questions/11718761/applying-autofixture-semanticcomparison-oflikeness-to-sequences-collections/11719316#11719316) podría ser útil al comparar elementos en una colección. Utiliza la semejanza de [AutoFixture] (https://github.com/AutoFixture/AutoFixture) demostrando también que es una capacidad ligera de proxying. –

2

También puede utilizar NFluent con la siguiente sintaxis a lo profundo comparar dos objetos sin aplicar la igualdad de sus objetos. NFluent es una biblioteca que intenta simplificar la escritura de código de prueba legible.

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

Este método falla con una excepción que contiene todas las diferencias en lugar de fallar en la primera. Encuentro que esta característica es un plus.

+0

¿Cómo se hace esto con la última versión de NFluent? ¡El método 'IsDeepEqualTo' ya no parece existir! –

+0

Parece que el método debería ser 'HasFieldsWithSameValues' en lugar de' IsDeepEqualTo' ...? –

0

Esto es lo que hago:

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

Si la comparación de miembros del público es suficiente para su uso-caso, simplemente atascar sus objetos en JSON y comparar las cadenas resultantes:

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

JavaScriptSerializer Class

Pros

  • Requiere código mínimo, cero esfuerzo, y no hay configuración preliminar
  • manijas de estructuras complejas con objetos anidados
  • No contamina los tipos con el código de la prueba específica de la unidad, como Equals

Contras

  • Solo se pueden serializar, los miembros públicos se consideran (no es necesario que anoten a sus miembros, tú gh)
  • no maneja referencias circulares
1

Marcos respuesta de Seeman cubre la cuestión general: que la prueba de la igualdad es una preocupación por separado, por lo que, por tanto, el código debe ser externo a la propia clase. (No he visto "contaminación de igualdad" antes, pero eso). Además, es una preocupación que está aislada de su proyecto de prueba de unidad. Aún mejor, en muchos casos es un "problema resuelto": hay muchas bibliotecas de aserciones disponibles que le permitirán probar la igualdad de muchas maneras arbitrarias.Él sugiere uno, aunque hay muchos que han surgido o se han vuelto mucho más maduros en los años intermedios.

Para ese fin, permítanme sugerir Fluent Assertions. Tiene muchas capacidades para todo tipo de afirmaciones. En este caso, sería muy simple:

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

Si utiliza NUnit es posible que el uso de esta sintaxis y especificar un IEqualityComparer específicamente para la prueba:

[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(); 
    } 
} 

Ver también aquí: Equal Constraint (NUnit 2.4/2.5)

-1
  1. Hola, en primer lugar, agregue su proyecto de prueba Newtonsoft.Json con Nuget PM

    PM> Install-Package Newtonsoft.Json -Versión 10.0.3

  2. A continuación, añadir el archivo de prueba

    usando Newtonsoft.Json;

  3. Uso:

    Assert.AreEqual (JsonConvert.SerializeObject (esperada), JsonConvert.SerializeObject (real));

2

que acabo de hacer:

Assert.AreEqual(Newtonsoft.Json.JsonConvert.SerializeObject(object1), 
       Newtonsoft.Json.JsonConvert.SerializeObject(object2)); 
Cuestiones relacionadas