Comencemos con un ejemplo.
Digamos que tengo un método llamado export
que depende en gran medida del esquema DB. Y por "depende en gran medida" quiero decir que sé que agregar una nueva columna a una tabla determinada a menudo (muy a menudo) conduce al export
cambio de método correspondiente (por lo general, también debe agregar el nuevo campo a los datos de exportación).
Los programadores a menudo se olvidan de cambiar el export
método, ya que no está muy claro que incluso debas mirar esto. Mi objetivo es obligar al programador a tomar una decisión explícita para determinar si olvidó mirar el export
método o simplemente no quiere agregar un campo a los datos de exportación. Y estoy buscando la solución de diseño para este problema.
Tengo dos ideas, pero ambas tienen fallas.
Contenedor inteligente "Leer todo"
Puedo crear el contenedor inteligente que se asegura de que todos los datos se lean explícitamente.
Algo como esto:
def export():
checker = AllReadChecker.new(table_row)
name = checker.get('name')
surname = checker.get('surname')
checker.ignore('age') # explicitly ignore the "age" field
result = [name, surname] # or whatever
checker.check_now() # check all is read
return result
Entonces, checker
afirma si table_row
contiene otros campos que no fueron leídos. Pero todo esto parece un poco pesado y (tal vez) afecta el rendimiento.
Prueba de unidad "Verificar ese método"
Solo puedo crear la prueba de unidad que recuerda el último esquema de tabla y falla cada vez que se cambia la tabla. En ese caso, el programador vería algo así como "no olvides revisar el export
método". Para ocultar el programador de advertencia, (o no lo haría, eso es un problema) verifique export
y corrija manualmente la prueba (ese es otro problema) agregando nuevos campos.
Tengo algunas otras ideas, pero son demasiado problemáticas para implementar o demasiado difíciles de entender (y no quiero que el proyecto se convierta en un rompecabezas).
El problema anterior es solo un ejemplo de la clase más amplia de problemas que encuentro de vez en cuando. Quiero vincular algunas piezas de código y / o infraestructura, por lo que cambiar una de ellas alerta inmediatamente al programador para que compruebe otra. Por lo general, tiene algunas herramientas simples, como extraer una lógica común o escribir pruebas unitarias confiables, pero estoy buscando la herramienta para casos más complejos: quizás conozco algunos patrones de diseño.
fuente
export
según el esquema?export
tiene todo lo que realmente necesita?Respuestas:
Está en el camino correcto con su idea de prueba unitaria, pero su implementación es incorrecta.
Si
export
se relaciona con el esquema y el esquema cambió, hay dos casos posibles:O
export
bien, todavía funciona perfectamente bien, ya que no se vio afectado por un ligero cambio en el esquema,O se rompe.
En ambos casos, el objetivo de la compilación es rastrear esta posible regresión. Un conjunto de pruebas, ya sean pruebas de integración, pruebas de sistema, pruebas funcionales u otra cosa, aseguran que su
export
procedimiento funcione con el esquema actual , independientemente del hecho de que haya cambiado o no desde la confirmación anterior. Si esas pruebas pasan, genial. Si fallan, esta es una señal para el desarrollador de que puede haberse perdido algo, y una clara indicación de dónde buscar.¿Por qué está mal tu implementación? Bueno, por varias razones.
No tiene nada que ver con las pruebas unitarias ...
... y, en realidad, ni siquiera es una prueba.
La peor parte es que arreglar la "prueba" requiere, bueno, cambiar realmente la "prueba", es decir, hacer una operación que no tiene relación alguna con la
export
.En cambio, al hacer pruebas reales para el
export
procedimiento, se asegura de que el desarrollador arregle elexport
.En términos más generales, cuando se encuentra con una situación en la que un cambio en una clase siempre o por lo general requiere un cambio en una clase completamente diferente, esta es una buena señal de que hizo mal su diseño y está violando el Principio de Responsabilidad Única.
Si bien hablo específicamente sobre las clases, también se aplica más o menos a otras entidades. Por ejemplo, un cambio en el esquema de la base de datos debería reflejarse automáticamente en su código, por ejemplo a través de generadores de código utilizados por muchos ORM, o al menos debería localizarse fácilmente: si agrego
Description
columna a laProduct
tabla y no uso ORM o generadores de código, Al menos espero hacer un solo cambio dentroData.Product
clase del DAL, sin la necesidad de buscar en toda la base de código y encontrar algunas ocurrencias deProduct
clase en, digamos, la capa de presentación.Si no puede restringir razonablemente el cambio a una ubicación (ya sea porque se encuentra en un caso en el que simplemente no funciona o porque requiere una gran cantidad de desarrollo), entonces crea un riesgo de regresiones . Cuando cambio de clase
A
, y la claseB
en algún lugar de la base del código deja de funcionar, es una regresión.Las pruebas reducen el riesgo de regresiones y, lo que es mucho más importante, le muestran la ubicación de una regresión. Es por eso que cuando sabe que los cambios en una ubicación causan problemas en una parte completamente diferente de la base del código, asegúrese de tener suficientes pruebas que generen alarmas tan pronto como aparezca una regresión en este nivel.
En todos los casos, evite confiar en tales casos solo en los comentarios. Algo como:
nunca funciona No solo los desarrolladores no lo leerán en la mayoría de los casos, sino que a menudo terminará eliminado o alejándose de la línea en cuestión y será imposible de entender.
fuente
If you change the following line...
que funciona automáticamente y no se puede ignorar. Nunca he visto a alguien usar esas trampas, de ahí la duda.B
no deja de funcionar, tal vez comienza a funcionar incorrectamente. Pero no puedo predecir el futuro y no puedo escribir pruebas que pronostiquen el futuro, así que trato de recordarle al desarrollador que escriba esa nueva prueba.Me parece que sus cambios no están especificados. Supongamos que vive en un lugar que no tiene códigos postales, por lo que no tiene una columna de código postal en la tabla de direcciones. Luego se introducen los códigos postales, o comienza a tratar con clientes que viven donde hay códigos postales, y debe agregar esta columna a la tabla.
Si el elemento de trabajo solo dice "agregar columna de código postal a la tabla de direcciones", entonces sí, la exportación se interrumpirá, o al menos no exportará códigos postales. Pero, ¿qué pasa con la pantalla de entrada que se utiliza para ingresar códigos postales? ¿El informe que enumera todos los clientes y sus direcciones? Hay toneladas de cosas que deben cambiar cuando agrega esta columna. El trabajo de recordar esas cosas es importante: no debe contar con artefactos de código aleatorio para "atrapar" a los desarrolladores para que recuerden.
Cuando se toma la decisión de agregar una columna significativa (es decir, no solo una búsqueda en caché total o desnormalizada u otro valor que no pertenece a una exportación o informe o en una pantalla de entrada), los elementos de trabajo creados deben incluir TODOS los cambios necesario: agregar la columna, actualizar el script de relleno, actualizar las pruebas, actualizar la exportación, los informes, las pantallas de entrada, etc. Es posible que no todos sean asignados (o recogidos) por la misma persona, pero todos deben hacerse.
A veces, los desarrolladores eligen agregar columnas ellos mismos como parte de la implementación de un cambio mayor. Por ejemplo, alguien puede haber escrito un elemento de trabajo para agregar algo a una pantalla de entrada y un informe, sin pensar en cómo se implementa. Si esto sucede mucho, deberá decidir si su sumador de elementos de trabajo necesita conocer los detalles de implementación (para poder agregar todos los elementos de trabajo correctos) o si los desarrolladores deben saber que el elemento de trabajo- La víbora a veces deja las cosas fuera. Si es lo último, entonces necesita una cultura de "no solo cambie el esquema; deténgase y piense qué más afecta".
Si hubo muchos desarrolladores y esto sucedió más de una vez, configuraría una alerta de registro para que el líder del equipo u otra persona de alto nivel recibiera alertas sobre los cambios de esquema. Esa persona podría buscar elementos de trabajo relacionados para lidiar con las consecuencias del cambio de esquema, y si los elementos de trabajo faltaban, no solo podría agregarlos sino educar a quienes los dejaron fuera del plan.
fuente
Casi siempre, al crear una exportación, también creo una importación correspondiente. Como tengo otras pruebas que pueblan completamente la estructura de datos que se exporta, puedo crear una prueba de unidad de ida y vuelta que compara un original completamente poblado con una copia exportada y luego importada. Si son iguales, entonces la exportación / importación está completa; si no son iguales, la prueba de la unidad falla y sé que el mecanismo de exportación necesita actualizarse.
fuente