Autofixture: Enfoque para generar una muestra aleatoria basada en la personalización

Creado en 8 ene. 2018  ·  11Comentarios  ·  Fuente: AutoFixture/AutoFixture

Hola, quiero poder generar valores distintos basados ​​en un ICustomization usando ISpecimenBuilder.CreateMany . Me preguntaba cuál sería la mejor solución ya que AutoFixture generará los mismos valores para todas las entidades.

`` c #
FooCustomization de clase pública: ICustomization
{
vacío público Personalizar (accesorio IFixture)
{
var espécimen = fixture.Build()
.OmitAutoProperties ()
.Con (x => x.CreationDate, DateTime.Now)
.With (x => x.Identifier, Guid.NewGuid (). ToString (). Substring (0, 6)) // Debería generar valores distintos
.Con (x => x.Mail, $ "[email protected]")
.Crear();

        fixture.Register(() => specimen);
}

}
''

PD: El objetivo principal es reducir la cantidad de código en mis pruebas, por lo que debo evitar llamar a With() para personalizar los valores para cada prueba. ¿Existe una forma adecuada de hacer esto?

Actualización: acabo de leer esta respuesta de @ploeh : https://github.com/AutoFixture/AutoFixture/issues/361#issuecomment -86873060 eso podría ser lo que estoy buscando.
Este enfoque tiene muchos inconvenientes: Primero, parece realmente contrario a la intuición llamar a Create<List<Foo>>() porque frustra el propósito de qué esperar de CreateMany<Foo> ; que generaría una lista de tamaño codificado> (?). Otro inconveniente es que tendría que tener dos personalizaciones para cada entidad; uno para crear colecciones personalizadas y otro para crear una sola instancia, ya que estamos anulando el comportamiento de Create<T> para crear colecciones.

question

Todos 11 comentarios

Esto parece un buen candidato para una pregunta de Stack Overflow . No hay nada de malo en hacer una pregunta aquí, pero creo que esta pregunta es del tipo en el que una respuesta también podría beneficiar a otras personas, y es más fácil encontrar preguntas y respuestas en Stack Overflow en comparación con los problemas de GitHub. Por un lado, Google hace un buen trabajo al indexar las preguntas de Stack Overflow, mientras que parece que clasifica los problemas de GitHub (cerrados) más bajos ...

@ploeh estoy de acuerdo contigo ....
Acabo de crear una pregunta SO basada en este problema. Este es el enlace para futuras referencias 👍

@ploeh Gracias por responder la pregunta sobre SO: blush:

@thiagomajesk En realidad, no hay problemas para responder las preguntas aquí. La razón por la que Mark sugirió SO es la capacidad de búsqueda que ofrece. Además, actualmente Mark está monitoreando SO, por lo que si publica una pregunta allí, hay más posibilidades de obtener su experiencia (como la que obtuvo esta vez): sweat_smile:

@zvirja Gracias por aclarar eso 😄

@zvirja Voy a cerrar la pregunta sobre SO basándome en la gran respuesta de @ploeh .
Pero me gustaría pedir un poco más de información sobre los detalles de implementación de este.
Como no quiero desviarme aún más del tema, creo que sería mejor preguntar sobre estos detalles aquí. Entonces tengo esta implementación:

