Autofixture: Propuesta de GenerationDepthBehavior.

Creado en 1 may. 2018  ·  12Comentarios  ·  Fuente: AutoFixture/AutoFixture

Aquí hay una propuesta de comportamiento del limitador de profundidad de generación.

fixture.Behaviors.Add (new GenerationDepthBehavior (2));

Tuve que volver a implementar ComposeIfMultiple para la prueba porque es interno.

`` c #
clase pública 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);
}

}

interfaz pública IGenerationDepthHandler
{

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

}

DepthSeededRequest de clase pública: SeededRequest
{
public int Depth {obtener; }
public DepthSeededRequest (solicitud de objeto, semilla de objeto, profundidad int): base (solicitud, semilla)
{
Profundidad = profundidad;
}
}

clase pública GenerationDepthGuard: ISpecimenBuilderNode
{
ThreadLocal privado de solo lectura> requestByThread
= nuevo ThreadLocal> (() => nueva pila());

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

}

clase pública GenerationDepthHandler: IGenerationDepthHandler
{
objeto público HandleGenerationDepthLimitRequest (
solicitud de objeto,
IEnumerableSavedRequests, profundidad int)
{
return new OmitSpecimen ();
}
}
''

question

Comentario más útil

@ malylemire1 ¿ posponemos esto por ahora y lo implementamos más tarde solo si vemos muchas solicitudes para esta función?

Lamento que no podamos incluir todas las funciones deseadas en el núcleo 😟 Es un poco difícil mantener el equilibrio entre la función proporcionada y la cantidad de código que mantenemos aquí. Parece que no será un gran problema para usted implementar esta función ad-hoc en su solución, por lo que no está bloqueado.

¡Gracias de nuevo por tu propuesta!

Todos 12 comentarios

Hola, gracias por compartir la idea! : +1:

¿Podría describir el razonamiento detrás de esta función? ¿Tiene algún escenario en particular en el que lo haya encontrado útil?

Hola, lo uso principalmente para entradas ORM o clases api externas donde la profundidad se puede propagar indefinidamente pero no es recursiva.
Utilizo AutoFixture para pruebas unitarias, pruebas de integración y pruebas funcionales. Prueba unitaria para las funcionalidades centrales del marco empresarial. Pruebas de integración con pruebas para unidades de trabajo complejas con patrón de repositorio y acceso a una base de datos de prueba. Prueba funcional con acceso a una aplicación completa.

Lo he puesto directamente en AutoMoqDataAttribute

`` c #
clase pública AutoMoqDataAttribute: AutoDataAttribute
{
///


/// Constructor predeterminado.
///

/// El valor predeterminado es 0 para una profundidad de generación ilimitada.
public AutoMoqDataAttribute (int generationDepth = 0)
: base (() =>
{
Fixture fixture = nuevo Fixture (). Personalizar (nuevo 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;
    })
{
}

}
''

También sugeriría hacer público ComposeIfMultiple. Facilitaría la implementación del comportamiento personalizado.

Entiendo lo que hace el comportamiento, la pregunta principal es el propósito y los escenarios.

Hola, lo uso principalmente para entradas ORM o clases api externas donde la profundidad se puede propagar indefinidamente pero no es recursiva.

Por lo que tengo entendido, el principal objetivo que persigue es el rendimiento. Desde la perspectiva funcional, todo funciona bien y simplemente le gustaría ahorrar algunos ticks de CPU.

Es un poco difícil juzgar si nos gustaría enviar esta función de fábrica o no. Entiendo que es posible que tenga un modelo de tipo con un anidamiento muy profundo y allí la ganancia de rendimiento es significativa. Pero también tiene algunos inconvenientes:

  • rompe la transparencia de sus pruebas unitarias. Ahora necesita saber el nivel del objeto al que se accede más profundo durante la prueba. Si luego cambia la implementación, las pruebas pueden comenzar a fallar debido a los valores predeterminados y probablemente necesitará actualizar el nivel.
  • aumenta el mantenimiento de la prueba, ya que ahora necesita realizar un seguimiento de un hecho de que es posible que el gráfico no se haya inicializado por completo.

Está completamente bien que en su equipo requiera esta función y acepte usarla. Sin embargo, yo diría que debería darle un uso común, ya que complica mucho las cosas.

Hay muchos escenarios potenciales y AutoFixture intenta ofrecer solo las características más comunes. Los bits más avanzados están cubiertos por las capacidades de extensibilidad. En este caso particular, diría que esa característica no es tan común como para convertirla en parte del producto. Otro punto es que la implementación es muy pequeña (ver más abajo), por lo que no debería ser un problema implementarlo en su proyecto de prueba localmente.

También sugeriría hacer público ComposeIfMultiple. Facilitaría la implementación del comportamiento personalizado.

Ya se ha discutido el n. ° 657.


Por cierto, la implementación podría simplificarse un poco (si no me falta algo):
`` c #
clase pública GenerationDepthBehavior: ISpecimenBuilderTransformation
{
public int Depth {obtener; }

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 ¿Podrías compartir tu opinión también? 😉

El ejemplo que se muestra en https://github.com/AutoFixture/AutoFixture/issues/1032#issuecomment -385724150 es la forma preferida de utilizar este comportamiento ad-hoc . En este punto, no creo que debamos cambiar el algoritmo incorporado.


Lo que podríamos considerar, sin embargo, es

  • use un PRNG divisible (el actual no es divisible)
  • agregue pruebas de aleatoriedad (siga https://github.com/hedgehogqa/haskell-hedgehog/issues/125, por ejemplo)
  • puntos de referencia

y luego , podríamos considerar realizar más optimizaciones, como esta.

@moodmosaic Parece que la sugerencia no es cambiar el comportamiento predeterminado, sino hacer que GenerationDepthBehavior parte de la biblioteca AutoFixture (para que pueda habilitarlo a pedido). Entonces, ¿cuál es su opinión al respecto? :guiño:

@zvirja En realidad, no es por motivos de rendimiento. El problema con la propagación indefinida es la prueba fallida sin la excepción de memoria insuficiente.

@zvirja , @ malylemire1 , a menos que este comportamiento ad-hoc aborde una amplia gama de escenarios, no creo que tenga que estar integrado.


No puedo detectar un MCVE , pero AFAICT, los problemas provienen de pruebas automatizadas que en realidad no son pruebas unitarias (por ejemplo, pruebas de límites / integración).

AutoFixture no fue diseñado principalmente para lidiar con pruebas de integración, por lo que se justifican algunos comportamientos ad-hoc (como el que se muestra aquí): +1: Sin embargo, no creo que tenga que ser parte de la biblioteca principal.

@ malylemire1 ¿ posponemos esto por ahora y lo implementamos más tarde solo si vemos muchas solicitudes para esta función?

Lamento que no podamos incluir todas las funciones deseadas en el núcleo 😟 Es un poco difícil mantener el equilibrio entre la función proporcionada y la cantidad de código que mantenemos aquí. Parece que no será un gran problema para usted implementar esta función ad-hoc en su solución, por lo que no está bloqueado.

¡Gracias de nuevo por tu propuesta!

Cerrando este problema como parte de la limpieza. ¡Gracias de nuevo por compartir la idea! 😉

@ malylemire1 Entiendo al 100% el caso de uso.
¿Es esto algo que puede poner a disposición en un paquete nuget?

¿Fue útil esta página
0 / 5 - 0 calificaciones