2012-02-02 6 views
8

Dada una aplicación fábrica de resumen:Unidad de prueba de una fábrica de resumen que toma parámetros

public class FooFactory : IFooFactory { 
    public IFoo Create(object param1, object param2) { 
     return new Foo(param1, param2); 
    } 
} 

¿Qué pruebas de unidad se escribiría para esta clase? ¿Cómo puedo verificar que param1 y param2 se enviaron a la creación de Foo? ¿Debo hacer estas propiedades públicas de Foo? ¿No sería eso romper la encapsulación? ¿O debería dejar esto para la prueba de integración?

+0

Las pruebas unitarias deben depender de los requisitos funcionales y las expectativas sobre el componente que se está probando. No veo ningún valor de unidad de prueba de esa clase sin el resto del contexto. – ivowiblo

+0

@ivowiblo Creo que está confundiendo las pruebas unitarias con las pruebas de estilo BDD. Si no escribe una prueba para esta unidad, ¿cómo sabe si funciona? –

+0

hacer la prueba de la unidad, pero ¿qué vas a probar? cual es la expectativa para esa unidad? Dije que no veo el valor de hacer una prueba unitaria de esa clase sin saber nada más. Hacer pruebas unitarias solo por hacer pruebas unitarias no tiene ningún sentido. Las pruebas (unidades, comportamientos, funcionalidades completas, lo que usted desee) siempre deben estar guiadas por las expectativas y los requisitos. Si no, es solo esnobismo (si esa palabra existe). – ivowiblo

Respuesta

11

Así es como me gustaría escribir uno de un par de pruebas de unidad para una fábrica de este tipo (con xUnit.net):

[Fact] 
public void CreateReturnsInstanceWithCorrectParam1() 
{ 
    var sut = new FooFactory(); 
    var expected = new object(); 
    var actual = sut.Create(expected, new object()); 
    var concrete = Assert.IsAssignableFrom<Foo>(actual); 
    Assert.Equal(expected, concrete.Object1); 
} 

¿Rompe encapsulación? Sí y no ... un poco. La encapsulación no solo trata sobre el ocultamiento de datos; más importante aún, se trata de protecting the invariants of objects.

Asumamos que Foo expone esta API pública:

public class Foo : IFoo 
{ 
    public Foo(object param1, object param2); 

    public void MethodDefinedByInterface(); 

    public object Object1 { get; } 
} 

Mientras que la propiedad Object1 viola levemente el Law of Demeter, no meterse con los invariantes de la clase porque es de sólo lectura.

Además, la propiedad Object1 es parte de la clase Foo concreto - no la interfaz IFoo:

public interface IFoo 
{ 
    void MethodDefinedByInterface(); 
} 

Una vez que darse cuenta de que en una API de acoplamiento flexible, concrete members are implementation details, tales sólo de hormigón de sólo lectura establecimientos un impacto muy bajo en la encapsulación. Piénsalo de esta manera:

El constructor Foo pública es también una parte de la API de la clase Foo hormigón, por lo que sólo mediante la inspección de la API pública, aprendemos que param1 y param2 son parte de la clase. En cierto sentido, esto ya 'rompe la encapsulación', por lo que hacer que cada parámetro esté disponible como propiedades de solo lectura en la clase concreta no cambia mucho.

Tales propiedades proporcionan la ventaja de que ahora podemos probar la forma estructural de la clase Foo devuelta por la fábrica.

Esto es mucho más fácil que tener que repetir un conjunto de pruebas de unidades de comportamiento que, debemos suponer, ya cubren la clase concreta de Foo. Es casi como una prueba lógica:

  1. De las pruebas de la clase concreta de Foo, sabemos que utiliza correctamente/interactúa con sus parámetros de constructor.
  2. De estas pruebas también sabemos que el parámetro constructor está expuesto como una propiedad de solo lectura.
  3. De una prueba de FooFactory sabemos que devuelve una instancia de la clase concreta de Foo.
  4. Además, sabemos por las pruebas del método Create que sus parámetros se pasan correctamente al constructor Foo.
  5. Q.E.D.
+0

Sí, tiene sentido. Me gusta la idea de diferenciar entre la API pública de la interfaz y la clase concreta. – JontyMC

+0

¿Por qué el voto anónimo? –

