Escribir pruebas para código cuyo propósito no entiendo

59

Recientemente completé una refactorización de caja negra. No puedo registrarlo porque no puedo averiguar cómo probarlo.

En un nivel alto, tengo una clase cuya inicialización implica tomar valores de alguna clase B. Si la clase B está "vacía", genera algunos valores predeterminados razonables. Extraje esta parte a un método que inicializa la clase B a los mismos valores predeterminados.

Todavía tengo que resolver el propósito / contexto de cualquiera de las clases, o cómo se utilizarían. Por lo tanto, no puedo inicializar el objeto desde una clase B vacía y verificar que tenga los valores correctos / haga lo correcto.

Mi mejor idea es ejecutar el código original, codificar los resultados de los métodos públicos en función de los miembros inicializados, y probar el nuevo código contra eso. No puedo articular por qué me siento vagamente incómodo con esta idea.

¿Hay un mejor ataque aquí?

JETM
fuente
28
Siento que has comenzado en el lado equivocado. Primero debe comprender el código, luego probarlo, luego refactorizar. ¿Por qué refactoriza sin saber para qué sirve el código?
Jacob Raihle
11
@JacobRaihle Es un programa bastante especializado para personas con títulos en cosas que nunca he tocado. Estoy recogiendo contexto a medida que avanzo, pero simplemente no es práctico esperar para tener una comprensión sólida antes de comenzar.
JETM
44
Lo que no es práctico es reescribir cosas y, cuando los cambios están en producción, descubrir por qué no debería haberlo hecho. Si podrá realizar pruebas exhaustivas antes de eso, está bien, esta puede ser una buena manera de conocer la base del código. Si no, es imperativo que comprenda antes de cambiar.
Jacob Raihle
37
Existe un tipo específico de prueba llamada Prueba de caracterización para cuando desea probar el comportamiento real del sistema. Simplemente tome su sistema original, luego agregue pruebas que afirmen lo que realmente hace (¡y no necesariamente lo que estaba destinado a hacer!). Sirven como un andamiaje alrededor de su sistema, que puede modificar de forma segura ya que puede asegurarse de que conserva su comportamiento.
Vincent Savard
3
¿No puede pedir / que alguien que lo entienda lo revise ?
pjc50

Respuestas:

122

¡Lo estas haciendo bien!

Crear pruebas de regresión automatizadas suele ser lo mejor que puede hacer para que un componente sea refactible. Puede ser sorprendente, pero tales pruebas a menudo se pueden escribir sin la plena comprensión de lo que hace internamente el componente, siempre y cuando comprenda las "interfaces" de entrada y salida (en el significado general de esa palabra). Lo hicimos varias veces en el pasado para aplicaciones heredadas completas, no solo clases, y a menudo nos ayudó a evitar romper cosas que no entendíamos completamente.

Sin embargo, debe tener suficientes datos de prueba y asegurarse de tener una comprensión firme de lo que hace el software desde el punto de vista de un usuario de ese componente, de lo contrario corre el riesgo de omitir casos de prueba importantes.

En mi humilde opinión, es una buena idea implementar sus pruebas automatizadas antes de comenzar a refactorizar, no después, para que pueda refactorizar en pequeños pasos y verificar cada paso. La refactorización en sí debería hacer que el código sea más legible, por lo que le ayuda a aumentar su comprensión de las partes internas poco a poco. Entonces, los pasos de orden en este proceso son

  1. entender el código "desde afuera",
  2. escribir pruebas de regresión,
  3. refactor, lo que conduce a una mejor comprensión de las partes internas del código
Doc Brown
fuente
21
Respuesta perfecta, también exactamente como se describe en el libro "Trabajar con código heredado"
Altoyr
Tuve que hacer algo como esto una vez. Recopile datos de salida típicos de la aplicación antes de modificarlos, luego verifique mi nueva versión de la aplicación ejecutando los mismos datos de prueba a través de ellos. Hace 30 años ... Fortran ... Era una especie de procesamiento de imágenes / mapeo, por lo que realmente no podía saber cuál era el resultado 'al' mirarlo o escribir casos de prueba. Y lo hice en una pantalla vectorial (persistente) de Tektronix. Trabajo del gobierno ... 2 Teletipos golpeando detrás de mí.
44
Uno podría agregar, aún puede escribir sus pruebas para el código anterior después del hecho. Luego puede probarlos en su versión refactorizada, y si eso se rompe, haga una búsqueda de bisección a través de su historial de confirmaciones para encontrar el punto donde comienza a romperse.
CodeMonkey
2
Sugeriría hacer una cosa más. Mientras recopila los datos de prueba, recopile estadísticas de cobertura de código si es posible. Sabrá qué tan bien sus datos de prueba describen el código en cuestión.
liori
2
@nocomprende, es divertido que hice exactamente eso con un código científico fortran 77 la semana pasada. Agregué la impresión de datos ascii a un archivo, configuré directorios de prueba con las entradas y la salida esperada, y mi caso de prueba fue solo una diferencia de los dos conjuntos de salida. Si no coinciden personaje por personaje, rompo algo. Cuando el código es principalmente dos subrutinas que son cada 2-3k LoC, debe comenzar en algún lugar.
Godric Seer
1

Una razón importante para escribir pruebas unitarias es que documentan la API del componente de alguna manera. No entender el propósito del código que se está probando es realmente un problema aquí. La cobertura del código es otro objetivo importante, difícil de lograr sin saber qué ramas de ejecución existen y cómo se activan.

Sin embargo, si es posible restablecer el estado limpiamente (o construir el nuevo objeto de prueba cada vez), se pueden escribir pruebas de tipo "basura dentro-basura" que solo alimentan al sistema principalmente al azar y observan la salida.

Tales pruebas son difíciles de mantener, ya que cuando fallan, puede ser complejo decir por qué y qué tan grave es. La cobertura puede ser cuestionable. Sin embargo, todavía son mucho mejores que nada. Cuando dicha prueba falla, el desarrollador puede revisar los últimos cambios con más atención y, con suerte, detectar el error allí.

h22
fuente
1
Cualquier tipo de información es mejor que volar a ciegas. Solía ​​localizar errores en los programas de servidor que estaban en producción invocando al depurador en un archivo de volcado por caída (Unix) y solicitando el seguimiento de la pila. Me dio el nombre de la función donde ocurrió la falla. Incluso sin otro conocimiento (no sabía cómo usar este depurador) ayudó en lo que de otra forma sería una situación misteriosa e irreproducible.