2 votes

Comment puis-je utiliser un faux lorsque le code valide les types qu'il reçoit

Je veux tester le code suivant :

public IEnumerable> ValidateAll()
{
    //...faire quelque chose
    var invalidFacts = GetInvalidFacts();
    //...faire quelque chose

    return duplicateFacts.Concat(invalidFacts);
}

private IEnumerable> GetInvalidFacts()
{
    var invalidFacts = Facts.Select(fact =>
    {
        try
        {
            fact.Validate();
            return new KeyValuePair(fact, null);
        }
        catch (FormatException e)
        {
            return new KeyValuePair(fact, e);
        }
        catch (Exception e)
        {
            return new KeyValuePair(fact, e);
        }
    }).Where(kv => kv.Value != null).ToList();

    return invalidFacts;
}

Essentiellement, l'objectif du test est de vérifier que tous les objets qui existent dans l'ensemble "Facts" appelleront leur méthode Validate. Comme je ne souhaite pas tester le code à l'intérieur de ces objets, car il existe déjà de nombreux tests qui le font, je veux injecter une liste de fausses données. J'utilise MOQ pour créer les fausses données.

Ainsi, mon test unitaire ressemble à ceci :

[TestMethod]
public void ValidateAll_ValidateMethodIsInvokedOnAllFacts_WhenCalled()
{
    var anyFactOne = new Mock(); //Fact est une classe abstraite.

    anyFactOne.Setup(f => f.Validate());

    var dataWarehouseFacts = new DataWarehouseFacts { Facts = new Fact[] { anyFactOne.Object, FactGenerationHelper.GenerateRandomFact() } };

    dataWarehouseFacts.ValidateAll();
} 

Maintenant, je reçois une exception car le code valide en réalité le type de Faits qui peut être injecté dans la classe DataWarehouseFacts, comme suit :

public IEnumerable Facts
{
    get
    {
        .....
    }
    set
    {
        var allowedTypes = new [] 
        { 
            typeof(ProductUnitFact), 
            typeof(FailureFact), 
            typeof(DefectFact), 
            typeof(ProcessRunFact), 
            typeof(CustomerFact),
            typeof(ProductUnitReturnFact),
            typeof(ShipmentFact),
            typeof(EventFact), 
            typeof(ComponentUnitFact),
            typeof(SourceDetails) 
        };

    if(!value.All(rootFact => allowedTypes.Contains(rootFact.GetType())))
       throw new Exception ("DataWarehouseFacts ne peut être défini qu'avec des faits principaux");

    ProductUnitFacts = value.OfType().ToList();
    FailureFacts = value.OfType().ToList();
    DefectFacts = value.OfType().ToList();
    ProcessRunFacts = value.OfType().ToList();
    CustomerFacts = value.OfType().ToList();
    ProductUnitReturnFacts = value.OfType().ToList();
    ShipmentFacts = value.OfType().ToList();
    EventFacts = value.OfType().ToList();
    ComponentUnitFacts = value.OfType().ToList();
    SourceDetails = value.OfType().Single();
    }
}

Quel serait le meilleur moyen de contourner cette validation ?

Merci.

2voto

ladenedge Points 4986

Les deux méthodes évidentes qui viennent à l'esprit sont :

  1. Ajoutez Fact à votre liste de types autorisés.
  2. Moq l'un de vos types de fait autorisés plutôt que la classe de base Fact elle-même. (Je suppose que votre méthode Validate() est modifiable.)

Une autre option légèrement plus compliquée serait d'injecter votre liste de types autorisés au moment du test, en supposant que vous avez le contrôle sur la classe DataWarehouseFacts. Cela ressemblerait à ceci :

class DWF
{
    static IEnumerable defaultAllowedFacts = new Fact[] { ... }
    IEnumerable allowedFacts;

    public DWF() : this(defaultAllowedFacts) { ... }
    internal DWF(IEnumerable allowed)
    {
        // pour les tests seulement, peut-être
        this.allowedFacts = allowed;
    }
    ...
}

Ensuite, supprimez simplement cette partie var allowedTypes = new [] et utilisez plutôt this.allowedFacts.

1voto

sehe Points 123151

Je profiterais de Type.IsAssignableFrom

Par exemple, au lieu de dire

