Llevándote bien con los Fixtures

Las Factories pueden ayudar a lidiar con las dolorosas etapas tempranas, pero pueden ser dolorosas conforme una aplicación crece. Los Fixtures pueden ser incómodos al inicio, pero brillantes con la complejidad. A su propio modo, ambos hacen más fácil el hacer pruebas. Ninguno es un anti-patrón.

technicalHay una división en el mundo de Rails. Bueno, hay muchas divisiones en el mundo Rails, especialmente acerca de ¿cómo escribir pruebas? Para puntualizar. Los desarrolladores en Rails no están de acuerdo sobre como configurar sus datos de prueba.

Hay dos posturas en este debate. Los Fixtures vienen con Rails por defecto. Estos son le método que eligió el equipo del Núcleo de Rails y DHH los respalda. En la otra esquina tenemos a la gente que aboga por reemplazar los fixures con gemas para factory como Factory Girl o Fabrication. Ambas estrategias tienen suficientes defensores y detractores como para mantener viva la discusión por muchos años. Recientemente un defensor de las factory fue tan lejos como para afirmar que los fixtures eran un anti-patrón, lo que me hizo pensar en como encajan los fixtures y factories en el libro de las las pruebas.

He estado usando Fabrication como mi generador de datos de prueba como mi elección durante los últimos meses. Antes estuve usando Factory Girl depues de abandonar bastante pronto los fixtures. Hasta la semana pasada, me sentia muy comodo con la generación de objetos de prueba. He estado usando Fabricators activamente en mis pruebas e incluyendolos en algunas pruebas de entranamiento. La última semana, regrese al proyecto de un viejo cliente que estaba soportado con fixtures. Lo primero que pense fue re-escribir los test para usar fabricators, pero simplemente no habia tiempo para hecerlo. A regañadientes, he vuelto a usar fixtures para mis pruebas.

La semana incio algo lenta como si tratara de adaptar los nuevos fixtures a mi flujo de trabajo con fabricator, pero despues de un sorpendentemente corto periodo de adaptación, realmente lo estaba disfrutando. No estaba preocupado de crear el paquete correcto de objetos para pruebas complejas. Todo lo que necesitaba estaba justo ahí, esperando a ser probado. Esperaba revertirlo en el transcurso de la semana, pero no creo que sonsidere cambiar a usar fixtures con regularidad. Esto me hizo pensar de nuevo en el debate fixture vs factory con una nueva luz.

Quiero darle otra revisión a las dieferencias entre fixtures y factories con una nueva óptca.

Definiciones

Los fixtures son definidos en archivos yaml. Generalmente lucen algo como esto:

bond:
  first_name: James
  last_name: Bond
  email: jamesbond007@mi6.gov.uk

bourne:
  first_name: Jason
  last_name: Bourne
  email: jbourne@cia.gov

Las factories (Fabricators en este caso) se definen algo así:

Fabricator :agent do
  first_name { Faker::Name.first_name }
  last_name { Faker::Name.last_name }
  email { Faker::Internet.email }
end

La vía de los fixtures consiste en escribir múltiples definiciones de el mismo tipo de objeto. Las factories son en realidad plantillas de objetos, así que terminan buscando mucho más que las definiciones de tablas en db/schema.rb. Los fixtures promueven la repetición y trabajan mejor con algunos datos de prueba creativos y dignos de recordar. Las factories no te hacen pensar o reiterarte.

Escribir fixures parece algo tedioso cuando te fijas en ellos a lo ancho y largo, especialmente cuando estas usando una gema para generación de datos, como Faker, con factories. Definir datos para un solo tipo de modelos es claramente una victoria para las factories.

Creando objetos

Las factories crean nuevos objetos bajo demanda en las pruebas. Las pruebas usando factories inician con un borrón y cuenta nueva en cada ocasión, esperando a que preguntes por un tipo de objeto específico. Los fixtures tienen una aproximación diferente. Cuando arranca el entorno de pruebas, una de las primeras cosas que hace es leer todos las definiciones en los fixtures e insertarlas directamente en la base de datos, sin discriminarlas. Los fixtures se saltan el proceso completo de creación de objetos, evitando validaciones y encadenamientos.

Debido a la forma en que son cargados, los fixtures son ridículamente rápidos, pero no pueden ser cambiados. Si un objeto con cierto tipo de datos de prueba es necesario para una prueba, este necesita ser definido en un archivo de fixture (o creado con un Model.new). Esto es especialmente frustrarte durante etapas tempranas del desarrollo como la concreción de modelos de datos y requerimientos de aplicación.

