2010-04-12 10 views
10

Empecé a usar AutoFixture http://autofixture.codeplex.com/ ya que las pruebas de mi unidad estaban llenas de mucha configuración de datos. Estaba dedicando más tiempo a recopilar datos que a escribir mi prueba unitaria. Aquí está un ejemplo de cómo mi unidad de prueba inicial se parece (ejemplo tomado de la muestra de la aplicación de carga del libro azul DDD)Refactorización de autoconjunto

[Test] 
public void should_create_instance_with_correct_ctor_parameters() 
{ 
    var carrierMovements = new List<CarrierMovement>(); 

    var deparureUnLocode1 = new UnLocode("AB44D"); 
    var departureLocation1 = new Location(deparureUnLocode1, "HAMBOURG"); 
    var arrivalUnLocode1 = new UnLocode("XX44D"); 
    var arrivalLocation1 = new Location(arrivalUnLocode1, "TUNIS"); 
    var departureDate1 = new DateTime(2010, 3, 15); 
    var arrivalDate1 = new DateTime(2010, 5, 12); 

    var carrierMovement1 = new CarrierMovement(departureLocation1, arrivalLocation1, departureDate1, arrivalDate1); 

    var deparureUnLocode2 = new UnLocode("CXRET"); 
    var departureLocation2 = new Location(deparureUnLocode2, "GDANSK"); 
    var arrivalUnLocode2 = new UnLocode("ZEZD4"); 
    var arrivalLocation2 = new Location(arrivalUnLocode2, "LE HAVRE"); 
    var departureDate2 = new DateTime(2010, 3, 18); 
    var arrivalDate2 = new DateTime(2010, 3, 31); 

    var carrierMovement2 = new CarrierMovement(departureLocation2, arrivalLocation2, departureDate2, arrivalDate2); 

    carrierMovements.Add(carrierMovement1); 
    carrierMovements.Add(carrierMovement2); 

    new Schedule(carrierMovements).ShouldNotBeNull(); 
} 

Así es como he tratado de refactorearlo con AutoFixture

[Test] 
public void should_create_instance_with_correct_ctor_parameters_AutoFixture() 
{ 
    var fixture = new Fixture(); 

    fixture.Register(() => new UnLocode(UnLocodeString())); 

    var departureLoc = fixture.CreateAnonymous<Location>(); 
    var arrivalLoc = fixture.CreateAnonymous<Location>(); 
    var departureDateTime = fixture.CreateAnonymous<DateTime>(); 
    var arrivalDateTime = fixture.CreateAnonymous<DateTime>(); 

    fixture.Register<Location, Location, DateTime, DateTime, CarrierMovement>(
     (departure, arrival, departureTime, arrivalTime) => new CarrierMovement(departureLoc, arrivalLoc, departureDateTime, arrivalDateTime)); 

    var carrierMovements = fixture.CreateMany<CarrierMovement>(50).ToList(); 

    fixture.Register<List<CarrierMovement>, Schedule>((carrierM) => new Schedule(carrierMovements)); 

    var schedule = fixture.CreateAnonymous<Schedule>(); 

    schedule.ShouldNotBeNull(); 
} 

private static string UnLocodeString() 
{ 
    var stringBuilder = new StringBuilder(); 

    for (int i = 0; i < 5; i++) 
     stringBuilder.Append(GetRandomUpperCaseCharacter(i)); 

    return stringBuilder.ToString(); 
} 

private static char GetRandomUpperCaseCharacter(int seed) 
{ 
    return ((char)((short)'A' + new Random(seed).Next(26))); 
} 

me gustaría saber si hay una mejor manera de refactorizarlo. Me gustaría hacerlo más corto y más fácil que eso.

Respuesta

14

Su primer intento parece bueno, pero hay al menos un par de cosas que puede simplificar un poco.

En primer lugar, usted debe ser capaz de reducir esto:

fixture.Register<Location, Location, DateTime, DateTime, CarrierMovement>(
    (departure, arrival, departureTime, arrivalTime) => 
     new CarrierMovement(departureLoc, arrivalLoc, departureDateTime, arrivalDateTime)); 

a esto:

fixture.Register<Location, Location, DateTime, DateTime, CarrierMovement>(
    () => new CarrierMovement(departureLoc, arrivalLoc, departureDateTime, arrivalDateTime)); 

ya que no está usando esas otras variables. Sin embargo, esto bloquea esencialmente cualquier creación de CarrierMovement para usar los mismos cuatro valores. Aunque cada CarrierMovement creado será una instancia separada, todos compartirán los mismos cuatro valores, y me pregunto si eso fue lo que quiso decir.

En la misma línea que el anterior, en lugar de

fixture.Register<List<CarrierMovement>, Schedule>((carrierM) => 
    new Schedule(carrierMovements)); 

puede escribir