allowedTypes.Contains(v.GetType())

Je dirais

allowedTypes.Any(t => t.IsAssignableFrom(v.GetType()))

De cette manière, vous pouvez passer des sous-classes appropriées aussi bien que les types correspondants exacts. Peut-être, c'était ce que vous cherchiez avec la liste de types elle-même?

0voto

Sergio Romero Points 1525

Tout d'abord, je tiens à remercier à la fois ladenedge (j'ai donné un +1 à sa réponse) et sehe pour leurs réponses. Même si ce n'était pas exactement ce que je cherchais, ce sont des idées intéressantes à garder à l'esprit.

Je ne pouvais pas simplement ajouter la classe Fact à la liste des types autorisés car cela aurait ouvert la porte à de nombreuses classes qui ne devraient pas être autorisées; il y a environ 30 classes qui en héritent.

Alors, ce que j'ai fini par faire, c'était d'extraire le code de la partie définissant la propriété Facts dans leurs propres méthodes et d'en rendre une protégée et virtuelle, comme ceci :

public IEnumerable Facts
        {
            get
            {
                ...
            }
            set
            {
                ValidateReceived(value);
                ExtractFactTypesFrom(value.ToList());
            }
        }

        protected virtual void ValidateReceived(IEnumerable factTypes)
        {
            if (factTypes == null) throw new ArgumentNullException("factTypes");

            var allowedTypes = GetAllowedFactTypes();
            if (!factTypes.All(rootFact => allowedTypes.Contains(rootFact.GetType()))) throw new Exception("DataWarehouseFacts ne peut être configuré qu'avec des faits racines");
        }

        private IEnumerable GetAllowedFactTypes()
        {
            var allowedTypes = new[]
            {
                typeof (ProductUnitFact), 
                typeof (SequenceRunFact), 
                typeof (FailureFact), 
                typeof (DefectFact),
                typeof (ProcessRunFact), 
                typeof (CustomerFact), 
                typeof (ProductUnitReturnFact),
                typeof (ShipmentFact), 
                typeof (EventFact), 
                typeof (ComponentUnitFact), 
                typeof (SourceDetails)
            };

            return allowedTypes;
        }

        private void ExtractFactTypesFrom(List value)
        {
            ProductUnitFacts = value.OfType().ToList();
            FailureFacts = value.OfType().ToList();
            DefectFacts = value.OfType().ToList();
            ProcessRunFacts = value.OfType().ToList();
            SequenceRunFacts = value.OfType().ToList();
            CustomerFacts = value.OfType().ToList();
            ProductUnitReturnFacts = value.OfType().ToList();
            ShipmentFacts = value.OfType().ToList();
            EventFacts = value.OfType().ToList();
            ComponentUnitFacts = value.OfType().ToList();
            SourceDetails = value.OfType().Single();
        }

Ainsi, j'ai pu créer une classe DataWarehouseFactsForTests et remplacer la méthode ValidateReceived pour qu'elle ne fasse rien :

public class DataWarehouseFactsForTests : DataWarehouseFacts
{
    protected override void ValidateReceived(IEnumerable factTypes)
    {}
}

Ainsi, j'ai pu utiliser Moq pour créer les Faits et vérifier le code dans la méthode privée GetInvalidFacts. Par exemple :

[TestMethod]
public void ValidateAll_RetourneUnDictionnaireAvecUneFormatException_Lorsqu'UneDesValidationDeFaitsLèveUneFormatException()
{
    var anyFactOne = new Mock();
    var anyFactTwo = new Mock();
    var anyFactThree = new Mock();

    anyFactOne.Setup(f => f.Validate()).Throws(new FormatException());

    var dataWarehouseFacts = new DataWarehouseFactsForTests { Facts = new Fact[] { anyFactOne.Object, anyFactTwo.Object, anyFactThree.Object } };

    var result = dataWarehouseFacts.ValidateAll().ToList();

    anyFactOne.Verify(f => f.Validate(), Times.Exactly(1));
    anyFactTwo.Verify(f => f.Validate(), Times.Exactly(1));
    anyFactThree.Verify(f => f.Validate(), Times.Exactly(1));

    Assert.AreEqual(1, result.Count());
    Assert.AreEqual(typeof(FormatException), result.First().Value.GetType());
}

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