139 votes

Comparer l'égalité entre deux objets dans NUnit

J'essaie d'affirmer qu'un objet est "égal" à un autre objet.

Les objets sont juste des instances d'une classe avec un tas de propriétés publiques. Existe-t-il un moyen simple pour que NUnit vérifie l'égalité sur la base des propriétés ?

C'est ma solution actuelle mais je pense qu'il y a peut-être quelque chose de mieux :

Assert.AreEqual(LeftObject.Property1, RightObject.Property1)
Assert.AreEqual(LeftObject.Property2, RightObject.Property2)
Assert.AreEqual(LeftObject.Property3, RightObject.Property3)
...
Assert.AreEqual(LeftObject.PropertyN, RightObject.PropertyN)

Ce que je cherche à faire serait dans le même esprit que la CollectionEquivalentConstraint dans laquelle NUnit vérifie que le contenu de deux collections est identique.

130voto

Max Wikström Points 802

Ne remplacez pas Equals juste à des fins de test. C'est fastidieux et cela affecte la logique du domaine. Au lieu de cela,

Utiliser JSON pour comparer les données de l'objet

Pas de logique supplémentaire sur vos objets. Pas de tâches supplémentaires pour les tests.

Il suffit d'utiliser cette méthode simple :

public static void AreEqualByJson(object expected, object actual)
{
    var serializer = new System.Web.Script.Serialization.JavaScriptSerializer();
    var expectedJson = serializer.Serialize(expected);
    var actualJson = serializer.Serialize(actual);
    Assert.AreEqual(expectedJson, actualJson);
}

Cela semble fonctionner parfaitement. Les informations sur les résultats de l'exécuteur de test afficheront la comparaison de la chaîne JSON (le graphique d'objets) incluse afin que vous puissiez voir directement ce qui ne va pas.

A noter également ! Si vous avez des objets complexes plus grands et que vous voulez simplement en comparer des parties, vous pouvez ( utiliser LINQ pour les données de séquence ) créer des objets anonymes à utiliser avec la méthode ci-dessus.

public void SomeTest()
{
    var expect = new { PropA = 12, PropB = 14 };
    var sut = loc.Resolve<SomeSvc>();
    var bigObjectResult = sut.Execute(); // This will return a big object with loads of properties 
    AssExt.AreEqualByJson(expect, new { bigObjectResult.PropA, bigObjectResult.PropB });
}

1 votes

C'est une excellente façon de tester, surtout si vous avez affaire à JSON de toute façon (par exemple, en utilisant un client typé pour accéder à un service web). Cette réponse devrait être beaucoup plus élevée.

0 votes

Votre solution a très bien fonctionné pour moi, merci Max, j'ai essayé d'ajouter ma propre version dans les commentaires mais il n'y avait pas assez d'espace donc je l'ai soumis comme une réponse séparée.

0 votes

C'est une décision vraiment bonne et simple. Je l'apprécie vraiment. Mais il ne fonctionne pas quand il y a la propriété ICollection dans l'objet.

121voto

Juanma Points 2648

Si vous ne pouvez pas surcharger Equals pour une raison quelconque, vous pouvez construire une méthode d'aide qui itère à travers les propriétés publiques par réflexion et assert chaque propriété. Quelque chose comme ceci :

public static class AssertEx
{
    public static void PropertyValuesAreEquals(object actual, object expected)
    {
        PropertyInfo[] properties = expected.GetType().GetProperties();
        foreach (PropertyInfo property in properties)
        {
            object expectedValue = property.GetValue(expected, null);
            object actualValue = property.GetValue(actual, null);

            if (actualValue is IList)
                AssertListsAreEquals(property, (IList)actualValue, (IList)expectedValue);
            else if (!Equals(expectedValue, actualValue))
                Assert.Fail("Property {0}.{1} does not match. Expected: {2} but was: {3}", property.DeclaringType.Name, property.Name, expectedValue, actualValue);
        }
    }

    private static void AssertListsAreEquals(PropertyInfo property, IList actualList, IList expectedList)
    {
        if (actualList.Count != expectedList.Count)
            Assert.Fail("Property {0}.{1} does not match. Expected IList containing {2} elements but was IList containing {3} elements", property.PropertyType.Name, property.Name, expectedList.Count, actualList.Count);

        for (int i = 0; i < actualList.Count; i++)
            if (!Equals(actualList[i], expectedList[i]))
                Assert.Fail("Property {0}.{1} does not match. Expected IList with element {1} equals to {2} but was IList with element {1} equals to {3}", property.PropertyType.Name, property.Name, expectedList[i], actualList[i]);
    }
}

0 votes

@wesley : ce n'est pas vrai. Méthode Type.GetProperties : Retourne toutes les propriétés publiques du Type courant. Voir msdn.microsoft.com/fr/us/library/aky14axb.aspx

4 votes

Merci. cependant, j'ai dû intervertir l'ordre des paramètres réels et attendus puisque la conversion est que l'attendu est un paramètre avant le réel.