Debido a la forma en que las factories son creadas, estas tienen la ventaja de la flexibilidad. Si te percatas de que en un punto en una prueba necesitas un cierto tipo de datos, fácilmente puedes sobre-escribir la definición de una factory para llenar las necesidades de la prueba. Esto es especialmente útil en una nueva aplicación donde aun estamos entendiendo como necesitan ser estructuradas nuestras pruebas.

test 'on_assignment collects agents on active missions' do
  agent_on_assignment = Fabricate(:agent, mission: Fabricate(:active_mission))
  agent_not_on_assignment = Fabricate :agent
  assert_includes Agent.on_assignment, agent_on_assignment
  refute_includes Agent.on_assignment, agent_not_on_assignment
end

En esta prueba, solamente necesitamos una factory predefinida para Mission y Agent, y manejar los detalles en la prueba. No necesitamos ir a otro archivo para establecer la definición. Si necesitamos un agente con una misión, entonces podemos extraer esto a su propio factory :agent_with_active_mission. Este tipo de flexibilidad da resultados con anticipación.

Desafortundamente, hay un impacto en el rendimiento con las factories. Las factories necesitan crear un objeto entero y posiblemente guardarlos en la base de datos para tenerlo disponible para la prueba. Esto se amontona conforme necesitas más objetos en las pruebas. pero como efecto colateral favorece las pruebas de forma aislada.

La misma prueba usando fixtures puede lucir algo como esto:

test 'on_assignment collects agents on active missions' do
  agents(:bourne).missions.clear
  assert_includes Agent.on_assignment, agents(:bond)
  refute_includes Agent.on_assignment, agents(:bourne)
 end

Necesitamos asegurar que el fixture del agente :bond esta asociado con una misión. Para hacer esto, tenemos que buscar entre los archivos de fixtures por los agentes y misiones. También podemos asumir que el fixture del agente :bourne debe tener misiones asociadas con el, así que debemos transparentar esa asociación.

Cuando de flexibilidad y obviedad se trata, las factories tienen claras ventajas con su habilidad para crear objetos dinámicamente cuando los necesitemos.

La desventaja de las Factories

Muchas de las comparaciones que he viso entre fixtures y factories usan ejemplos de una aplicación que esta en etapas muy tempranas de desarrollo en pruebas individuales. Las factories abordan el problema del no saber como deben lucir los datos de prueba. Ellas te permiten tomar decisiones acerca de los datos de prueba cuando este mucho más esclarecido lo que necesitas: en la prueba.

Esta ventaja temprana lentamente se convierte en una desventaja conforme la aplicación crece. Las factories crean objetos, propiciando el que los desarrolladores prefieran pruebas con objetos y datos tan reducidos como sea posible. Existe una carga asociada con la agregación de nuevos datos y objetos de prueba. Las pruebas toman más tiempo y nuevas variantes de datos necesitan ser añadidas. Las factories empiezan construir objetos con asociaciones y dependencias para probar escenarios de complejidad creciente. La simplicidad de las factories nos dieron al inicio pueden terminar por fatigar una aplicación más compleja.

Es semejante al problema de la última milla con el internet de banda ancha. Escribir esas primeras pruebas resulta mucho más fácil con factories, pero la cantidad de esfuerzo necesario para cubrir pruebas complejas crece en el tiempo. Este no es un factor decisivo pero es un tanto irritante.

Los fixtures despues de la luna de miel

Ya nos examinamos los aspectos negativos del uso de fixtures. Se termina por experimentar algo de dolor desde el principio por tener que evolucionar las pruebas con datos redundantes y buenos sin la ayuda de un generador. Tienes que saltar entre las pruebas y los fixtures a menudo cuando se trata de las necesidades cambiantes. Mantener ese sobre-esfuerzo a lo largo del proyecto es intimidante.

Pero aquí esta la cosa acerca de los fixtures: se vuelven más fáciles conforme pasa el tiempo. Iniciar con los fixtures es un tanto doloroso, pero puede ser condenadamente agradable una vez que se establecen los requerimientos de datos. He encontrado que los fixtures pueden ser mucho mejores si les entras con estas cosas en mente:

Escribir al menos tres fixtures para cada modelo.