`` c #
clase pública UniqueShortGuidBuilder: ISpecimenBuilder
{
cadena privada de solo lectura propName;
privado de solo lectura int longitud;

public UniqueShortGuidBuilder(Expression<Func<TEntity, string>> expr, int lenght)
{
    propName = ((MemberExpression)expr.Body).Member.Name;
    this.lenght = lenght;
}

objeto público Create (solicitud de objeto, contexto ISpecimenContext)
{
var pi = solicitud como PropertyInfo;

if (pi == null || pi.PropertyType != typeof(string) || pi.Name != propName)
    return new NoSpecimen();

return Guid.NewGuid().ToString().Substring(0, lenght);

}
''

Esperaba que al crear muchos objetos con AutoFixture, el ISpecimenBuilder.Create se llamara varias veces, generando Guids distintos para mis entidades; pero aparentemente esto no es cierto y en realidad comparten el mismo.

Bueno, acabo de probar su código y funcionó exactamente como lo deseaba:

`` c #
clase pública foo
{
Cadena pública PropertyToCustomize {get; colocar; }
}

clase pública UniqueShortGuidBuilder: ISpecimenBuilder
{
cadena privada de solo lectura propName;
privado de solo lectura int longitud;

public UniqueShortGuidBuilder(Expression<Func<TEntity, string>> expr, int lenght)
{
    propName = ((MemberExpression) expr.Body).Member.Name;
    this.lenght = lenght;
}

public object Create(object request, ISpecimenContext context)
{
    var pi = request as PropertyInfo;

    if (pi == null || pi.PropertyType != typeof(string) || pi.Name != propName)
        return new NoSpecimen();

    return Guid.NewGuid().ToString().Substring(0, lenght);
}

}

[Hecho]
Public void TestCustomization ()
{
var fixture = new Fixture ();
fixture.Customizations.Add (nuevo UniqueShortGuidBuilder(x => x.PropertyToCustomize, 5));

var manyFoos = fixture.CreateMany<Foo>(10);

var uniqueIds = manyFoos
    .Select(x => x.PropertyToCustomize)
    .Where(x => x.Length == 5)
    .Distinct()
    .ToArray();
Assert.Equal(10, uniqueIds.Length);

}
''

Por lo tanto, me gustaría volver a pedirle que vea cómo difiere su código real, para que podamos entender por qué esa diferencia es importante 😟

@zvirja Eso es raro. El código a continuación es una reproducción mínima del problema que tengo.
Si elimino el Fixture.Customize(new FooCustomization()); la prueba pasará; de lo contrario, no.

`` c #
usando el sistema;
utilizando Microsoft.VisualStudio.TestTools.UnitTesting;
usando AutoFixture.Kernel;
utilizando System.Linq.Expressions;
usando System.Reflection;
usando AutoFixture;
utilizando System.Linq;

espacio de nombres UnitTestProject1
{
clase pública foo
{
Cadena pública PropertyToCustomize {get; colocar; }
}

public class FooCustomization : ICustomization
{
    public void Customize(IFixture fixture)
    {
        var specimen = fixture.Build<Foo>()
            .OmitAutoProperties()
            .With(x => x.PropertyToCustomize)
            .Create();

        fixture.Register(() => specimen);
    }
}

public class UniqueShortGuidBuilder<TEntity> : ISpecimenBuilder
{
    private readonly string propName;
    private readonly int lenght;

    public UniqueShortGuidBuilder(Expression<Func<TEntity, string>> expr, int lenght)
    {
        propName = ((MemberExpression)expr.Body).Member.Name;
        this.lenght = lenght;
    }

    public object Create(object request, ISpecimenContext context)
    {
        var pi = request as PropertyInfo;

        if (pi == null || pi.PropertyType != typeof(string) || pi.Name != propName)
            return new NoSpecimen();

        return Guid.NewGuid().ToString().Substring(0, lenght);
    }
}

[TestClass]
public class UnitTest1
{
    public static Fixture Fixture { get; private set; }

    [ClassInitialize]
    public static void ClassInitialize(TestContext context)
    {
        Fixture = new Fixture();
        Fixture.Customizations.Add(new UniqueShortGuidBuilder<Foo>(x => x.PropertyToCustomize, 5));
        Fixture.Customize(new FooCustomization());
    }

    [TestMethod]
    public void TestMethod1()
    {
        var manyFoos = Fixture.CreateMany<Foo>(10);

        var uniqueIds = manyFoos
            .Select(x => x.PropertyToCustomize)
            .Where(x => x.Length == 5)
            .Distinct()
            .ToArray();

        Assert.AreEqual(10, uniqueIds.Length);
    }
}

}
''

Recomendación: si confía en eliminar los primeros _n_ caracteres de una cadena GUID, no nombre la clase UniqueShortGuidBuilder . Los valores pueden ser bastante únicos cuando la longitud es 5, pero si lo establece en 1, probablemente verá colisiones. En cualquier caso, la singularidad no está garantizada.

Si solo le importa la longitud de la cadena, más la unicidad no garantizada, ¿por qué no simplemente pedirle a AutoFixture un número entre, digamos, 0 y 99,999, y convertirlo en una cadena? IIRC, los números son únicos hasta que se agota el rango.

Si elimino Fixture.Customize (new FooCustomization ()); la prueba pasará, de lo contrario no.

Bueno, de hecho, ese es el comportamiento esperado si revisa la situación con más detenimiento 😅 Por favor, vea mi respuesta en el n. ° 962. La implementación proporcionada del FooCustomization crea una instancia del tipo Foo y configura el dispositivo para devolver siempre esa instancia. Por lo tanto, cuando luego cree varias instancias del tipo Foo , cada vez se devuelve el mismo objeto y no se vuelve a invocar su constructor.

Si desea que se cree una nueva instancia por cada vez que solicite Foo , use la API Customize<> como sugerí aquí .

@ploeh Gracias por el consejo, pero ese código es solo un ejemplo burdo para propósitos de prueba. En casos reales, la longitud no será ridículamente corta para afectar la singularidad de Guids 😄.

Si solo le importa la longitud de la cadena, más la unicidad no garantizada, ¿por qué no simplemente pedirle a AutoFixture un número entre, digamos, 0 y 99,999, y convertirlo en una cadena?

Apuesto a que esta es una buena solución para la mayoría de los casos, pero no puedo hacer eso para este caso específico debido a las validaciones alfanuméricas en mi sistema. Además de eso, tengo validaciones más complejas que involucran ese campo.

Bueno, de hecho, ese es el comportamiento esperado si revisas la situación con más detenimiento.

@zvirja Está claro después de su respuesta en el # 962. Pero para mí ese comportamiento no fue obvio, ya que no hay documentación inicial (explícita) sobre AutoFixture (como señalé aquí ) 😁.

Para terminar, gracias a ambos por la paciencia y todas las excelentes explicaciones.
Estoy seguro de que esto también será útil para otros en el futuro 😉.

Está bien, es justo, puedo tener una tendencia a tomar las cosas demasiado literalmente 😄

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

Temas relacionados

ploeh picture ploeh  ·  7Comentarios

josh-degraw picture josh-degraw  ·  4Comentarios

tiesmaster picture tiesmaster  ·  7Comentarios

ecampidoglio picture ecampidoglio  ·  7Comentarios

malylemire1 picture malylemire1  ·  7Comentarios