Autofixture: Vorschlag für GenerationDepthBehavior.

Erstellt am 1. Mai 2018  ·  12Kommentare  ·  Quelle: AutoFixture/AutoFixture

Hier ist ein Vorschlag für das Verhalten des Tiefenbegrenzers der Generation.

Fixture.Behaviors.Add(neue GenerationDepthBehavior(2));

Musste ComposeIfMultiple zum Testen neu implementieren, da es intern ist.

```c#
öffentliche Klasse GenerationDepthBehavior : ISpecimenBuilderTransformation
{
private const int DefaultGenerationDepth = 1;
private readonly int generationDepth;

public GenerationDepthBehavior() : this(DefaultGenerationDepth)
{
}

public GenerationDepthBehavior(int generationDepth)
{
    if (generationDepth < 1)
        throw new ArgumentOutOfRangeException(nameof(generationDepth), "Generation depth must be greater than 0.");

    this.generationDepth = generationDepth;
}


public ISpecimenBuilderNode Transform(ISpecimenBuilder builder)
{
    if (builder == null) throw new ArgumentNullException(nameof(builder));

    return new GenerationDepthGuard(builder, new GenerationDepthHandler(), this.generationDepth);
}

}

öffentliche Schnittstelle IGenerationDepthHandler
{

object HandleGenerationDepthLimitRequest(object request, IEnumerable<object> recordedRequests, int depth);

}

öffentliche Klasse DepthSeededRequest : SeededRequest
{
public int Tiefe { get; }
public DepthSeededRequest(Objektanforderung, Objekt-Seed, Int-Tiefe) : base(Request, Seed)
{
Tiefe = Tiefe;
}
}

öffentliche Klasse GenerationDepthGuard : ISpecimenBuilderNode
{
privat readonly ThreadLocal> AnfragenByThread
= neues ThreadLocal>(() => neuer Stapel());

private Stack<DepthSeededRequest> GetMonitoredRequestsForCurrentThread() => this.requestsByThread.Value;


public GenerationDepthGuard(ISpecimenBuilder builder)
    : this(builder, EqualityComparer<object>.Default)
{
}

public GenerationDepthGuard(
    ISpecimenBuilder builder,
    IGenerationDepthHandler depthHandler)
    : this(
        builder,
        depthHandler,
        EqualityComparer<object>.Default,
        1)
{
}

public GenerationDepthGuard(
    ISpecimenBuilder builder,
    IGenerationDepthHandler depthHandler,
    int generationDepth)
    : this(
        builder,
        depthHandler,
        EqualityComparer<object>.Default,
        generationDepth)
{
}


public GenerationDepthGuard(ISpecimenBuilder builder, IEqualityComparer comparer)
{
    this.Builder = builder ?? throw new ArgumentNullException(nameof(builder));
    this.Comparer = comparer ?? throw new ArgumentNullException(nameof(comparer));
    this.GenerationDepth = 1;
}


public GenerationDepthGuard(
    ISpecimenBuilder builder,
    IGenerationDepthHandler depthHandler,
    IEqualityComparer comparer)
    : this(
    builder,
    depthHandler,
    comparer,
    1)
{
}

public GenerationDepthGuard(
    ISpecimenBuilder builder,
    IGenerationDepthHandler depthHandler,
    IEqualityComparer comparer,
    int generationDepth)
{
    if (builder == null) throw new ArgumentNullException(nameof(builder));
    if (depthHandler == null) throw new ArgumentNullException(nameof(depthHandler));
    if (comparer == null) throw new ArgumentNullException(nameof(comparer));
    if (generationDepth < 1)
        throw new ArgumentOutOfRangeException(nameof(generationDepth), "Generation depth must be greater than 0.");

    this.Builder = builder;
    this.GenerationDepthHandler = depthHandler;
    this.Comparer = comparer;
    this.GenerationDepth = generationDepth;
}


public ISpecimenBuilder Builder { get; }

public IGenerationDepthHandler GenerationDepthHandler { get; }

public int GenerationDepth { get; }

public int CurrentDepth { get; }

public IEqualityComparer Comparer { get; }

protected IEnumerable RecordedRequests => this.GetMonitoredRequestsForCurrentThread();

public virtual object HandleGenerationDepthLimitRequest(object request, int currentDepth)
{
    return this.GenerationDepthHandler.HandleGenerationDepthLimitRequest(
        request,
        this.GetMonitoredRequestsForCurrentThread(), currentDepth);
}

public object Create(object request, ISpecimenContext context)
{
    if (request is SeededRequest)
    {
        int currentDepth = -1;

        var requestsForCurrentThread = this.GetMonitoredRequestsForCurrentThread();

        if (requestsForCurrentThread.Count > 0)
        {
            currentDepth = requestsForCurrentThread.Max(x => x.Depth) + 1;
        }

        DepthSeededRequest depthRequest = new DepthSeededRequest(((SeededRequest)request).Request, ((SeededRequest)request).Seed, currentDepth);

        if (depthRequest.Depth >= this.GenerationDepth)
        {
            return HandleGenerationDepthLimitRequest(request, depthRequest.Depth);
        }

        requestsForCurrentThread.Push(depthRequest);
        try
        {
            return this.Builder.Create(request, context);
        }
        finally
        {
            requestsForCurrentThread.Pop();
        }
    }
    else
    {
        return this.Builder.Create(request, context);
    }
}

public virtual ISpecimenBuilderNode Compose(
    IEnumerable<ISpecimenBuilder> builders)
{
    var composedBuilder = ComposeIfMultiple(
        builders);
    return new GenerationDepthGuard(
        composedBuilder,
        this.GenerationDepthHandler,
        this.Comparer,
        this.GenerationDepth);
}

internal static ISpecimenBuilder ComposeIfMultiple(IEnumerable<ISpecimenBuilder> builders)
{
    ISpecimenBuilder singleItem = null;
    List<ISpecimenBuilder> multipleItems = null;
    bool hasItems = false;

    using (var enumerator = builders.GetEnumerator())
    {
        if (enumerator.MoveNext())
        {
            singleItem = enumerator.Current;
            hasItems = true;

            while (enumerator.MoveNext())
            {
                if (multipleItems == null)
                {
                    multipleItems = new List<ISpecimenBuilder> { singleItem };
                }

                multipleItems.Add(enumerator.Current);
            }
        }
    }

    if (!hasItems)
    {
        return new CompositeSpecimenBuilder();
    }

    if (multipleItems == null)
    {
        return singleItem;
    }

    return new CompositeSpecimenBuilder(multipleItems);
}

public virtual IEnumerator<ISpecimenBuilder> GetEnumerator()
{
    yield return this.Builder;
}

IEnumerator IEnumerable.GetEnumerator()
{
    return this.GetEnumerator();
}

}

öffentliche Klasse GenerationDepthHandler : IGenerationDepthHandler
{
öffentliches Objekt HandleGenerationDepthLimitRequest(
Objektanfrage,
IEzählbaraufgezeichneteAnfragen, int. Tiefe)
{
Rückgabe von neuem OmitSpecimen();
}
}
```

