Con frecuencia trabajo con programas muy numéricos / matemáticos, donde el resultado exacto de una función es difícil de predecir de antemano.
Al tratar de aplicar TDD con este tipo de código, a menudo encuentro que escribir el código bajo prueba es significativamente más fácil que escribir pruebas unitarias para ese código, porque la única forma que sé para encontrar el resultado esperado es aplicar el algoritmo mismo (ya sea en mi cabeza, en papel o por la computadora). Esto se siente mal, porque estoy usando efectivamente el código bajo prueba para verificar mis pruebas unitarias, en lugar de al revés.
¿Existen técnicas conocidas para escribir pruebas unitarias y aplicar TDD cuando el resultado del código bajo prueba es difícil de predecir?
Un ejemplo (real) de código con resultados difíciles de predecir:
Una función weightedTasksOnTime
que, dada la cantidad de trabajo realizado por día workPerDay
en el rango (0, 24], la hora actual initialTime
> 0 y una lista de tareas taskArray
; cada una con un tiempo para completar la propiedad time
> 0, fecha de vencimiento due
y valor de importancia importance
; devuelve un valor normalizado en el rango [0, 1] que representa la importancia de las tareas que se pueden completar antes de su due
fecha si cada tarea se completa en el orden dado por taskArray
, comenzando en initialTime
.
El algoritmo para implementar esta función es relativamente sencillo: iterar sobre las tareas en taskArray
. Para cada tarea, agregue time
a initialTime
. Si la nueva hora < due
, agregue importance
a un acumulador. El tiempo se ajusta mediante trabajo inverso por día. Antes de devolver el acumulador, divídalo por la suma de las tareas importantes para normalizar.
function weightedTasksOnTime(workPerDay, initialTime, taskArray) {
let simulatedTime = initialTime
let accumulator = 0;
for (task in taskArray) {
simulatedTime += task.time * (24 / workPerDay)
if (simulatedTime < task.due) {
accumulator += task.importance
}
}
return accumulator / totalImportance(taskArray)
}
Creo que el problema anterior se puede simplificar, manteniendo su núcleo, eliminando workPerDay
y el requisito de normalización, para dar:
function weightedTasksOnTime(initialTime, taskArray) {
let simulatedTime = initialTime
let accumulator = 0;
for (task in taskArray) {
simulatedTime += task.time
if (simulatedTime < task.due) {
accumulator += task.importance
}
}
return accumulator
}
Esta pregunta aborda situaciones en las que el código bajo prueba no es una reimplementación de un algoritmo existente. Si el código es una reimplementación, intrínsecamente tiene resultados fáciles de predecir, porque las implementaciones confiables existentes del algoritmo actúan como un oráculo de prueba natural.