Autofixture: Funktionsvorschlag: Clone-Methode zu IFixture hinzufügen

Erstellt am 24. Sept. 2020  ·  9Kommentare  ·  Quelle: AutoFixture/AutoFixture

Einführung

Ich würde gerne, wenn IFixture eine Clone Methode oder etwas Ähnliches hätte, um eine Kopie des Fixtures zu erstellen.

Im Moment kann ich eine Erweiterungsmethode erstellen, um dies zu tun:

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

Aber ich bin der Meinung, dass eine solche Fähigkeit, eine Kopie zu erstellen, im Projekt selbst liegen sollte.

Einzelheiten

Mein Szenario ist, dass ich in einem Test einige Instanzen einer Klasse mit vielen Parametern konstruiere. Außerdem möchte ich, dass ein Konstruktorparameter für eine Instanz einen bestimmten Wert und für eine andere Instanz einen anderen Konstruktorwert hat. Es gibt mehrere Möglichkeiten, dies zu tun; so wie ich es gemacht habe ist:

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

//...

Der Grund für das Entfernen der Anpassung ist, dass ich es ohne es versucht habe, weil ich dachte, dass die zuletzt hinzugefügte Anpassung möglicherweise eine höhere Priorität hat und verwendet wird. aber es war nicht der Fall.

Wenn ich versuche, dies mit einer Art Erweiterungsmethode wie folgt zu vereinfachen:

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

Es mag an manchen Stellen funktionieren, aber an anderen nicht, da das Gerät nach dem Erstellen des Objekts eine Anpassung enthält, die ich nicht mehr wünsche und die ich entfernen muss.

Ich würde es vorziehen, keine Zeile zum Entfernen der hinzugefügten Anpassung(en) zu haben. Wenn also stattdessen meine Erweiterungsmethode eine Kopie meines Fixtures erstellt hat, der ich Modifikationen hinzufüge, funktionieren meine Kreationen wie erwartet und das Original Fixture bleibt unberührt.

Funktionieren würde auch die Möglichkeit, eine Anpassung hinzuzufügen, die automatisch entfernt wird, nachdem das Objekt erstellt wurde.

Hoffe das macht alles Sinn.

Danke für die Berücksichtigung meines Problems :smiley:

feature request

Hilfreichster Kommentar

@ajorians Ich wollte das Problem wegen Inaktivität schließen, konnte aber nicht an diesem riesigen Konstruktor
Ich muss fragen, haben Sie daran gedacht, Ihr Design zu ändern?

Wenn ich mir Ihren Konstrukteur ansehe, kann ich mehrere Probleme und mögliche Lösungen für Ihr Problem erkennen.

Zuallererst vermischen Sie die Konzepte von neuen und injizierbaren Entitäten.
Die einfachste Lösung besteht darin, Ihre injizierbaren Abhängigkeiten (auch bekannt als Service Interfaces) in Eigenschaften zu verschieben und sie mithilfe der Eigenschaftsinjektion zu injizieren.
Dies löst Ihr unmittelbares Problem, das Fixture kopieren zu müssen, und macht den Konstruktor überschaubarer.

Zweitens befasst sich mit der Überspritzen Codegeruch.
Dies bedeutet, dass die am häufigsten verwendeten Dienste in separate Abstraktionen verschoben werden.
Dies sollte Ihrem Design helfen, die SRP zu respektieren, und durch Erweiterung sollte es den enormen Aufwand an Setup reduzieren, den Sie derzeit in Ihren Tests durchführen müssen.

Da die Klasse ViewModel , gehe ich davon aus, dass Sie eine MVVM-Anwendung haben. Es ist wahrscheinlich, dass Sie Ihre Anwendung weiter entkoppeln können, indem Sie ein Messaging-Modell einführen. Vielleicht ein Ereignisaggregator. Die meisten MVVM-Frameworks haben sie integriert, sodass Sie nicht viel Einrichtungsarbeit leisten müssen, um von ihnen zu profitieren.

Lassen Sie mich wissen, ob das hilft.

Alle 9 Kommentare

@jorians Guten Tag! So wie ich Ihr Beispiel verstanden habe, klingt es so, als hätten wir bereits, was Sie brauchen. Die Methode Build() von Fixtures erstellt tatsächlich eine unveränderliche Kopie des internen Graphen, sodass Sie "einmalige" Anpassungen auf die AutoFixture anwenden können, ohne sie beizubehalten.

