Autofixture: Demande : Ajouter une méthode de clonage à IFixture

Créé le 24 sept. 2020  ·  9Commentaires  ·  Source: AutoFixture/AutoFixture

introduction

J'aimerais que IFixture ait une méthode Clone ou quelque chose de similaire pour créer une copie de l'appareil.

Pour le moment, je peux créer une méthode d'extension pour faire ceci :

public static IFixture Clone( this IFixture fixture)
{
   var cloneFixture = new Fixture();

   cloneFixture.Behaviors.Clear();
   foreach ( var behavior in fixture.Behaviors)
   {
      cloneFixture.Behaviors.Add( behavior );
   }

   cloneFixture.Customizations.Clear();
   foreach ( var customization in fixture.Customizations )
   {
      cloneFixture.Customizations.Add( customization );
   }

   cloneFixture.OmitAutoProperties = fixture.OmitAutoProperties;
   cloneFixture.RepeatCount = fixture.RepeatCount;

   cloneFixture.ResidueCollectors.Clear();
   foreach ( var residueCollector in fixture.ResidueCollectors )
   {
      cloneFixture.ResidueCollectors.Add( residueCollector );
   }

   return cloneFixture;
}

Mais je pense qu'une telle capacité à faire une copie devrait être dans le projet lui-même.

Des détails

Mon scénario est que je construis quelques instances d'une classe avec beaucoup de paramètres dans un test. De plus, je veux qu'un paramètre de constructeur soit une certaine valeur pour une instance et une valeur de constructeur différente pour une instance différente. Il y a plusieurs manières de le faire ; la façon dont je l'ai fait est:

fixture.Customizations.Add( new FilteringSpecimenBuilder(
            new FixedBuilder( mediaId ),
            new ParameterSpecification( 
                typeof( ObjectId ), 
                "mediaId" ) ) );
var mediaVM = fixture.Build<MediaViewModel>()   
                     .With( mvm => mvm.ParentMixerId, mixerId )
                     .With( mvm => mvm.TrackId, trackId )
                     .Create();

_ = fixture.Customizations.Remove( fixture.Customizations.Last() );

//...

La raison de la suppression de la personnalisation est que j'ai essayé sans penser que la dernière personnalisation ajoutée aura peut-être une priorité plus élevée et sera utilisée ; mais ce n'était pas le cas.

Si j'essaie de simplifier cela avec une sorte de méthode d'extension comme ceci :

public static IFixture AddCustomization<T>( this IFixture fixture, T value, string name )
{
   fixture.Customizations.Add( new FilteringSpecimenBuilder(
         new FixedBuilder( value ),
         new ParameterSpecification(
            typeof( T ),
            name ) ) );
   return fixture;
}

Cela peut fonctionner à certains endroits mais pas à d'autres, car une fois l'objet construit, le luminaire contient une personnalisation que je ne souhaite plus et que je dois supprimer.

Je préférerais ne pas avoir de ligne pour supprimer la ou les personnalisations ajoutées. Donc, si à la place ma méthode d'extension a fait une copie de mon appareil auquel j'ajoute des modifications à la copie, alors mes créations fonctionnent comme prévu et l'appareil d'origine est intact.

La possibilité d'ajouter une personnalisation qui est automatiquement supprimée après la création de l'objet fonctionnerait également.

J'espère que tout cela a du sens.

Merci d'avoir pensé à mon problème :smiley:

feature request

Commentaire le plus utile

@ajorians J'étais sur le point de fermer le problème en raison de l'inactivité mais je ne pouvais pas passer devant ce gigantesque constructeur.
Je dois demander, avez-vous envisagé de changer votre conception?

En regardant votre constructeur, je peux voir plusieurs problèmes et solutions potentielles à votre problème.

Tout d'abord, vous mélangez les concepts d'entités nouvelles et injectables.
La solution la plus simple consiste à déplacer vos dépendances injectables (également appelées interfaces de service) vers les propriétés et à les injecter à l'aide de l'injection de propriétés.
Cela résoudra votre problème immédiat d'avoir à copier le Fixture et rendra le constructeur plus gérable.

Deuxièmement se penche sur la plus-injection de code odeur.
Cela signifie déplacer les services ensemble les plus couramment utilisés dans des abstractions distinctes.
Cela devrait aider votre conception à respecter le SRP et par extension, cela devrait réduire l'énorme quantité de configuration que vous devez faire en ce moment, dans vos tests.

Puisque la classe s'appelle ViewModel je suppose que vous avez une application MVVM. Il est probable que vous puissiez découpler davantage votre application en introduisant un modèle de messagerie. Peut-être un agrégateur d'événements. La plupart des frameworks MVVM les ont intégrés afin que vous n'ayez pas à faire beaucoup de travail de configuration pour en bénéficier.

