web-dev-qa-db-fra.com

Comment affirmer que deux listes contiennent des éléments avec les mêmes propriétés publiques dans NUnit?

Je veux affirmer que les éléments de deux listes contiennent les valeurs que je m'attendais, quelque chose comme:

var foundCollection = fooManager.LoadFoo();
var expectedCollection = new List<Foo>() 
{
    new Foo() { Bar = "a", Bar2 = "b" },
    new Foo() { Bar = "c", Bar2 = "d" }
};

//assert: I use AreEquivalent since the order does not matter
CollectionAssert.AreEquivalent(expectedCollection, foundCollection);

Cependant, le code ci-dessus ne fonctionnera pas (je suppose car .Equals () ne renvoie pas la valeur true pour différents objets ayant la même valeur). Dans mon test, je ne me soucie que des valeurs de la propriété publique, pas de la question de savoir si les objets sont égaux. Que puis-je faire pour faire mon affirmation? 

19
Louis Rhys

RÉACTION RÉPONSE

Il existe une surcharge CollectionAssert.AreEqual(IEnumerable, IEnumerable, IComparer) pour affirmer que deux collections contiennent les mêmes objets dans le même ordre, en utilisant une implémentation IComparer pour vérifier l'équivalence des objets.

Dans le scénario décrit ci-dessus, l'ordre n'est pas important. Cependant, pour gérer suffisamment la situation dans laquelle se trouvent plusieurs objets équivalents dans les deux collections, il est d'abord nécessaire de classer les objets de chaque collection et d'utiliser une comparaison individuelle pour s'assurer que le nombre d'objets équivalents est identique dans les deux collections.

Enumerable.OrderBy fournit une surcharge qui prend un argument IComparer<T>. Pour vous assurer que les deux collections sont triées dans le même ordre, il est plus ou moins nécessaire que les types des propriétés d'identification implémentent IComparable. Voici un exemple de classe de comparaison qui implémente les interfaces IComparer et IComparer<Foo>, et dans laquelle il est supposé que Bar a la priorité lors de la commande:

public class FooComparer : IComparer, IComparer<Foo>
{
    public int Compare(object x, object y)
    {
        var lhs = x as Foo;
        var rhs = y as Foo;
        if (lhs == null || rhs == null) throw new InvalidOperationException();
        return Compare(lhs, rhs);
    }

    public int Compare(Foo x, Foo y)
    {
        int temp;
        return (temp = x.Bar.CompareTo(y.Bar)) != 0 ? temp : x.Bar2.CompareTo(y.Bar2);
    }
}

Pour affirmer que les objets des deux collections sont identiques et viennent en nombres égaux (mais pas nécessairement dans le même ordre pour commencer), les lignes suivantes devraient faire l'affaire:

var comparer = new FooComparer();
CollectionAssert.AreEqual(
    expectedCollection.OrderBy(foo => foo, comparer), 
    foundCollection.OrderBy(foo => foo, comparer), comparer);    
20
Anders Gustafsson

Non, NUnit ne dispose pas d'un tel mécanisme à partir de l'état actuel. Vous devrez lancer votre propre logique d'assertion. Soit en tant que méthode distincte, soit en utilisant Has.All.Matches:

Assert.That(found, Has.All.Matches<Foo>(f => IsInExpected(f, expected)));

private bool IsInExpected(Foo item, IEnumerable<Foo> expected)
{
    var matchedItem = expected.FirstOrDefault(f => 
        f.Bar1 == item.Bar1 &&
        f.Bar2 == item.Bar2 &&
        f.Bar3 == item.Bar3
    );

    return matchedItem != null;
}

Bien entendu, cela suppose que vous connaissiez d’emblée toutes les propriétés pertinentes (sinon, IsInExpected devra recourir à la réflexion) et que l’ordre des éléments n’est pas pertinent.

(Et votre hypothèse était correcte, la collection de NUnit affirme utiliser des comparateurs par défaut pour les types, qui dans la plupart des cas sont définis par l'utilisateur comme étant la variable ReferenceEquals de l'objet)

6
k.m

Utiliser Has.All.Matches () fonctionne très bien pour comparer une collection found à la collection attendue . Cependant, il n'est pas nécessaire de définir le prédicat utilisé par Has.All.Matches () en tant que fonction distincte. Pour des comparaisons relativement simples, le prédicat peut être inclus dans l'expression lambda comme celle-ci.

Assert.That(found, Has.All.Matches<Foo>(f => 
    expected.Any(e =>
        f.Bar1 == e.Bar1 &&
        f.Bar2 == e.Bar2 &&
        f.Bar3= = e.Bar3)));

Maintenant, bien que cette affirmation garantisse que chaque entrée de la collection found existe également dans la collection attendue , elle ne prouve pas le contraire, à savoir que chaque entrée de la collection attendue est contenue dans la found collection. Donc, quand il est important de savoir que found et attend contenir sont sémantiquement équivalents (c'est-à-dire qu'ils contiennent les mêmes entrées sémantiquement équivalentes), nous devons ajouter une assertion supplémentaire. 

Le choix le plus simple consiste à ajouter ce qui suit.

Assert.AreEqual(found.Count() == expected.Count());

Pour ceux qui préfèrent un plus gros marteau, l'affirmation suivante pourrait être utilisée à la place.