Si. Debes escribir tus propios datos de prueba. Como desarrollador, esto apesta porque sabemos que no lo tenemos que hacer. Simplemente se siente mal. Pero si lidias con tus propios sentimientos de frustración, pasan cosas divertidas. Tus datos de prueba empiezan a formar una narrativa. Una historia toma forma al rededor de tus modelos, enlazas objetos logicamente como si estuviesen en tu aplicación. Con las factories, estás construyendo moldes que pueden llenarse con plastico no descriptivo, pero con fixtures, contruiras algo a mano con mayor detalle.Los fixtures te dan la oportundad de dejas de pensar en tu aplicación como un puñado de clases y la imaginas en el mundo real. Esto realmente ayuda

Abraza el cargar todo previamente.

Tres fixtures por cada modelo pareec una exajeración cuando empezas, pero rápidamente sientes que no es suficiente para representar diferentes tipos de datos.

No lo es.

Cuando estas trabajando con factories, terminas por definir varios objetos diferentes para manejar diferentes situaciones. Este es un efecto colateral de las factories iniciando cada prueba con borrón y cuenta nueva. Si necesitas a menudo un objeto usuario que tiene multples objetos compra, necesitas definir una única factory que los construya.

Si puedes abrazar la forma en que Rails carga los fixtures antes de probar, tendrías acceso a las mas complejas relaciones con mínimo esfuerzo. Las factories requiren que inicies con datos de prueba pequeños y aislados y agregas factories más complejas de forma incremental. Los fixtures, por otro lado, se espera que sean tan complejos como sea posible, tan pronto como sea posible. Puedes hacer esto simplemente asegurando que los objetos asociados estan conectados.

Así es, tendrás que mantener tus datos actualizados.

Una vez que tienes el mismo tipo de cosa definida al menos tres veces, probablemente necesitarás implementar cambos a los datos al menos tres veces. Nuevamente, como desarrolladores, esto se siente como si no tuviesemos porque hacer esto. Se siente como un gasto de tiempo.

Tambien podemos recibir una dosis concentrada muy pronto en la vida de la aplicación conforme nuestros modelos tomen forma al rededor de requerimientos cambiantes.

Si trabajas en superar esta incomodidad inicial, no sera tan malo como pienzas. Miigraciones y cambios de datos pasan mucho al inicio, pero van disminuyendo rápidamente conforme una aplicación va creciendo.

No te quedes atascado con los nombres

Las factories están diseñadas para ser plantillas de diferentes representaciones de datos. Las factories tienen finalmente un montón de nombres para recordar los muchos tipos de datos. ¿Necesitas un usuario que tenga múltiples ordenes? Probablemente terminaras teniendo una factory :user_with_multiple_orders para obtener justo lo que necesitas.

¡No! No con fixtures. Tu limitas el número de objetos en el fixture para cumplir con varios roles diferentes, por eso no tendremos una semántica de nombrado de lujo. Simplemente dale a esos objetos nombres cortos, únicos y fáciles de recordar y llamar algún día.

No te olvides del Object.new

Los fixtures se saltan el ciclo entero de creación de objetos. Cuando estas probando validaciones o llamandas en Rails, simplemente creas un nuevo objeto del modelo con la de de un fixture, algo como:

test 'should require a unique code_name' do
  duplicate_mission = Mission.new(missions(:grand_slam).attributes)
  assert_invalid Mission.new, code_name: "can't be blank"
  assert_invalid duplicate_mission, code_name: 'has already been taken'
end

(assert_invalid es una aserción personalizada declarada en test_helper.rb)

Crear un nuevo objeto de un modelo es también una buena forma de manejar variantes únicas de tus datos que serán irremediablemente necesarios.

Entonces ¿Fixtures o Factories?

guerra-quesosNo pienso que vaya ha haber una resolución a la discusión de Fixtures vs. Factories. Ambas son increíblemente útiles y tienen sus propias ventajas y desventajas. Conocer las limitaciones de cada una te ayudará a escoger la mejor herramienta para el trabajo.

Me estoy moviendo de regreso a fixtures despues de darles otra oportunidad, pero puedo ser perfectamente feliz en un proyecto con factories. Es bueno tener opciones.

Esta es una traducción del artículo original: [Getting Friendly with Fixtures] (https://whatdoitest.com/getting-friendly-with-fixtures)

Responder

Introduce tus datos o haz clic en un icono para iniciar sesión:

Logo de WordPress.com

Estás comentando usando tu cuenta de WordPress.com. Cerrar sesión / Cambiar )

Imagen de Twitter

Estás comentando usando tu cuenta de Twitter. Cerrar sesión / Cambiar )

Foto de Facebook

Estás comentando usando tu cuenta de Facebook. Cerrar sesión / Cambiar )

Google+ photo

Estás comentando usando tu cuenta de Google+. Cerrar sesión / Cambiar )

Conectando a %s