Faites-moi savoir si cela aide.

Tous les 9 commentaires

@ajorians Bonne journée ! D'après ce que j'ai compris de votre exemple, il semble que nous ayons déjà ce dont vous avez besoin. La méthode de fixation Build() crée en fait une copie immuable du graphique interne, vous pouvez donc appliquer des personnalisations "une fois" au-dessus de l'AutoFixture sans les conserver.

Revoyez cette démo de terrain de jeu :

```c#
modèle de classe publique
{
public int Id { get; ensemble; }
chaîne publique Nom { get; ensemble; }
}

[Fait]
Démo publique vide()
{
var fixture = new Fixture();
luminaire.Personnaliser(c => c.Avec(m => m.Id, 42));

var value1 = fixture.Create<Model>();
Assert.Equal(42, value1.Id);
Assert.NotNull(value1.Name);

var value2 = fixture.Build<Model>()
    .With(m => m.Id, (int genInt) => genInt * 2 /* make it always even */)
    .Without(x => x.Name)
    .Create();
Assert.NotEqual(42, value2.Id);
Assert.Equal(value2.Id % 2, 0);
Assert.Null(value2.Name);

var value3 = fixture.Create<Model>();
Assert.Equal(42, value3.Id);
Assert.NotNull(value3.Name);

}
```

Faites-nous savoir si cela résout vos besoins. Si ce n'est pas le cas, veuillez décrire plus précisément pourquoi cela ne fonctionne pas pour vous, afin que nous puissions essayer de vous aider.

Merci!

Salut @zvirja ,

Bonne journée!

Si je peux; notre classe Model n'aurait pas de set et ne serait réglable que via le constructeur. Donc si vous deviez le remplacer par ceci :

public class Model
{
   public Model( int id, string name )
   {
      Id = id;
      Name = name;
   }

   public int Id { get; }
   public string Name { get; }
}

Désolé de ne pas l'avoir précisé à l'origine.

Donc, comme je comprends les choses, je dois ajouter des personnalisations afin de faire des paramètres de constructeur. Et ces personnalisations sont avant le Build() .

Si vous êtes intéressé, certaines des raisons pour lesquelles nous faisons beaucoup dans le constructeur et sans setters sont que nous pouvons utiliser le mot-clé readonly et être sûr que les variables membres sont définies et ne peuvent pas être désactivées au moment où une méthode est appelé.

J'espère que cela a plus de sens. Merci pour votre aide ! :souriant:

@ajorians Dois-je bien comprendre votre problème, que vous avez un modèle avec une grande quantité d'arguments d'entrée, vous souhaitez donc en personnaliser un seul, sans avoir à vous en soucier explicitement. Droit?

Sinon, vous pouvez toujours écrire quelque chose comme
c# var value2 = fixture.Build<Model>() .FromFactory((int id, string name) => new Model(id, name)) .Create();

Malheureusement, il n'évolue pas assez bien avec un nombre croissant de paramètres 😟

@ajorians Dois-je bien comprendre votre problème, que vous avez un modèle avec une grande quantité d'arguments d'entrée, vous souhaitez donc en personnaliser un seul, sans avoir à vous en soucier explicitement. Droit?

Oui, c'est exactement ça.

Malheureusement, il n'évolue pas assez bien avec un nombre croissant de paramètres 😟

Heureusement, Freeze fonctionne pour beaucoup de nos classes avec un nombre croissant de paramètres. Mais oui, la classe que j'avais en tête où j'utilise les personnalisations pour faire des paramètres de constructeur a actuellement plus de paramètres 30 :
image

Et augmentera probablement de 4 supplémentaires avant la fin de cette année.

Je comprends que cela peut être inhabituel.

Mais oui, j'ai une grande quantité d'arguments de constructeur d'entrée en croissance et j'aimerais en personnaliser un (ou quelques-uns) sans penser aux autres. Sans avoir ce type enregistré ou gelé pendant tout le test. Et de manière concise, l'ajout d'une personnalisation représente environ 5 lignes de code.

Je suis heureusement capable de faire tout cela ; mais la façon dont je l'ai fait impliquait de créer une méthode d'extension clone dans mon code côté client.

J'espère que tout cela a du sens. Merci d'avoir regardé ça avec moi :smiley:

@ajorians J'étais sur le point de fermer le problème en raison de l'inactivité mais je ne pouvais pas passer devant ce gigantesque constructeur.
Je dois demander, avez-vous envisagé de changer votre conception?

En regardant votre constructeur, je peux voir plusieurs problèmes et solutions potentielles à votre problème.

Tout d'abord, vous mélangez les concepts d'entités nouvelles et injectables.
La solution la plus simple consiste à déplacer vos dépendances injectables (également appelées interfaces de service) vers les propriétés et à les injecter à l'aide de l'injection de propriétés.
Cela résoudra votre problème immédiat d'avoir à copier le Fixture et rendra le constructeur plus gérable.