question

Hilfreichster Kommentar

@malylemire1 Wäre es für Sie in Ordnung, wenn wir dies

Es tut mir leid, dass wir nicht alle gewünschten Funktionen in den Kern aufnehmen können 😟 Es ist ein bisschen schwierig, das Gleichgewicht zwischen der bereitgestellten Funktion und der Menge an Code, die wir hier pflegen, zu halten. Es sieht so aus, als ob es für Sie kein großes Problem darstellt, diese Ad-hoc-Funktion in Ihrer Lösung zu implementieren, sodass Sie nicht blockiert werden.

Danke nochmal für deinen Vorschlag!

Alle 12 Kommentare

Hallo, danke für das Teilen der Idee! :+1:

Könnten Sie bitte die Gründe für diese Funktion beschreiben? Haben Sie ein bestimmtes Szenario, in dem Sie es nützlich fanden?

Hallo, ich verwende es hauptsächlich für ORM-Entites oder externe API-Klassen, bei denen sich die Tiefe unbegrenzt ausbreiten kann, aber nicht rekursiv ist.
Ich verwende AutoFixture für Unit Tests, Integration Tests und Functional Tests. Unit-Test für Kernfunktionen des Enterprise-Frameworks. Integrationstests mit Tests für komplexe Arbeitseinheiten mit Repository-Muster und Zugriff auf eine Testdatenbank. Funktionstest mit Zugriff auf eine gesamte Anwendung.