0 votes

C'est une meilleure approche IMHO, Equal & HashCode overrides ne devraient pas avoir à être basés sur la comparaison de chaque champ et de plus c'est très fastidieux à faire à travers chaque objet. Bon travail !

101voto

dkl Points 1031

Essayez FluentAssertions bibliothèque :

dto.Should().BeEquivalentTo(customer) 

Il peut également être installé à l'aide de NuGet.

20 votes

ShouldHave a été déprécié, il faut donc utiliser dto.ShouldBeEquivalentTo( customer ) ; à la place.

3 votes

C'est la meilleure réponse pour cette raison .

0 votes

ShouldBeEquivalent est bogué :(

55voto

Lasse V. Karlsen Points 148037

Remplacez .Equals pour votre objet et dans le test unitaire vous pouvez alors simplement faire ceci :

Assert.AreEqual(LeftObject, RightObject);

Bien sûr, cela pourrait signifier que vous déplacez toutes les comparaisons individuelles vers la méthode .Equals, mais cela vous permettrait de réutiliser cette implémentation pour de multiples tests, et cela a probablement du sens si les objets doivent être capables de se comparer à leurs frères et sœurs de toute façon.

2 votes

Merci, lassevk. Cela a marché pour moi ! J'ai implémenté .Equals en suivant les instructions données ici : msdn.microsoft.com/fr/us/library/336aedhh(VS.80).aspx

14 votes

Et GetHashCode(), évidemment ;-p

1 votes

Une mise en garde importante : si votre objet implémente également la fonction IEnumerable elle sera comparée comme une collection sans tenir compte des implémentations de l'option Equals parce que NUnit donne IEnumerable une priorité plus élevée. Voir le NUnitEqualityComparer.AreEqual pour plus de détails. Vous pouvez remplacer le comparateur en utilisant l'une des méthodes de comparaison de la contrainte d'égalité. Using() méthodes. Même dans ce cas, il ne suffit pas d'implémenter la méthode non générique IEqualityComparer à cause de l'adaptateur que NUnit utilise.

36voto

Chris Yoxall Points 249

Je préfère ne pas remplacer Equals juste pour permettre les tests. N'oubliez pas que si vous surchargez Equals, vous devez également surcharger GetHashCode, sinon vous risquez d'obtenir des résultats inattendus si vous utilisez vos objets dans un dictionnaire par exemple.

J'aime l'approche de réflexion ci-dessus, car elle tient compte de l'ajout de propriétés à l'avenir.

Cependant, pour une solution simple et rapide, il est souvent plus facile de créer une méthode d'aide qui teste si les objets sont égaux, ou d'implémenter IEqualityComparer dans une classe que vous gardez privée pour vos tests. Lorsque vous utilisez la solution IEqualityComparer, vous n'avez pas besoin de vous soucier de l'implémentation de GetHashCode. Par exemple :

// Sample class.  This would be in your main assembly.
class Person
{
    public string Name { get; set; }
    public int Age { get; set; }
}

// Unit tests
[TestFixture]
public class PersonTests
{
    private class PersonComparer : IEqualityComparer<Person>
    {
        public bool Equals(Person x, Person y)
        {
            if (x == null && y == null)
            {
                return true;
            }

            if (x == null || y == null)
            {
                return false;
            }

            return (x.Name == y.Name) && (x.Age == y.Age);
        }

        public int GetHashCode(Person obj)
        {
            throw new NotImplementedException();
        }
    }

    [Test]
    public void Test_PersonComparer()
    {
        Person p1 = new Person { Name = "Tom", Age = 20 }; // Control data

        Person p2 = new Person { Name = "Tom", Age = 20 }; // Same as control
        Person p3 = new Person { Name = "Tom", Age = 30 }; // Different age
        Person p4 = new Person { Name = "Bob", Age = 20 }; // Different name.

        Assert.IsTrue(new PersonComparer().Equals(p1, p2), "People have same values");
        Assert.IsFalse(new PersonComparer().Equals(p1, p3), "People have different ages.");
        Assert.IsFalse(new PersonComparer().Equals(p1, p4), "People have different names.");
    }
}

0 votes

La fonction equals ne gère pas les valeurs nulles. J'ajouterais ce qui suit avant votre instruction de retour dans la méthode equals. if (x == null && y == null) { return true ; } if (x == null || y == null) { return false ; } J'ai modifié la question pour ajouter la prise en charge de null.

0 votes

Cela ne fonctionne pas pour moi avec throw new NotImplementedException() ; dans le GetHashCode. Pourquoi ai-je besoin de cette fonction dans l'IEqualityComparer de toute façon ?

Prograide.com

Prograide est une communauté de développeurs qui cherche à élargir la connaissance de la programmation au-delà de l'anglais.
Pour cela nous avons les plus grands doutes résolus en français et vous pouvez aussi poser vos propres questions ou résoudre celles des autres.

Powered by:

X