Sehen Sie sich diese Spielplatz-Demo an:

```c#
Modell der öffentlichen Klasse
{
öffentliche int-Id { get; einstellen; }
öffentliche Zeichenfolge Name { get; einstellen; }
}

[Tatsache]
öffentliche ungültige Demo()
{
var Fixture = new Fixture();
Befestigung.Anpassen(c => c.With(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);

}
```

Lassen Sie uns wissen, ob es Ihre Anforderungen löst. Wenn nicht, beschreiben Sie bitte genauer, warum es bei Ihnen nicht funktioniert, damit wir versuchen können, Ihnen zu helfen.

Vielen Dank!

Hallo @zvirja ,

Guten Tag!

Wenn ich darf; unsere Klasse Model hätte kein set und wäre nur über den Konstruktor einstellbar. Wenn Sie es also durch Folgendes ersetzen würden:

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

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

Sorry, dass ich das nicht ursprünglich angegeben habe.

So wie ich die Dinge verstehe, muss ich Anpassungen hinzufügen, um Konstruktorparameter auszuführen. Und diese Anpassungen sind vor dem Build() .

Wenn Sie sich für einige der Gründe interessieren, warum wir viel im Konstruktor und ohne Setter machen, können wir das Schlüsselwort readonly und sicherstellen, dass die Membervariablen gesetzt sind und nicht wieder rückgängig gemacht werden können, wenn eine Methode fertig ist namens.

Hoffe das macht mehr Sinn. Danke, dass Sie dabei helfen! :smiley:

@ajorians Verstehe ich Ihr Problem richtig, dass Sie ein Modell mit

Ansonsten kannst du immer sowas schreiben wie
c# var value2 = fixture.Build<Model>() .FromFactory((int id, string name) => new Model(id, name)) .Create();

Leider skaliert es mit wachsender Anzahl von Parametern nicht so gut 😟

@ajorians Verstehe ich Ihr Problem richtig, dass Sie ein Modell mit

Ja, das ist genau richtig.

Leider skaliert es mit wachsender Anzahl von Parametern nicht so gut 😟

Glücklicherweise funktioniert Freeze für viele unserer Klassen mit einer wachsenden Anzahl von Parametern. Aber ja, die Klasse, die ich im Sinn hatte, wo ich die Anpassungen verwende, um Konstruktorparameter zu tun, hat derzeit mehr als 30 Parameter:
image

Und wird wahrscheinlich vor Ende dieses Jahres um weitere 4 oder so wachsen.

Ich verstehe, dass das ungewöhnlich sein kann.

Aber ja, ich habe eine große Menge wachsender Eingabekonstruktorargumente und möchte nur eines (oder einige) davon anpassen, ohne über den Rest nachzudenken. Ohne dass dieser Typ für den gesamten Test registriert oder eingefroren wird. Und kurz gesagt, das Hinzufügen einer Anpassung ist ungefähr 5 Codezeilen.

Zum Glück kann ich das alles; Aber so wie ich es gemacht habe, habe ich eine clone Erweiterungsmethode in meinem clientseitigen Code erstellt.

Hoffe das macht alles Sinn. Danke, dass du das mit mir angeschaut hast :smiley:

@ajorians Ich wollte das Problem wegen Inaktivität schließen, konnte aber nicht an diesem riesigen Konstruktor
Ich muss fragen, haben Sie daran gedacht, Ihr Design zu ändern?

Wenn ich mir Ihren Konstrukteur ansehe, kann ich mehrere Probleme und mögliche Lösungen für Ihr Problem erkennen.

Zuallererst vermischen Sie die Konzepte von neuen und injizierbaren Entitäten.
Die einfachste Lösung besteht darin, Ihre injizierbaren Abhängigkeiten (auch bekannt als Service Interfaces) in Eigenschaften zu verschieben und sie mithilfe der Eigenschaftsinjektion zu injizieren.
Dies löst Ihr unmittelbares Problem, das Fixture kopieren zu müssen, und macht den Konstruktor überschaubarer.

Zweitens befasst sich mit der Überspritzen Codegeruch.
Dies bedeutet, dass die am häufigsten verwendeten Dienste in separate Abstraktionen verschoben werden.
Dies sollte Ihrem Design helfen, die SRP zu respektieren, und durch Erweiterung sollte es den enormen Aufwand an Setup reduzieren, den Sie derzeit in Ihren Tests durchführen müssen.