Ich habe es direkt auf AutoMoqDataAttribute gestellt

```c#
öffentliche Klasse AutoMoqDataAttribute : AutoDataAttribute
{
///


/// Standardkonstruktor.
///

/// Standardwert 0 für unbegrenzte Generierungstiefe.
public AutoMoqDataAttribute(int generationDepth = 0)
: basis(() =>
{
IFixture Fixture = new Fixture().Customize(new AutoMoqCustomization());

        fixture.Behaviors.OfType<ThrowingRecursionBehavior>().ToList()
                .ForEach(b => fixture.Behaviors.Remove(b));

        fixture.Behaviors.Add(new OmitOnRecursionBehavior());

        if (generationDepth > 0)
        {
            fixture.Behaviors.Add(new GenerationDepthBehavior(generationDepth));
        }

        return fixture;
    })
{
}

}
```

Ich würde auch vorschlagen, ComposeIfMultiple öffentlich zu machen. Es würde die Implementierung von benutzerdefiniertem Verhalten vereinfachen.

Ich verstehe, was das Verhalten bewirkt, die Hauptfrage ist der Zweck und die Szenarien.

Hallo, ich verwende es hauptsächlich für ORM-Entites oder externe API-Klassen, bei denen sich die Tiefe unbegrenzt ausbreiten kann, aber nicht rekursiv ist.

Soweit ich weiß, ist das Hauptziel, das Sie anstreben, die Leistung. Aus funktionaler Sicht funktioniert alles bestens und man möchte sich einfach ein paar CPU-Ticks sparen.

Es ist ein bisschen schwer zu beurteilen, ob wir diese Funktion sofort ausliefern möchten oder nicht. Ich verstehe, dass Sie möglicherweise ein Typmodell mit sehr tiefer Verschachtelung haben und dort der Leistungsgewinn erheblich ist. Aber es gibt auch ein paar Nachteile:

  • es bricht die Transparenz Ihrer Unit-Tests. Jetzt müssen Sie die Ebene des Objekts, auf das am tiefsten zugegriffen wird, während des Tests kennen. Wenn Sie die Implementierung später ändern, können Tests aufgrund von Standardwerten abbrechen und Sie müssen wahrscheinlich die Ebene aktualisieren.
  • es erhöht die Testwartung, da Sie jetzt die Tatsache verfolgen müssen, dass der Graph möglicherweise nicht vollständig initialisiert wurde.

Es ist völlig in Ordnung, dass Sie in Ihrem Team diese Funktion benötigen und Sie zustimmen, sie zu verwenden. Ich würde jedoch argumentieren, es allgemein zu verwenden, da es die Dinge sehr kompliziert.

Es gibt viele mögliche Szenarien und AutoFixture versucht, nur die gängigsten Funktionen bereitzustellen. Die fortgeschritteneren Bits werden durch die Erweiterbarkeitsfunktionen abgedeckt. In diesem speziellen Fall würde ich sagen, dass diese Funktion nicht so häufig ist, um sie zu einem Teil des Produkts zu machen. Ein weiterer Punkt ist, dass die Implementierung sehr klein ist (siehe unten), daher sollte es kein Problem sein, sie lokal in Ihrem Testprojekt zu implementieren.

Ich würde auch vorschlagen, ComposeIfMultiple öffentlich zu machen. Es würde die Implementierung von benutzerdefiniertem Verhalten vereinfachen.

Es wurde bereits #657 besprochen.


