2012-01-12 10 views
10

¿Cómo maneja los datos ficticios utilizados para las pruebas? Mantenerlos con sus respectivas entidades? En un proyecto de prueba por separado? Cargarlos con un serializador de recursos externos? ¿O simplemente recrearlos donde sea necesario?Estrategias ficticias de prueba de datos y unidades en una pila de aplicaciones modulares

Tenemos una pila de aplicaciones con varios módulos que dependen de otra y cada una contiene entidades. Cada módulo tiene sus propias pruebas y necesita datos ficticios para ejecutar.

Ahora un módulo que tiene muchas dependencias necesitará una gran cantidad de datos ficticios de los otros módulos. Sin embargo, aquellos no publican sus objetos ficticios porque son parte de los recursos de prueba, por lo que todos los módulos tienen que configurar todos los objetos ficticios que necesiten una y otra vez.

también: la mayoría de los campos en nuestras entidades no están nullable por lo que incluso las transacciones que se ejecutan contra la capa de objetos les obliga a contener algún valor, la mayor parte del tiempo con más limitaciones como singularidad, longitud, etc.

¿Existe una la mejor forma de salir de esto o todos los compromisos de soluciones?


Más Detalle

Nuestra pila es como la siguiente:

un módulo:

src/main/java --> gets jared (.../entities/*.java contains the entities) 
src/main/resources --> gets jared 
src/test/java --> contains dummy object setup, will NOT get jared 
src/test/resources --> not jared 

Utilizamos Maven para manejar dependencias. ejemplo

módulo:

  • Módulo A tiene algunos objetos ficticios
  • Módulo B necesita sus propios objetos y el mismo que el Módulo A

Opción A)

Un módulo de prueba T puede contener todos los objetos ficticios y proporcionarlos en un ámbito de prueba (para que las dependencias cargadas no sean jared) a todas las pruebas en todos los módulos. ¿Eso funcionará? Significado: Si se me carga T en Un y corro instalar en Un será que no contiene referencias introducidas por T sobre todo no B? Luego, sin embargo, A sabrá sobre B's datamodel.

Opción b)

Módulo A proporciona los objetos ficticios en algún lugar de src/main/java../entities/dummy permitiendo B para conseguirlos, mientras Un no sabe nada de datos ficticios B 's

Opción c)

Cada módulo contiene recursos externos que son objetos ficticios serializados. Pueden ser deserializados por el entorno de prueba que los necesita porque tiene la dependencia del módulo al que pertenecen. Sin embargo, esto requerirá que cada módulo cree y serialice sus objetos ficticios y ¿cómo lo haría? Si con otra prueba unitaria introduce dependencias entre pruebas unitarias que nunca deberían ocurrir o con un script, será difícil de depurar y no será flexible.

Opción d)

usen una estructura simulada y asignar manualmente los campos requeridos para cada prueba según sea necesario. El problema aquí es que la mayoría de los campos en nuestras entidades no aceptan nulos y, por lo tanto, requerirán que se llamen a los instaladores o constructores, lo que nos haría volver a empezar desde el principio.

Lo que no queremos

No queremos configurar una base de datos estática con datos estáticos como la estructura de los objetos requeridos cambiará constantemente. Mucho en este momento, un poco más tarde. Por lo tanto, queremos que hibernate configure todas las tablas y columnas y las complete con datos en el tiempo de prueba de la unidad. Además, una base de datos estática introduciría muchos errores potenciales e interdependencias de prueba.


¿Mis pensamientos van en la dirección correcta? ¿Cuál es la mejor práctica para hacer frente a las pruebas que requieren una gran cantidad de datos? Tendremos varios módulos interdependientes que requerirán objetos llenos con algún tipo de datos de varios otros módulos.


EDITAR

Algunos más información sobre cómo lo estamos haciendo ahora mismo en respuesta a la segunda respuesta:

Así, por simplicidad, tenemos tres módulos: Person, Product, Order. Person pondrá a prueba algunos métodos Manager utilizando un objeto MockPerson:

(en persona/src/test/java :)

public class MockPerson { 

    public Person mockPerson(parameters...) { 
     return mockedPerson; 
    } 
} 

public class TestPerson() { 
    @Inject 
    private MockPerson mockPerson; 
    public testCreate() { 
     Person person = mockPerson.mockPerson(...); 
     // Asserts... 
    } 
} 

no será empaquetado La clase MockPerson.

Lo mismo se aplica para los ensayos sobre el producto:

(en producto/src/test/java :) se necesita

public class MockProduct() { ... } 
public class TestProduct { 
    @Inject 
    private MockProduct mockProduct; 
    // ... 
} 

MockProduct pero no serán empaquetados.

Ahora las Pruebas Solicitar requerirá MockPerson y MockProduct, por lo que ahora necesitan actualmente para crear ambos, así como para poner a prueba MockOrderOrder.

(en orden /src/test/java :)

Estos son duplicados y tendrá que ser cambiado cada vez que cambia Person o Product

public class MockProduct() { ... } 
public class MockPerson() { ... } 

Ésta es la única clase que debería estar aquí:

public class MockOrder() { ... } 

public class TestOrder() { 
    @Inject 
    private order.MockPerson mockPerson; 
    @Inject 
    private order.MockProduct mockProduct; 
    @Inject 
    private order.MockOrder mockOrder; 
    public testCreate() { 

     Order order = mockOrder.mockOrder(mockPerson.mockPerson(), mockProduct.mockProduct()); 
     // Asserts... 
    } 
} 

El probl es decir, que ahora tenemos que actualizar person.MockPerson y order.MockPerson siempre que se cambie Person.

¿No es mejor simplemente publicar los Mocks con el contenedor para que cualquier otra prueba que tenga la dependencia de todos modos pueda simplemente llamar a Mock.mock y obtener un objeto de configuración agradable? ¿O es este el lado oscuro, el camino fácil?

Respuesta

3

Esto puede aplicarse o no - Tengo curiosidad por ver un ejemplo de sus objetos ficticios y el código de instalación relacionado. (Para tener una mejor idea de si se aplica a su situación). Pero lo que hice en el pasado ni siquiera es introducir este tipo de código en las pruebas. Como usted describe, es difícil producir, depurar y especialmente empaquetar y mantener.

Lo que he hecho usaully (y AFAIKT en Java esta es la mejores prácticas) es tratar de utilizar la Carta de ajuste de datos del constructor, según lo descrito por Nat Pryce en su Test Data Builders puesto.

Si usted piensa que esto es algo relevante, consulta los siguientes:

+0

¡Hola cwash! Gracias por el puntero de fábrica. Recuerdo usar eso en un tutorial de rieles;) Esta podría ser la solución, tendré que verificarlo más. – Pete

+0

@Pete - Genial, ¿puedes dejarnos una nota/actualización para decirnos qué has decidido? – cwash

+0

Por lo tanto, parece que podemos usarlo como un proveedor de datos falsos central. Al final, sin embargo, es solo la Opción a) lo que significa que algún proyecto externo contendrá todos los generadores de datos requeridos. Todavía me pregunto si esa es la mejor manera de hacerlo. Esperando obtener más opiniones sobre esto ... – Pete

1

Me pregunto si no puede resolver su problema cambiando su enfoque de prueba.

Unidad Prueba de un módulo que depende de otros módulos y, por lo tanto, en los datos de prueba de otros módulos no es una prueba de unidad real!

¿Qué pasa si se inyecta un simulacro para todas las dependencias de su módulo bajo prueba para que pueda probarlo en completo aislamiento. Entonces no necesita configurar un entorno completo donde cada módulo dependiente tenga los datos que necesita, solo configura los datos para el módulo que realmente está probando.

Si imagina una pirámide, entonces la base sería su unidad de pruebas, encima de eso tiene pruebas funcionales y en la parte superior tiene algunas pruebas de escenario (o como Google las llama, pruebas pequeñas, medianas y grandes).

Dispondrá de una gran cantidad de pruebas unitarias que pueden probar cada ruta de código porque las dependencias falsas son completamente configurables. Entonces puede confiar en sus partes individuales y lo único que harán sus pruebas funcionales y de escenarios es probar si cada módulo está conectado correctamente a otros módulos.

Esto significa que los datos de prueba de su módulo no son compartidos por todas las pruebas, sino solo por unos pocos que están agrupados.

El patrón de generador mencionado por cwash definitivamente ayudará en sus pruebas funcionales. Estamos usando un .NET Builder que está configurado para construir un árbol de objetos completo y generar valores predeterminados para cada propiedad, por lo que cuando lo guardamos en la base de datos, todos los datos requeridos están presentes.

+0

Gracias por su respuesta. ¿Puedes proporcionar algún pseudo código para una mejor comprensión? Al menos para la parte de prueba de la unidad. No estamos haciendo pruebas funcionales y de escenarios todavía. Por lo que te entiendo, estamos haciendo justo lo que estás diciendo en este momento. Cada módulo tiene clases simuladas que proporcionan datos configurables y se inyectan en las suites de prueba. Luego, cada prueba llama al objeto simulado para crear los datos requeridos. Proporcionaré un código fuente y más información para alinear nuestro enfoque e inquietudes con más detalle, de modo que pueda verificar si eso es de lo que está hablando. – Pete

+0

@Pete No soy desarrollador de Java, así que podría explicarme por qué se refiere con: 'El problema es que ahora tenemos que actualizar person.MockPerson y order.MockPerson cada vez que se cambie Person'. ¿Qué tienes que cambiar? ¿No te burlas solo de las propiedades y métodos que son importantes? –

+0

Tal vez mi pregunta es demasiado larga en general, por lo que es difícil elegir los detalles necesarios. Lo siento ... describí cómo tenemos que simular todos los campos, ya que la mayoría son 'nullable = false'. Entonces, si agrego una nueva columna a la entidad 'Persona ', tendré que agregar la burla a todas las ocurrencias de MockPerson en cada módulo. – Pete

3

Bueno, he leído cuidadosamente todas las evaluaciones hasta ahora, y es una muy buena pregunta. Veo los siguientes acercamientos al problema:

  1. Configurar base de datos de prueba (estática);
  2. Cada prueba tiene sus propios datos de configuración que crean datos (dinámicos) de prueba antes de ejecutar pruebas unitarias;
  3. Use un objeto simulado o falso. Todos los módulos conocen todos los objetos ficticios, de esta manera no hay duplicados;
  4. Reduce el alcance de la prueba de la unidad;

primera opción es bastante sencillo y tiene muchos inconvenientes, alguien tiene que reproducirse es de vez en cuando, cuando las pruebas de unidad "estropearlo", si hay cambios en el módulo de datos, alguien tiene que introducir las correspondientes cambios en los datos de prueba, una gran cantidad de gastos generales de mantenimiento. No quiere decir que la generación de estos datos en primera mano sea engañosa. Ver también la segunda opción.

Segunda opción, escribe el código de prueba que antes de la prueba invoca algunos de sus métodos de negocio "centrales" que crean su entidad. Idealmente, su código de prueba debe ser independiente del código de producción, pero en este caso, terminará con código duplicado, que debe admitir dos veces. A veces, es bueno dividir su método de negocio de producción para tener un punto de entrada para la prueba de su unidad (hago esos métodos privados y uso Reflection para invocarlos, también se necesita alguna observación sobre el método, la refactorización es ahora un poco complicada) . El principal inconveniente es que si debe cambiar sus métodos de negocio "principales", de repente afecta a toda la prueba de su unidad y no puede realizar la prueba. Por lo tanto, los desarrolladores deben ser conscientes de ello y no comprometerse parciales con los métodos comerciales "centrales", a menos que funcionen. Además, con cualquier cambio en esta área, debe tener presente "cómo afectará mi prueba de unidad". A veces, también, es imposible reproducir todos los datos requeridos de forma dinámica (generalmente, es debido a la API de terceros, por ejemplo, si llama a otra aplicación con su propia base de datos desde la cual necesita usar algunas teclas. los datos asociados) se crean manualmente a través de una aplicación de terceros. En tal caso, estos datos y solo estos datos deben crearse estáticamente. Por ejemplo, sus 10000 claves creadas a partir de 300000.

La tercera opción debe ser buena Las opciones a) yd) suenan bastante bien para mí. Para su objeto ficticio puede usar el marco simulado o no puede usarlo. Mock Framework está aquí solo para ayudarte. No veo problema de que toda su unidad conozca todas sus entidades.

Cuarta opción significa que debe redefinir qué es "unidad" en la prueba de su unidad.Cuando tienes un par de módulos con interdependencia, puede ser difícil probar cada módulo de forma aislada. Este enfoque dice que lo que originalmente probamos fue prueba de integración y no prueba de unidad. Entonces, dividimos nuestros métodos, extraemos pequeñas "unidades de obras" que reciben todas sus interdependencias con otros módulos como parámetros. Estos parámetros pueden ser (con suerte) fácilmente burlados. El principal inconveniente de este enfoque es que no prueba todo su código, sino solo los "puntos focales". Debe realizar la prueba de integración por separado (generalmente por el equipo de control de calidad).

+0

¡Oye! Gracias por el aporte. Especialmente la 4ta opción fue interesante. Tienes razón, lo que hacemos no es realmente pruebas unitarias, sino pruebas de integración, ya que invocamos funciones de administrador de módulos inferiores para crear objetos necesarios en una prueba unitaria en un módulo superior, probando también implícitamente parte de la funcionalidad del módulo inferior. Me doy cuenta de que esto hace que las pruebas no sean estrictamente independientes. Creo que pensamos: bueno, entonces aumentará la densidad de prueba para todos los módulos. Creo que debería leer sobre la utilidad de las pruebas unitarias contra las pruebas de integración ... – Pete

+1

@ Pete ese es exactamente el punto. Debe asegurarse de que puede probar sus funciones de forma aislada. Burlarse de cada dependencia y usar un generador para generar datos de prueba. También asegúrese de tener en cuenta la Ley de Demeter (no pasar a un Cliente completo cuando solo necesita la dirección) porque eso hará que su código sea mucho mejor comprobable. –

Cuestiones relacionadas