Assert.That(expected, Has.All.Matches<Foo>(e => 
    found.Any(f =>
        e.Bar1 == f.Bar1 &&
        e.Bar2 == f.Bar2 &&
        e.Bar3= = f.Bar3)));

En utilisant la première assertion ci-dessus conjointement avec la deuxième (préférée) ou la troisième affirmation, nous avons maintenant prouvé que les deux collections sont sémantiquement identiques.

4
RickC

Avez-vous essayé quelque chose comme ça?

Assert.That(foundCollection, Is.EquivalentTo(expectedCollection))
3
Erwin

Pour effectuer des opérations d'équivilance sur des types complexes, vous devez implémenter IComaprable.

http://support.Microsoft.com/kb/320727

Vous pouvez également utiliser la réflexion récursive, ce qui est moins souhaitable.

1
Slappy

Une option consiste à écrire des contraintes personnalisées pour comparer les éléments. Voici un bel article sur le sujet: http://www.davidarno.org/2012/07/25/improving-nunit-custom-constraints-with-syntax-helpers/

1
Scroog1

J'avais un problème similaire. Afficher la liste des contributeurs, qui contient "commenters" et autres personnes ... Je souhaite obtenir tous les commentaires et en déduire les créateurs, mais je ne m'intéresse souvent qu'aux créateurs uniques. Si quelqu'un a créé 50 commentaires, je veux seulement que son nom apparaisse une fois. J'écris donc un test pour voir que les commentateurs sont dans le résultat de GetContributors (). 

Je me trompe peut-être, mais ce que je pense de votre après (ce que je recherchais quand j'ai trouvé ce post) est d'affirmer qu'il y a exactement un de chaque article dans une collection, trouvé dans une autre collection. 

J'ai résolu ça comme ça: 

Assert.IsTrue(commenters.All(c => actual.Count(p => p.Id == c.Id) == 1));

Si vous souhaitez également que la liste résultante ne contienne pas d'autres éléments que prévu, vous pouvez simplement comparer la longueur des listes. 

Assert.IsTrue(commenters.length == actual.Count());

J'espère que cela vous sera utile. Dans l'affirmative, je vous serais très reconnaissant de bien vouloir noter ma réponse. 

1
Dizzypointed

Code simple expliquant comment utiliser IComparer

using System.Collections;
using System.Collections.Generic;
using Microsoft.VisualStudio.TestTools.UnitTesting;

namespace CollectionAssert
{
    [TestClass]
    public class UnitTest1
    {
        [TestMethod]
        public void TestMethod1()
        {
            IComparer collectionComparer = new CollectionComparer();
            var expected = new List<SomeModel>{ new SomeModel { Name = "SomeOne", Age = 40}, new SomeModel{Name="SomeOther", Age = 50}};
            var actual = new List<SomeModel> { new SomeModel { Name = "SomeOne", Age = 40 }, new SomeModel { Name = "SomeOther", Age = 50 } };
            NUnit.Framework.CollectionAssert.AreEqual(expected, actual, collectionComparer);
        }
    }

    public class SomeModel
    {
        public string Name { get; set; }
        public int Age { get; set; }
    }

    public class CollectionComparer : IComparer, IComparer<SomeModel>
    {
        public int Compare(SomeModel x, SomeModel y)
        {
            if(x == null || y == null) return -1;
            return x.Age == y.Age && x.Name == y.Name ? 0 : -1;
        }

        public int Compare(object x, object y)
        {
            var modelX = x as SomeModel;
            var modelY = y as SomeModel;
            return Compare(modelX, modelY);
        }
    }
}
0
Shishira M J

Je recommande de ne pas utiliser de réflexion ou quelque chose de complexe, cela ajoute simplement plus de travail/maintenance.

Sérialiser l'objet (je recommande JSON) et chaîne de les comparer. Je ne sais pas pourquoi vous vous opposez à order by, mais je le recommande tout de même car cela enregistrera une comparaison personnalisée pour chaque type.

Et cela fonctionne automatiquement avec les objets de domaine qui changent.

Exemple (SharpTestsEx pour fluent)

using Newtonsoft.Json;
using SharpTestsEx;

JsonConvert.SerializeObject(actual).Should().Be.EqualTo(JsonConvert.SerializeObject(expected));

Vous pouvez l'écrire comme une simple extension et le rendre plus lisible.

   public static class CollectionAssertExtensions
    {
        public static void CollectionAreEqual<T>(this IEnumerable<T> actual, IEnumerable<T> expected)
        {
            JsonConvert.SerializeObject(actual).Should().Be.EqualTo(JsonConvert.SerializeObject(expected));
        }
    }

puis, en utilisant votre exemple, appelez-le comme suit:

var foundCollection = fooManager.LoadFoo();
var expectedCollection = new List<Foo>() 
{
    new Foo() { Bar = "a", Bar2 = "b" },
    new Foo() { Bar = "c", Bar2 = "d" }
};


foundCollection.CollectionAreEqual(foundCollection);

Vous recevrez un message d'assertion comme ceci:

...: "a", "Bar2": "b"}, {"Bar": "d", "Bar2": "d"}]

...: "a", "Bar2": "b"}, {"Bar": "c", "Bar2": "d"}

..._ ___ ___ ___ ___ ___ __^_ __ _ ​​

0
Choco Smith