Da die Klasse ViewModel , gehe ich davon aus, dass Sie eine MVVM-Anwendung haben. Es ist wahrscheinlich, dass Sie Ihre Anwendung weiter entkoppeln können, indem Sie ein Messaging-Modell einführen. Vielleicht ein Ereignisaggregator. Die meisten MVVM-Frameworks haben sie integriert, sodass Sie nicht viel Einrichtungsarbeit leisten müssen, um von ihnen zu profitieren.

Lassen Sie mich wissen, ob das hilft.

Hallo @aivascu ,

@ajorians Ich wollte das Problem wegen Inaktivität schließen

Wenn ich etwas tun kann, lass es mich einfach wissen. Da es sich um eine Funktionsanfrage handelt, kann ich sehen, dass sie bis zur Implementierung ziemlich inaktiv sein kann.

Ich muss fragen, haben Sie daran gedacht, Ihr Design zu ändern?

Im Moment überlegen wir, eine Prismenanwendung zu werden . Das würde bei einigen der von Ihnen genannten Punkte helfen. Aber leider sehe ich nicht, dass wir die Philosophie ändern, einfach alles in den Konstruktor zu stecken, so dass es einmal auf null geprüft werden kann und readonly und immer nicht- null während der gesamten Lebensdauer der Klasse. Wir müssen etwas tun. Aber ich glaube nicht, dass sich das im nächsten Jahr oder so verbessern wird.

Lassen Sie mich wissen, ob das hilft.

All das hilft. Ich werde mein Team wissen lassen, dass sogar Leute auf der Welt denken, dass wir Änderungen in Betracht ziehen müssen.

Aber wenn ich fragen darf, ist es vernünftig, dass IFixture eine Clone oder eine ähnliche Methode hat? Oder den Leuten irgendwie erlauben, eine Anpassung hinzuzufügen und dann pop die letzte hinzugefügte Anpassung zu deaktivieren oder irgendwie in den Zustand zurückzukehren, bevor eine Anpassung hinzugefügt wird? Ich stimme zu, dass es wichtig ist, das Szenario zu verstehen, das dieses gewünschte Verhalten auslöst, und das Szenario hat sicherlich viele Probleme an erster Stelle. Und ich sehe, dass Sie keine zusätzliche Komplexität hinzufügen möchten, es sei denn, es gibt einen guten Anwendungsfall. Nun, Sie können wählen, was als nächstes passiert, aber wenn ich helfen kann, lassen Sie es mich einfach wissen.

Danke fürs Lesen und Nachdenken :smiley:

@ajorians meiner Meinung nach sollten wir der Klasse Fixture keine Methode .Clone() hinzufügen. Aus meiner Sicht sollte es für jeden einzelnen Test immer nur eine Testhalterung geben.

Es gab in der Vergangenheit einige Anfragen, eine Funktion zum Aufheben/Auswerfen/Einmalen einfrieren zu implementieren, aber niemand konnte sie implementieren.
Ich denke, der Grund dafür ist, dass es keine triviale Möglichkeit gibt, zuverlässig zu identifizieren, welcher Builder den eingefrorenen Wert zurückgibt oder was der letzte injizierte Wert war.

Was Sie tun können, ist ein Relay zu implementieren, das die Anfragen nach einer bestimmten Anzahl erfolgreicher Auflösungen ignoriert, wie unten beschrieben. Sie können die Implementierung auch aktualisieren, um eine bestimmte Anzahl von Anforderungen zu überspringen. Der Rest sollte durch die Anpassung abgedeckt werden, die Sie im ursprünglichen Beitrag verwendet haben.

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

Hallo @aivascu ,

@ajorians meiner Meinung nach sollten wir der Klasse Fixture keine Methode .Clone() hinzufügen.

OK. Nun, danke für die Überlegung.

Was Sie tun können, ist ein Relay zu implementieren, das die Anfragen nach einer bestimmten Anzahl erfolgreicher Auflösungen ignoriert

Ich schau mal nach.

Danke noch einmal! :smiley:

@ajorians Ich habe eine formellere Funktionsanfrage Nr. 1214 für das explizite und automatische Zurücksetzen der Anpassung erstellt. Dort können Sie den Fortschritt dieser Funktion verfolgen.

War diese Seite hilfreich?
0 / 5 - 0 Bewertungen