Die Implementierung könnte übrigens etwas vereinfacht werden (wenn ich nicht etwas übersehe):
```c#
öffentliche Klasse GenerationDepthBehavior: ISpecimenBuilderTransformation
{
public int Tiefe { get; }

public GenerationDepthBehavior(int depth)
{
    Depth = depth;
}

public ISpecimenBuilderNode Transform(ISpecimenBuilder builder)
{
    return new RecursionGuard(builder, new OmitOnRecursionHandler(), new IsSeededRequestComparer(), Depth);
}

private class IsSeededRequestComparer : IEqualityComparer
{
    bool IEqualityComparer.Equals(object x, object y)
    {
        return x is SeededRequest && y is SeededRequest;
    }

    int IEqualityComparer.GetHashCode(object obj)
    {
        return obj is SeededRequest ? 0 : EqualityComparer<object>.Default.GetHashCode(obj);
    }
}

}
```


@moodmosaic Könntest du bitte auch deine Meinung teilen? 😉

Das in https://github.com/AutoFixture/AutoFixture/issues/1032#issuecomment -385724150 gezeigte Beispiel ist die bevorzugte Methode zur Verwendung dieses Ad-hoc- Verhaltens. An dieser Stelle denke ich nicht, dass wir den eingebauten Algorithmus ändern sollten.


Was wir jedoch in Betracht ziehen

  • ein teilbares PRNG verwenden (das aktuelle ist nicht teilbar)
  • Fügen Sie Zufallstests hinzu (folgen Sie beispielsweise https://github.com/hedgehogqa/haskell-hedgehog/issues/125)
  • Benchmarks

und dann könnten wir in Erwägung ziehen, weitere Optimierungen wie diese vorzunehmen.

@moodmosaic Es scheint der Vorschlag zu sein, das Standardverhalten nicht zu ändern, sondern GenerationDepthBehavior einem Teil der AutoFixture-Bibliothek zu machen (damit Sie es bei Bedarf aktivieren können). Was ist also Ihre Meinung dazu? :zwinkern:

@zvirja Es ist nicht wirklich aus Leistungsgründen. Das Problem bei der unbestimmten Weitergabe ist ein fehlgeschlagener Test mit einer Ausnahme wegen unzureichenden Speichers.

@zvirja , @malylemire1 , es sei denn, dieses Ad-hoc-Verhalten adressiert eine Vielzahl von Szenarien, ich glaube nicht, dass es integriert sein muss.


Ich kann kein MCVE erkennen , aber AFAICT, die Probleme kommen von automatisierten Tests, die eigentlich keine Unit- Tests sind (zB Boundary/Integration-Tests).

AutoFixture wurde nicht in erster Linie mit Integrationstests beschäftigen entwickelt, so dass einige Ad-hoc - Verhalten (wie hier dargestellt) gerechtfertigt sind: +1: Ich glaube nicht , es ist Teil der Kernbibliothek sein muss, though.

@malylemire1 Wäre es für Sie in Ordnung, wenn wir dies

Es tut mir leid, dass wir nicht alle gewünschten Funktionen in den Kern aufnehmen können 😟 Es ist ein bisschen schwierig, das Gleichgewicht zwischen der bereitgestellten Funktion und der Menge an Code, die wir hier pflegen, zu halten. Es sieht so aus, als ob es für Sie kein großes Problem darstellt, diese Ad-hoc-Funktion in Ihrer Lösung zu implementieren, sodass Sie nicht blockiert werden.

Danke nochmal für deinen Vorschlag!

Schließen dieses Problems als Teil der Bereinigung. Nochmals vielen Dank für das Teilen der Idee! 😉

@malylemire1 Ich verstehe den Anwendungsfall zu 100%.
Ist das etwas, das Sie in einem Nuget-Paket zur Verfügung stellen können?

War diese Seite hilfreich?
0 / 5 - 0 Bewertungen

Verwandte Themen

zvirja picture zvirja  ·  3Kommentare

tomasaschan picture tomasaschan  ·  3Kommentare

Ephasme picture Ephasme  ·  3Kommentare

ploeh picture ploeh  ·  3Kommentare

zvirja picture zvirja  ·  4Kommentare