Deuxièmement se penche sur la plus-injection de code odeur.
Cela signifie déplacer les services ensemble les plus couramment utilisés dans des abstractions distinctes.
Cela devrait aider votre conception à respecter le SRP et par extension, cela devrait réduire l'énorme quantité de configuration que vous devez faire en ce moment, dans vos tests.

Puisque la classe s'appelle ViewModel je suppose que vous avez une application MVVM. Il est probable que vous puissiez découpler davantage votre application en introduisant un modèle de messagerie. Peut-être un agrégateur d'événements. La plupart des frameworks MVVM les ont intégrés afin que vous n'ayez pas à faire beaucoup de travail de configuration pour en bénéficier.

Faites-moi savoir si cela aide.

Salut @aivascu ,

@ajorians J'étais sur le point de fermer le problème en raison de l'inactivité

S'il y a quelque chose que je peux faire, faites-le moi savoir. Étant une demande de fonctionnalité que je peux voir jusqu'à ce qu'elle soit mise en œuvre, elle peut avoir une bonne quantité d'inactivité.

Je dois demander, avez-vous envisagé de changer votre conception?

Pour le moment, nous envisageons de devenir une application prisme . Cela aiderait avec certains des éléments que vous avez mentionnés. Mais malheureusement, je ne nous vois pas changer la philosophie consistant à simplement tout mettre dans le constructeur de telle sorte qu'il puisse être vérifié pour null une fois et être readonly et toujours être non- null pendant toute la durée de vie de la classe. Nous devons faire quelque chose. Mais je ne pense pas que cela va s'améliorer l'année prochaine.

Faites-moi savoir si cela aide.

Tout cela aide. Je ferai savoir à mon équipe que même les gens dans le monde pensent que nous devons envisager d'apporter des changements.

Mais si je peux demander, est-il raisonnable pour IFixture d'avoir une Clone ou une méthode similaire ? Ou permettre aux gens d'ajouter une personnalisation, puis pop la dernière personnalisation ajoutée ou revenir à l'état avant d'ajouter une personnalisation ? Je conviens qu'il est important de comprendre le scénario qui provoque ce comportement souhaité et que le scénario présente certainement de nombreux problèmes en premier lieu. Et je vois que vous ne voulez pas ajouter de complexité supplémentaire à moins qu'il n'y ait un bon cas d'utilisation. Eh bien, vous pouvez choisir ce qui se passe ensuite, mais si je peux vous aider, faites-le moi savoir.

Merci d'avoir lu et considéré :smiley:

@ajorians mon avis est que nous ne devrions pas ajouter un .Clone() méthode au Fixture classe. À mon avis, il ne devrait y avoir qu'un seul appareil de test à un moment donné pour un seul test.

Il y a eu quelques demandes pour implémenter une fonctionnalité de dégeler/éjecter/geler une fois dans le passé, mais personne n'a pu l'implémenter.
Je pense que la raison en est qu'il n'y a pas de moyen trivial d'identifier de manière fiable quel constructeur va retourner la valeur gelée ou quelle était la dernière valeur injectée.

Ce que vous pouvez faire, c'est implémenter un relais qui ignore les demandes après un certain nombre de résolutions réussies, comme ci-dessous. Vous pouvez également mettre à jour l'implémentation pour ignorer un certain nombre de demandes. Le reste devrait être couvert par la personnalisation que vous avez utilisée dans le message d'origine.

public class CountingRelay : ISpecimenBuilder
{
    public CountingRelay(ISpecimenBuilder builder, int maxCount)
    {
        this.MaxCount = maxCount;
        this.Builder = builder;
    }

    public int Count { get; private set; }
    public int MaxCount { get; }
    public ISpecimenBuilder Builder { get; }

    public object Create(object request, ISpecimenContext context)
    {
        if (this.Count == this.MaxCount) return new NoSpecimen();
        var result = this.Builder.Create(request, context);
        if (!(result is NoSpecimen)) this.Count++;
        return result;
    }
}

Salut @aivascu ,

@ajorians mon avis est que nous ne devrions pas ajouter un .Clone() méthode au Fixture classe.

D'ACCORD. Eh bien, merci d'avoir pensé.

Ce que vous pouvez faire, c'est implémenter un relais qui ignore les demandes après un certain nombre de résolutions réussies

Je regarderai.

Merci encore! :souriant:

@ajorians J'ai créé une demande de fonctionnalité plus formelle n° 1214 pour une réinitialisation de la personnalisation explicite et automatique. Vous pouvez y suivre la progression de cette fonctionnalité.

Cette page vous a été utile?
0 / 5 - 0 notes