fixture.Register(() => new Schedule(carrierMovements)); 

ya que no utiliza la variable carrierM. Tipo de inferencia se dará cuenta de que está registrando un Horario debido al tipo de devolución del Func.

Sin embargo, en el supuesto de que el constructor horario es el siguiente:

public Schedule(IEnumerable<CarrierMovement> carrierMovements) 

en su lugar podría haber acaba de registrar la carrierMovements así:

fixture.Register<IEnumerable<CarrierMovement>>(carrierMovements); 

cual causaría AutoFixture para resolver automáticamente Horario correctamente. Este enfoque es más fácil de mantener porque le permite agregar un parámetro al constructor Schedule en el futuro sin romper la prueba (siempre que AutoFixture pueda resolver el tipo de parámetro).

Sin embargo, podemos hacerlo mejor que eso en este caso porque realmente no usamos la variable carrierMovements para nada más que el registro. Lo que realmente necesitamos hacer es decirle a AutoFixture cómo crear instancias de IEnumerable<CarrierMovement>. Si no se preocupan por el número 50 (no debería), incluso podemos usar la sintaxis de grupo Método de la siguiente manera:

fixture.Register(fixture.CreateMany<CarrierMovement>); 

Aviso parantheses la falta de invocación de método: estamos registrando un Func, y dado que el método CreateMany<T> devuelve IEnumerable<T>, la inferencia de tipos se ocupa del resto.

Sin embargo, esos son todos los detalles.En un nivel superior, es posible que desee considerar no registrar CarrierMovement en absoluto. Suponiendo que este constructor:

public CarrierMovement(Location departureLocation, 
    Location arrivalLocation, 
    DateTime departureTime, 
    DateTime arrivalTime) 

autofixture debe ser capaz de averiguar por sí mismo.

Se creará una nueva instancia de ubicación para cada ubicación de salida y llegada, pero eso no es diferente de lo que hizo manualmente en la prueba original.

Cuando se trata de los tiempos, de forma predeterminada AutoFixture utiliza DateTime.Now, que al menos garantiza que la hora de llegada nunca será antes de la hora de salida. Sin embargo, es muy probable que sean idénticos, pero siempre puede registrar una función de autoincremento si eso es un problema.

Teniendo en cuenta estas consideraciones, aquí es una alternativa:

public void should_create_instance_with_correct_ctor_parameters_AutoFixture() 
{ 
    var fixture = new Fixture(); 

    fixture.Register(() => new UnLocode(UnLocodeString())); 

    fixture.Register(fixture.CreateMany<CarrierMovement>); 

    var schedule = fixture.CreateAnonymous<Schedule>(); 

    schedule.ShouldNotBeNull(); 
} 

Para resolver el problema con IList<CarrierMovement> tendrá que registrarlo. He aquí una manera de hacerlo:

fixture.Register<IList<CarrierMovement>>(() => 
    fixture.CreateMany<CarrierMovement>().ToList()); 

Sin embargo, ya que preguntas, quiero decir que el constructor horario es el siguiente:

public Schedule(IList<CarrierMovement> carrierMovements) 

y realmente creo que debería reconsiderar el cambio de esa API para tomar una IEnumerable<Carriemovement>. Desde una perspectiva de diseño de API, el suministro de una colección a través de cualquier miembro (incluido un constructor) implica que el miembro puede modificar la colección (por ejemplo, invocando los métodos Agregar, Eliminar y Borrar). Ese no es el comportamiento que esperarías de un constructor, así que no lo permitas.

AutoFixture generará automáticamente nuevos valores para todos los objetos Location en mi ejemplo anterior, pero debido a la velocidad de la CPU, las instancias posteriores de DateTime probablemente sean idénticas.

Si desea aumentar DateTimes, puede escribir una clase pequeña que incremente el DateTime devuelto cada vez que se invoca. Voy a dejar la aplicación de esa clase para el lector interesado, pero luego pude registrarlo como tal:

var dtg = new DateTimeGenerator(); 
fixture.Register(dtg.Next); 

asumiendo esta API (aviso una vez más la sintaxis Grupo método anterior):

public class DateTimeGenerator 
{ 
    public DateTime Next(); 
} 
+0

Gracias por tus comentarios. Sin embargo, tengo una pequeña excepción lanzada por AutoFixture Ploeh.AutoFixture.ObjectCreationException: AutoFixture no pudo crear una instancia de tipo System.Collections.Generic.IList'1 [DDDBookingApplication.Domain.Voyage.CarrierMovement], ya que no tiene ningún público constructor. Supongo que debería decir cómo crear CarrierMovement? –

+0

También me gustaría tener diferentes conjuntos de datos para todos los instancias. ¿Cuáles son tus pensamientos? –

+0

Gracias por todos los detalles. La prueba es corta ahora y pasa :) –