Pruebas unitarias para una biblioteca de computación científica.

15

He tenido un poco de experiencia con las pruebas unitarias antes, en lo que llamo (no peyorativamente) el clásico proyecto de ingeniería de software: un MVC, con una GUI de usuario, una base de datos, lógica de negocios en la capa media, etc. Ahora ' m escribir una biblioteca de computación científica en C # (sí, sé que C # es demasiado lento, usar C, no reinventar la rueda, y todo eso, pero tenemos mucha gente haciendo computación científica en mi facultad en C #, y más o menos lo necesitamos). Es un proyecto pequeño, en términos de la industria de desarrollo de software, porque lo escribo principalmente solo, y de vez en cuando con la ayuda de algunos colegas. Además, no me pagan por ello, y lo más importante, es un proyecto académico. Quiero decir, espero que tenga calidad profesional algún día, porque planeo ir a código abierto,

De todos modos, el proyecto se está haciendo grande (alrededor de 18,000 líneas de código, que creo que es grande para el proyecto de un solo hombre), y se me está yendo de las manos. Estoy usando git para el control de fuente, y creo que me fue bastante bien, pero estoy probando como en la vieja escuela, quiero decir, escribiendo aplicaciones de consola completa que prueban una gran parte del sistema, principalmente porque no tengo idea de cómo hacer pruebas unitarias en este escenario, aunque creo que eso es lo que debería estar haciendo. El problema es que la biblioteca contiene principalmente algoritmos, por ejemplo, algoritmos gráficos, clasificadores, solucionadores numéricos, distribuciones aleatorias, etc. Simplemente no sé cómo especificar pequeños casos de prueba para cada uno de estos algoritmos, y dado que muchos de ellos son estocástico No sé cómo validar la corrección. Para la clasificación, por ejemplo, hay algunas métricas como precisión y recuperación, pero estas métricas son mejores para comparar dos algoritmos que para juzgar un solo algoritmo. Entonces, ¿cómo puedo definir la corrección aquí?

Finalmente, también está el problema del rendimiento. Sé que es un conjunto completamente diferente de pruebas, pero el rendimiento es una de las características importantes de las herramientas científicas, en lugar de la satisfacción del usuario u otras métricas de ingeniería de software.

Uno de mis mayores problemas es con las estructuras de datos. La única prueba que puedo encontrar para un árbol kd es una prueba de esfuerzo: inserte muchos vectores aleatorios y luego realice muchas consultas aleatorias, y compárelas con una búsqueda lineal ingenua. Lo mismo para el rendimiento. Y con los optimizadores numéricos, tengo funciones de referencia que puedo probar, pero nuevamente, esta es una prueba de esfuerzo. No creo que estas pruebas puedan clasificarse como pruebas unitarias, y lo más importante, ejecutarse continuamente, ya que la mayoría de ellas son bastante pesadas. Pero también creo que estas pruebas deben hacerse, no puedo simplemente insertar dos elementos, abrir la raíz, y sí, funciona para el caso 0-1-n.

Entonces, ¿cuál es el enfoque de prueba (unitario) para este tipo de software, si lo hay? ¿Y cómo organizo las pruebas unitarias y las pesadas en torno al ciclo de código-construcción-confirmación-integración?

Alejandro Piad
fuente

Respuestas:

19

Diría que la informática científica es bastante adecuada para pruebas unitarias. Tiene entradas y salidas definidas, condiciones previas y posteriores claramente definidas que probablemente no cambiarán cada dos semanas de acuerdo con el capricho de algunos diseñadores, y sin requisitos de IU difíciles de probar.

Nombra algunos elementos que pueden causar problemas; Esto es lo que debe hacer al respecto:

  • Algoritmos aleatorizados: hay dos posibilidades. Si realmente desea probar la aleatorización en sí misma, solo programe una gran cantidad de repeticiones y afirme que la proporción esperada de casos cumple con el criterio deseado, con márgenes de error lo suficientemente grandes como para que las fallas de prueba espurias sean bastante raras. (Un conjunto de pruebas que señala de manera poco confiable los errores fantasmas es mucho peor que uno que no detecta todos los defectos imaginables). Alternativamente, use una fuente aleatoria configurable y reemplace el reloj del sistema (o lo que sea que use) con una fuente determinista por dependencia inyección para que sus pruebas se vuelvan completamente predecibles.
  • algoritmos definidos solo en términos de precisión / recuperación: nada le impide colocar un conjunto completo de casos de entrada y medir la precisión y recuperación al agregarlos todos; es solo una cuestión de generar semiautomáticamente estos casos de prueba de manera eficiente para que proporcionar los datos de prueba no se convierta en el cuello de botella para su productividad. Alternativamente, especificar algunos pares de entrada / salida elegidos juiciosamente y afirmar que el algoritmo calcula exactamente la entrada deseada también puede funcionar si la rutina es lo suficientemente predecible.
  • requisitos no funcionales: Si la especificación realmente le da el espacio explícita / requisitos de tiempo, a continuación, que, básicamente, tiene que ejecutar suites completas de pares de entrada / salida y compruebe que la conforma de uso de recursos de aproximadamente el patrón de uso requerido. El truco aquí es calibrar su propia clase de prueba primero, para que no mida diez problemas con diferentes tamaños que terminen siendo demasiado rápidos para medir, o que demoren tanto que ejecutar el conjunto de pruebas no sea práctico. Incluso puede escribir un pequeño generador de casos de uso que cree casos de prueba de diferentes tamaños, dependiendo de qué tan rápido esté funcionando el PU.
  • Pruebas de ejecución rápida y lenta: ya sean pruebas unitarias o de integración, a menudo terminas con muchas pruebas muy rápidas y algunas muy lentas. Dado que ejecutar sus pruebas regularmente es muy valioso, generalmente tomo la ruta pragmática y separo todo lo que tengo en un conjunto rápido y lento, para que el rápido pueda ejecutarse con la mayor frecuencia posible (ciertamente antes de cada confirmación), y no importa si dos pruebas 'semánticamente' pertenecen juntas o no.
Kilian Foth
fuente
+1. Muchas gracias, hay mucho si la comprensión de su respuesta. Solo un par de preguntas: ¿Qué tal los algoritmos de optimización como la metaheurística? Tengo un montón de funciones de referencia, pero todo lo que puedo hacer con ellas es comparar dos algoritmos diferentes. ¿Necesito encontrar un algoritmo de referencia también? ¿Qué significa que un algoritmo genético sea correcto? ¿Y cómo pruebo cada una de las estrategias "parametrizables", como el tipo de recombinación y mutación, etc.
Alejandro Piad
1
Para la metaheurística, me conformaría con elegir algunos pares característicos de E / S, es decir, los "éxitos famosos" de la rutina, y verificaría que el método (o el mejor de los dos) de hecho encuentre esta solución. Los problemas de "selección de cerezas" que funcionan bien es, por supuesto, un no-no en la investigación de optimización, pero para las pruebas de software no es una preocupación: no está afirmando la calidad del algoritmo, solo la implementación correcta. Esa es la única "corrección" que puedes probar. En cuanto a las rutinas parametrizables múltiples: sí, me temo que requiere una cantidad combinatoria de pruebas ...
Kilian Foth
Entonces, ¿es como diseñar un punto de referencia trivial que todas las implementaciones correctas deberían resolver exactamente? ¿Hay alguna forma de demostrar la calidad del algoritmo? Sé que no puedo definir un estándar de calidad la mayor parte del tiempo, pero al menos podría desear que ningún cambio disminuya la calidad alcanzada.
Alejandro Piad