Pruebas basadas en propiedades (con una pizca de JavaScript)

Si vienes de OO y alguna vez has probado alguna pieza de software, es probable que hayas utilizado pruebas basadas en ejemplos. En otras palabras, la función bajo prueba se llama con una entrada predefinida y el valor de retorno se compara con alguna expectativa predefinida. Por ejemplo, la función sum se llama con 1 y 2 y el resultado esperado es 3 .

Sin embargo, hay problemas con este enfoque. En primer lugar, solo se puede verificar una cantidad limitada de ejemplos. En segundo lugar, es un ser humano que decide qué entrada-salida verificar. Esto, lamentablemente, refleja el sesgo del desarrollador. Por lo tanto, hay una gran posibilidad de que alguna entrada crítica quede sin marcar.

Pero, ¿cómo es posible tener expectativas sobre el valor de retorno de una función si la entrada no es elegida por el desarrollador?

Resulta que es posible derivar invariantes (propiedades) del código que siempre son verdaderas, sin importar cuál sea la entrada. Si mezclas eso con generadores de valores aleatorios, obtienes pruebas basadas en propiedades.

En el caso de la sum , usaríamos un generador de números para producir la entrada. Entonces, derivaremos algunas propiedades para probar, como la conmutatividad (es decir, x + y == y + x ) y la asociatividad (es decir, (x + y) + z == x + (y + z) ). Por último, el marco de prueba basado en propiedades generaría algunos conjuntos de x , y y z y se verificaría varias veces si se mantienen las propiedades.

En presencia de una falla, obtendríamos el primer conjunto de x , y y z para el cual no se tenía la propiedad.

La sum uno es un simple ejemplo. Pero podríamos imaginar algo más complejo que implique otras llamadas a funciones y una entrada menos trivial. En ese caso, solo obtener el conjunto de entrada que falló podría no ser suficiente.

Es por eso que la mayoría de los marcos de prueba basados ??en propiedades proporcionan una función llamada reducción. En otras palabras, después de una falla, el marco intenta reducir la entrada que hizo que la propiedad falle a su forma más simple mediante la eliminación o simplificación de los datos de entrada.

Muéstrame el código

Vamos a aplicar pruebas basadas en propiedades a uno de esos ejemplos estúpidos que nunca verás en la vida real. Voy a usar JavaScript y JSVerify .

Digamos que tenemos el siguiente código que necesita pruebas:

 const div = (dividendo, divisor) => dividendo / divisor 

Fácil, ¿verdad? Las siguientes pruebas basadas en ejemplos son verdes, ¡así que podemos llamarlo un día!

 describe ('div', () => { 
it ('con números naturales', () => {
const expected = 2
 const actual = div (6, 3) 
 assert.strictEqual (real, esperado) 
})
 it ('con números decimales', () => { 
const expected = 2
 const actual = div (6.3, 3.15) 
 assert.strictEqual (real, esperado) 
})
})

Bueno en realidad no. Veamos qué ocurre con las pruebas basadas en propiedades.

En primer lugar, como generador podemos usar jsc.nat que devuelve números naturales.

En segundo lugar, como propiedades, simplemente verifiquemos el de "distribución correcta" (es decir, (n1 + n2) / n3 == (n1 / n3) + (n2 / n3) ).

 describe ('div', () => { 
const naturalNumber = jsc.nat
 jsc.property ( 
'es correcto-distributivo',
naturalNumber, naturalNumber, naturalNumber,
(n1, n2, n3) => div (n1 + n2, n3) === div (n1, n3) + div (n2, n3)
)
})

¡AUGE!

 Error: Falló después de 4 pruebas y 4 se encoge. rngState: 08b51479f83d6a20ec; Contraejemplo: 0; 0; 0; 

Con n1 = 0 , n2 = 0 y n3 = 0 algo salió mal. Desde el nodo REPL

 div (0 + 0, 0) === div (0, 0) + div (0, 0) 
// falso
 div (0 + 0, 0) 
// NaN
 div (0, 0) + div (0, 0) 
// NaN
 NaN === NaN 
// falso

JavaScript, ¿verdad? Si ejecutamos nuevamente la prueba basada en la propiedad

 Error: Falló después de 4 pruebas y 4 se encoge. rngState: 08b51479f83d6a20ec; Contraejemplo: 2; 32; 3; 

BOOM OTRA VEZ. Pero esta vez es un fracaso diferente. Desde el nodo REPL

 div (2 + 32, 3) === div (2, 3) + div (32, 3) 
// falso
 div (2 + 32, 3) 
// 11.333333333333334
 div (2, 3) + div (32, 3) 
// 11.333333333333332

JavaScript y aritmética de coma flotante, ¿verdad?

Ahora, si las pruebas basadas en la propiedad descubrieron 2 errores en 2 carreras de

 const div = (dividendo, divisor) => dividendo / divisor 

imagina qué más puede encontrar de un código más complejo.

Outro

Las pruebas basadas en la propiedad eliminan el sesgo. De esta forma, permite descubrir errores que el desarrollador no pensó probar.

Además, obliga a considerar el código desde otro punto de vista, lo cual es algo bueno.

Al mismo tiempo, las propiedades son algo más abstractas que los ejemplos (por ejemplo, conmutatividad en suma vs sum(1, 2) == 3 ). Es por eso que mezclar los dos estilos es la mejor idea.

¿Hambriento por más? Mira que utilizo TDD basado en propiedades en la publicación de seguimiento .

Punteros