Autofixture: Solicitud: Agregar método de clonación a IFixture

Creado en 24 sept. 2020  ·  9Comentarios  ·  Fuente: AutoFixture/AutoFixture

Introducción

Me gustaría que IFixture tuviera un método Clone o algo similar para crear una copia del dispositivo.

Por el momento puedo crear un método de extensión para hacer esto:

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

Pero creo que esa capacidad para hacer una copia debería estar en el proyecto en sí.

Detalles

Mi escenario es que construyo un par de instancias de una clase con muchos parámetros en una prueba. Además, quiero que un parámetro de constructor sea un valor determinado para una instancia y un valor de constructor diferente para una instancia diferente. Hay varias formas de hacer esto; la forma en que lo he estado haciendo es:

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

//...

El motivo de la eliminación de la personalización es que lo intenté sin pensar que tal vez la última personalización agregada tendrá una mayor prioridad y se utilizará; pero no fue el caso.

Si trato de simplificar esto con algún tipo de método de extensión como este:

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

Puede funcionar en algunos lugares, pero no funcionará en otros porque después de que se construye el objeto, el dispositivo contiene una personalización que ya no deseo y necesito eliminar.

Preferiría no tener una línea para eliminar las personalizaciones agregadas. Entonces, si en cambio mi método de extensión hizo una copia de mi accesorio a la que agrego modificaciones a la copia, entonces mis creaciones funcionan como se esperaba y el accesorio original no se modifica.

También funcionaría la capacidad de agregar una personalización que se elimina automáticamente después de que se crea el objeto.

Espero que todo esto tenga sentido.

Gracias por considerar mi problema: smiley:

feature request

Comentario más útil

@ajorians Estuve a punto de cerrar el tema por inactividad pero no pude pasar junto a ese gigantesco constructor.
Tengo que preguntar, ¿ha considerado cambiar su diseño?

Al mirar a su constructor, puedo ver varios problemas y posibles soluciones a su problema.

En primer lugar, está mezclando los conceptos de entidades nuevas e inyectables.
La solución más sencilla es mover sus dependencias inyectables (también conocidas como interfaces de servicio) a las propiedades e inyectarlas mediante la inyección de propiedades.
Esto resolverá su problema inmediato de tener que copiar Fixture y hará que el constructor sea más manejable.

En segundo lugar está abordando la sobre-inyección código olor.
Esto significa alejar los servicios juntos más comúnmente utilizados en abstracciones separadas.
Esto debería ayudar a que su diseño respete el SRP y, por extensión, debería reducir la gran cantidad de configuración que tiene que hacer en este momento, en sus pruebas.

Dado que la clase se llama ViewModel , supongo que tiene una aplicación MVVM. Es probable que pueda desacoplar aún más su aplicación mediante la introducción de un modelo de mensajería. Quizás un agregador de eventos. La mayoría de los frameworks MVVM los tienen integrados para que no tenga que hacer mucho trabajo de configuración para beneficiarse de ellos.

Déjeme saber si esto ayuda.

Todos 9 comentarios

@ajorians ¡ Buen día! Por cómo entendí tu ejemplo, parece que ya tenemos lo que necesitas. El método de fijación Build() realidad está creando una copia inmutable del gráfico interno, por lo que puede aplicar personalizaciones "únicas" en la parte superior del AutoFixture sin conservarlas.

Revise esta demostración del patio de recreo:

`` c #
modelo de clase pública
{
public int Id {obtener; colocar; }
Nombre de cadena pública {get; colocar; }
}