0

Puede hacer que FooFactory sea una clase genérica que espera a Foo como parámetro, y especifique que se trata de un simulacro. Llamando a factory.Create (...) luego crea una instancia del simulacro, y luego puede confirmar que el simulacro recibió los argumentos que esperaba.

+2

Estimado Gödel no. Eso es probar un detalle de implementación. – jason

+0

@Jason: Peor ... Se plantea la cuestión de hacer una fábrica para que esa fábrica simplifique la generación del objeto, pero si JontyMC pregunta ... – Arafangion

0

Depende de lo que haga Foo con los 2 objetos de entrada. Busque algo que Foo haga con ellos que pueda ser fácilmente cuestionado. Por ejemplo, si se invocan métodos (incluidos getters o setters) sobre los objetos, proporcione simulaciones o stubs que puede verificar si se han llamado a las cosas esperadas. Si hay algo en el IFoo que puede ser probado en contra, puede pasar los valores conocidos a través de sus objetos simulados para probar contra después de la creación. Los objetos en sí mismos no deberían estar disponibles públicamente para verificar que los hayas aprobado.

También podría verificar que se devuelva el tipo esperado (Foo), dependiendo de si los otros comportamientos evaluados dependen de la diferencia entre las implementaciones IFoo.

Si se producen condiciones de error, intentaré forzarlas también, lo que sucederá si pasa nulo para uno o ambos objetos, etc. Foo se probará por separado, por supuesto, por lo que puede asumir durante una prueba de FooFactory que Foo hace lo que debería.

+0

¿Comprobarías el tipo de devolución? ¿Qué demonios te está comprando? Has configurado todas estas abstracciones y ahora vas a escribir una prueba que se interrumpirá tan pronto como cambies los detalles de implementación. Yikes. – jason

+0

@Jason Si voy a escribir pruebas para varias fábricas que crean una implementación IFoo, verificaría que cada una devuelva el tipo concreto que espero. Cuestiono la necesidad de probar una fábrica que hace tan poco como llamar nueva, pero dentro del ámbito de lo que se pidió ... –

+0

No. No es una conducta de la que los consumidores de la fábrica puedan depender (el tipo de devolución es 'IFoo') entonces no debería ser probado. – jason

0

Las fábricas generalmente no están unitarias, al menos de acuerdo con mi experiencia - no hay mucho valor en la unidad que las prueba. Debido a que es la implementación de ese mapeo de implementación de interfaz, se refiere a la implementación concreta. Como tal, burlarse de Foo no es posible comprobar si recibe esos parámetros pasados.

Por otro lado, normalmente los contenedores DI funcionan como fábricas, por lo que si está utilizando uno, no necesitaría una fábrica.

+0

Se requieren fábricas abstractas cuando una dependencia depende de parámetros conocidos solo en tiempo de ejecución o la duración es menor que la del consumidor: http://stackoverflow.com/questions/1993397/abstract-factory-pattern-on-top- of-ioc – JontyMC

+0

La mayoría de los marcos DI harían el paso de parámetros en tiempo de ejecución. También pueden hacer gestión de por vida. – Aliostad

2

Bueno, presumiblemente esos parámetros hacen que el IFoo devuelto tenga algo de cierto al respecto. Pruebe que eso sea cierto sobre la instancia devuelta.

+0

¿No significaría eso que necesitarías saber sobre las partes internas del IFoo en cuestión? – JontyMC

+0

No. Estás pasando esos parámetros por una razón, para obtener un comportamiento esperado. ¿Cuál es ese comportamiento? Prueba para eso La especificación para 'FooFactory.Create' debería decir algo así como" crea un objeto que implementa 'IFoo' tal que' P (param1, param2) 'donde' P' es un predicado que depende de 'param1' y' param2'. " ¡Prueba que 'P (param1, param2)' es verdadero para el objeto devuelto! – jason

+0

Entonces ... asumiendo que Foo no es trivial y ya está cubierto por N pruebas, esencialmente necesitarás escribir N nuevas pruebas, casi idénticas, para probar que el comportamiento de la instancia de Foo devuelta es consistente con la entrada al Crear método. Parece que estarías indirectamente acoplando las pruebas. Los problemas de mantenimiento seguirán. –

Cuestiones relacionadas