[Hecho]
demostración pública vacía ()
{
var fixture = new Fixture ();
accesorio Personalizar(c => c. con (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);

}
''

Háganos saber si resuelve sus necesidades. De lo contrario, describa más específicamente por qué no funciona para usted, para que podamos intentar ayudarlo.

¡Gracias!

Hola @zvirja ,

¡Buen día!

Si puedo; nuestra clase Model no tendría un set y solo se podría configurar a través del constructor. Entonces, si lo reemplazaras con esto:

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

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

Perdón por no especificar eso originalmente.

Entonces, según entiendo las cosas, tengo que agregar personalizaciones para hacer parámetros de constructor. Y estas personalizaciones están antes de Build() .

Si está interesado, algunas de las razones por las que hacemos mucho en el constructor y sin establecedores es para que podamos usar la palabra clave readonly y asegurarnos de que las variables miembro estén configuradas y no se puedan desarmar para cuando un método sea llamado.

Espero que tenga más sentido. ¡Gracias por ayudar con esto! : smiley:

@ajorians ¿Entiendo correctamente su problema, que tiene un modelo con una gran cantidad de argumentos de entrada, por lo que le gustaría personalizar solo uno de ellos, sin tener que preocuparse explícitamente por él? ¿Derecha?

De lo contrario, siempre puedes escribir algo como
c# var value2 = fixture.Build<Model>() .FromFactory((int id, string name) => new Model(id, name)) .Create();

Desafortunadamente, no escala bastante bien con un número creciente de parámetros 😟

@ajorians ¿Entiendo correctamente su problema, que tiene un modelo con una gran cantidad de argumentos de entrada, por lo que le gustaría personalizar solo uno de ellos, sin tener que preocuparse explícitamente por él? ¿Derecha?

Si, eso es exactamente correcto.

Desafortunadamente, no escala bastante bien con un número creciente de parámetros 😟

Afortunadamente, Freeze funciona para muchas de nuestras clases con un número creciente de parámetros. Pero sí, la clase que tenía en mente donde estoy usando las personalizaciones para hacer parámetros de constructor actualmente tiene más de 30 parámetros:
image

Y probablemente crecerá 4 o menos antes de que finalice este año.

Entiendo que esto puede ser inusual.

Pero sí, tengo una gran cantidad de argumentos de constructores de entrada en crecimiento y me gustaría personalizar solo uno (o algunos) de ellos sin pensar en el resto. Sin tener ese tipo registrado o congelado durante toda la prueba. Y de una manera concisa, ya que agregar una personalización es de aproximadamente 5 líneas de código.

Afortunadamente puedo hacer todo eso; pero la forma en que lo hice implicó hacer un método de extensión clone en mi código del lado del cliente.

Espero que todo esto tenga sentido. Gracias por mirar esto conmigo: smiley:

@ajorians Estuve a punto de cerrar el tema por inactividad pero no pude pasar junto a ese gigantesco constructor.
Tengo que preguntar, ¿ha considerado cambiar su diseño?

Al mirar a su constructor, puedo ver varios problemas y posibles soluciones a su problema.

En primer lugar, está mezclando los conceptos de entidades nuevas e inyectables.
La solución más sencilla es mover sus dependencias inyectables (también conocidas como interfaces de servicio) a las propiedades e inyectarlas mediante la inyección de propiedades.
Esto resolverá su problema inmediato de tener que copiar Fixture y hará que el constructor sea más manejable.

En segundo lugar está abordando la sobre-inyección código olor.
Esto significa alejar los servicios juntos más comúnmente utilizados en abstracciones separadas.
Esto debería ayudar a que su diseño respete el SRP y, por extensión, debería reducir la gran cantidad de configuración que tiene que hacer en este momento, en sus pruebas.

Dado que la clase se llama ViewModel , supongo que tiene una aplicación MVVM. Es probable que pueda desacoplar aún más su aplicación mediante la introducción de un modelo de mensajería. Quizás un agregador de eventos. La mayoría de los frameworks MVVM los tienen integrados para que no tenga que hacer mucho trabajo de configuración para beneficiarse de ellos.

Déjeme saber si esto ayuda.

Hola @aivascu ,

@ajorians estuve a punto de cerrar el tema por inactividad

Si hay algo que pueda hacer, házmelo saber. Al ser una solicitud de función que puedo ver hasta que se implemente, podría tener una buena cantidad de inactividad.

Tengo que preguntar, ¿ha considerado cambiar su diseño?

Por el momento estamos considerando convertirnos en una aplicación de prisma . Eso ayudaría con algunos de los elementos que mencionaste. Pero, lamentablemente, no veo que cambiemos la filosofía de simplemente poner todo en el constructor de modo que se pueda verificar null una vez y ser readonly y siempre ser no null a lo largo de la vida de la clase. Necesitamos hacer algo. Pero no creo que esto vaya a mejorar en el próximo año.

Déjeme saber si esto ayuda.

Todo esto ayuda. Le haré saber a mi equipo que incluso las personas en el mundo piensan que debemos considerar hacer cambios.

Pero si puedo preguntar, ¿es razonable que IFixture tenga un Clone o un método similar? ¿O de alguna manera permitir que las personas agreguen una personalización y luego pop desactiva la última personalización agregada o de alguna manera regresa al estado antes de agregar una personalización? Estoy de acuerdo en que comprender el escenario que provoca este comportamiento deseado es importante y, ciertamente, el escenario tiene muchos problemas en primer lugar. Y veo que no desea agregar complejidad adicional a menos que haya un buen caso de uso. Bueno, puedes elegir lo que sucede a continuación, pero si puedo ayudar, házmelo saber.

Gracias por leer y considerar: smiley:

@ajorians mi opinión es que no hay que añadir un .Clone() método para el Fixture clase. A mi modo de ver, debería haber solo un dispositivo de prueba en un momento dado para una sola prueba.

Ha habido algunas solicitudes para implementar una función de descongelar / expulsar / congelar una vez en el pasado, pero nadie pudo implementarla.
Creo que la razón es que no hay una forma trivial de identificar de manera confiable qué constructor va a devolver el valor congelado o cuál fue el último valor inyectado.

Lo que puede hacer es implementar un relé que ignore las solicitudes después de un cierto recuento de resoluciones exitosas, como se muestra a continuación. También puede actualizar la implementación para omitir una cierta cantidad de solicitudes. El resto debe estar cubierto por la personalización que usó en la publicación original.

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

Hola @aivascu ,

@ajorians mi opinión es que no hay que añadir un .Clone() método para el Fixture clase.

está bien. Bueno, gracias por considerarlo.

Lo que puede hacer es implementar un relé que ignore las solicitudes después de un cierto recuento de resoluciones exitosas

Le daré un vistazo.

¡Gracias de nuevo! : smiley:

@ajorians He creado una solicitud de función más formal # 1214 para el restablecimiento de personalización explícito y automático. Puede realizar un seguimiento del progreso de esta función allí.

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

Temas relacionados

ploeh picture ploeh  ·  3Comentarios

Accc99 picture Accc99  ·  4Comentarios

zvirja picture zvirja  ·  4Comentarios

ploeh picture ploeh  ·  7Comentarios

mjfreelancing picture mjfreelancing  ·  4Comentarios