Prueba de hipótesis no estadística

Realizar pruebas cuando no sabe qué probar

December 11, 2020
Modern Data Platform

En tu escritorio, tu café ya se ha enfriado. En mitad de la noche, el cursor parpadea casi desafiantemente en la pantalla, burlándose de ti. Te espera una noche larga. Sin embargo, todo está bien. Después de aprender sobre manejo moderno de dependencias de python publicaste tu nuevo y extravagante paquete llamado foobar, qué toque de originalidad. Ha tenido un éxito inesperado. Las estrellas se están disparando en GitHub, pero alguien en todo el mundo ha abierto una edición. Así que aquí estamos ahora.

Planeaban ejecutar algunas pruebas A/B para ver cómo responde su base de usuarios a tu brillante y completo paquete, solo para descubrir que... ¡no hay pruebas!

Probando

Primero, un resumen rápido de las pruebas. Es un código que prueba otro código. Ahí está, hecho. Pero, ¿qué pasa pruebas unitarias? Vamos a tomar De Martin Fowler aborde el tema, él sabe un par de cosas sobre esto. Las pruebas unitarias deben: - considerar una unidad - ser rápidas (más que otros tipos de pruebas) - estar escritas por programadores

Y en este punto, es posible que se pregunte ¿qué es una unidad?, y hay muchos recursos disponibles que cubren este tema. No quiero añadir ruido a la señal. Pero ceñámonos a la idea de que es un asunto. Más específicamente, un cosa atómica, hasta cierto grado de atomicidad que le permite dormir por la noche.

Soy un aprendiz práctico, así que empecemos con un ejemplo sencillo sobre cómo escribir algunas pruebas en Python. Tienes el siguiente código en foobar.añadir:

fromFoobar.Optimized_TypesImportBigNumberDefAdd_Numbers (x:BigNumber, y:BigNumber) ->BigNumber: """Añade dos números grandes y devuelve un nuevo número grande. Parámetros ---------- x (BigNumber): el primer operando. y (BigNumber): el segundo operando. Devuelve ------- BigNumber: la suma de x e y. «"»... # Sigue una implementación brillante

¿Cómo vas a probar esto? Hola pytest, mi viejo amigo, he vuelto a hacer la prueba contigo.

Algunas pruebas

¡Añadamos algunas pruebas!

deftest_add_numbers_zero_and_zero () :assertadd_numbers (0,0) ==0deftest_add_numbers_one_and_zero () :assertadd_numbers (1,0) ==1deftest_add_numbers_cero_and_one () :assertadd_numbers (0,1) ==0

¿Has notado que algo va mal? ¿Puedes oler algún olor a código? Estas pruebas tienen (al menos) un par de problemas: - pruebas mal definidas - código repetido

Mientras que en el primer punto la cuestión es entre la silla y el teclado ergo split, en el segundo es donde los marcos vienen al rescate:

data= [(0,0,0), (1,0,1), (0,1,1)] @pytest .mark.parametrize («n1, n2, esperado», datos) deftest_add_numbers (n1, n2, esperado) :assertadd_numbers (n1, n2) ==esperado

Así es más, ¿no te encanta el olor del código borrado por las mañanas?

Probar propiedades, no casos

Pero tomemos un momento para pensar en esto. ¿Tu función agrega. Estás probando justo algunos casos. Cuando me enteré por primera vez de las pruebas, pregunté por qué no se hacen pruebas en todos los casos posibles. La respuesta es bastante sencilla: tiempo. Por lo tanto, normalmente solo pruebas algunos casos extremos y caminos satisfactorios junto con los resultados esperados, en función de tus conocimientos sobre el dominio y el software. Pero, ¿qué es lo que define a suma ¿La operación es correcta?

Conmutatividad

$$a + b = b + a$$

Asociatividad

$$a + (b + c) = (a + b) + c$$

Identidad

$$a + 0 = a$$

Entonces, ¿cómo vas a probar esto? Ingresa hipótesis:

[...] una biblioteca de Python para crear pruebas unitarias que son más sencillas de escribir y más potentes cuando se ejecutan, encontrando casos extremos en tu código que no habrías pensado buscar.

de la importación de hipótesis dada a partir de la hipótesis estrategias de importación asst @given (st.integers (), st.integers ()) deftest_code_add_commutativity (a, b) :assertadd_numbers (b, a) @given (st.integers ()) deftest_code_add_identity (a) :assertadd_numbers (a) ,0) ==a @given (st.integers (), st.integers (), st.integers ()) deftest_code_add_associativity (a, b, c) :assertadd_numbers (a, add_numbers (b, c)) ==add_numbers (add_numbers (a, b), c)

¡Y eso es todo! En lugar de probar fundas ahora estás probando propiedades de tu código. ¿Qué tan guay es eso?

Entonces, ¿qué son las pruebas basadas en propiedades?

Empecemos con la divertida definición:

La cosa que Comprobación rápida hace

Y ahora, busquemos algo más... completo, qué es lo que hace quickcheck:

  1. Las afirmaciones se escriben sobre las propiedades lógicas que debe cumplir una función.
  2. QuickCheck intenta generar un caso de prueba que falsifique tales afirmaciones.
  3. QuickCheck intenta reducirlo a un subconjunto mínimo de errores eliminando o simplificando los datos de entrada que no son necesarios para que la prueba falle

Entonces, ¿es lo mismo que pruebas difusas? Bueno, sí, pero en realidad no. Desde el punto de vista del usuario final, se obtiene un fuzzer y una biblioteca de herramientas para facilitar la construcción de pruebas basadas en propiedades con dicho fuzzer.

Diseccionando nuestro ejemplo

Por lo tanto, si echas un vistazo a nuestro ejemplo anterior, verás que decoras las pruebas con dado para indicar un punto de entrada para hipótesis. También estás utilizando un estrategia para generar números enteros. Luego escribes tu afirma ¡como siempre!

Ejemplos

Pero hay mucho en esto, y quizás necesites un par de ideas sobre qué tipo de propiedades puedes probar.

Clasificación

Hablemos de la clasificación. ¿Cuáles son sus propiedades?

  • ordenar (l) devuelve una lista
  • una lista ordenada tiene los mismos elementos que la lista original
  • hay un orden entre los elementos
  • ordenar una lista ordenada no cambia nada

Así que, con hipótesis necesitaría generar, por ejemplo, listas de números enteros y probar dichas propiedades,

@given (st.lists (st.integers ())) deftest_sorting (l) :sl=my_sort (l) assertisinstance (sl, list) AssertCounter (sl) ==Counter (l) assertall (x<=yforx, yinzip (sl, sl [1:])) assertmy_sort (sl) ==sl

Fácil, ¿verdad?

Codificar/decodificar

Imagina que tienes un codificar/decodificar un par de funciones. A pesar de probar algunos casos particulares, es posible que se dé cuenta de que codificar (decodificar (x)) == x y decodificar (codificar (x)) == x útil.

Un ejemplo sencillo podría ser un a binario y de_binary par de funciones de codificación/decodificación:

importar pytest desde la importación de hipótesis dada, rechazar desde hipótesis las estrategias de importación asstdefto_binary (i) :res= [] while i! =0:i, mod=divmod (i,2) res.append (mod) return "» .join (map (str, res)) [:: -1] deffrom_binary (b) :returnsum ((2**idx) *int (v) foridx, vinenumerate (b [:: -1])) @given (st.text (alphabet="1", min_size=1) deftest_only_ones (x) :assertfrom_binary (x) ==2**len (x) -1 @given (st.integers (min_value=0)) deftest_encode_decode (x) :assertfrom_binary (to_binary (x)) ==x @given (st.text (alphabet="01", min_size=1)) deftest_decode_encode (x) :x=x.lstrip («0") iflen (x) ==0:reject () assertto_binary (from_binary (x)) ==x

Optimización

Imagine que tiene una función de verdad básica que ha sido probada y comprobada (¡tal vez incluso utilizando pruebas basadas en propiedades!) y desea optimizarla para tiempos de ejecución más cortos. Digamos que teníamos multiply_numbers (x: número grande, y: número grande) en foobar.multiplique que se había implementado utilizando multiplicación larga, y supongamos es correcto. Pero ahora quieres volver a implementarlo usando Algoritmo de Karatsuba, puedes ejecutar ambos y afirmar que ambos resultados son los mismos para garantizar la exactitud y, al mismo tiempo, medir sus tiempos de reloj y afirmar que, para números lo suficientemente grandes, Karatsuba debería tener tiempos de ejecución más cortos.

Por lo tanto, consideraría su implementación anterior como oráculo y compruebe que la nueva implementación está de acuerdo con ella.

Otros casos

Me divertí mucho usando la hipótesis para encontrar un sistema de monedas no canónico para problema de creación de cambios, que puedes encontrar aquí. ¡Pero esa es una historia para otro día!

¡Cuéntame más!

Hypothesis tiene muchas características interesantes. Es se integra muy bien con pytest, tiene estrategias para generar los tipos más comunes y ¡te permite generar los tuyos propios!

Pero imaginemos que encuentra un caso sin resolver, e imaginemos que es un caso muy complicado. Probablemente no sirva de nada. Por suerte, hay encogimiento integrado ¡lo que significa que reducirá el ejemplo fallido a un ejemplo lo más simple posible!

Así que ahora tienes un simple caso fallido en el que trabajar y solucionarlo. Pero la próxima vez que realices las pruebas, querrás ejecutar el mismo ejemplo para ver si está arreglado. Pero dijimos que los ejemplos eran aleatorios (más o menos) 😞. Bueno, no del todo, la hipótesis se mantiene pequeña base de datos de ejemplos fallidos para comprobar si hay ejecuciones futuras.

Una función que aún no tengo que probar es prueba la escritura fantasma, pero seguro que parece prometedor y muy interesante. El módulo ghostwriter genera funciones de prueba que le permiten comenzar con las pruebas basadas en propiedades de forma más rápida y sencilla. Varios de los ejemplos que se proporcionan aquí se pueden asignar a algún escritor fantasma actualmente implementado.

Aunque la mayoría de las veces no sirve de nada ver las entradas generadas, puede configurar las pruebas verbosidad para verificar los ejemplos generados.

Hipótesis para proyectos de datos

Como científico de datos, no consideré que las hipótesis fueran un accidente. Estaba trabajando en un proyecto basado en el análisis de redes sociales. Nuestras redes tenían propiedades bien definidas y, además, los datos relacionados con cada nodo tenían algunas reglas de formato vagas, pero propiedades bien definidas (piense en los números de teléfono, por ejemplo, el número de bordes, etc.). Teníamos que realizar algunas transformaciones en los datos y afirmar que los resultados tenían sentido. Sin embargo, los datos de entrada eran relativamente complejos, por lo que las transformaciones aún tenían cierta complejidad.

Probar todos los casos extremos habría sido difícil, si no imposible, y llevaría mucho tiempo. Algunas propiedades de las transformaciones son reglas empresariales codificadas, mientras que otras son lógicas.

Como habrás adivinado, la mayor parte de esto se hizo usando Pandas. Marco de datos. La hipótesis tiene estrategias para pandas, incluyendo Serie, índices, Marcos de datos y son muy fáciles de usar. También tiene lleno de nudos estrategias!

¿Puedo usarlo para el desarrollo web?

No pretendo ser un experto en esto, no lo soy. Pero el hipótesis [django]extra tiene estrategias para probar modelos y formas de Django.

Últimas palabras

Por supuesto, esto no es una fórmula mágica, no hay soluciones milagrosas. Las pruebas basadas en propiedades tienen algunos inconvenientes, entre los que destaca que lento. O al menos en comparación con tus antiguas pruebas unitarias. Deberías tomarte un momento para pensar cuándo y dónde usarlo. Sin embargo, siempre es bueno tener más herramientas en tu cinturón de herramientas.

Referencias

Este artículo ha sido adaptado de una charla interna, que se dio en tiempos anteriores a la pandemia, cuando podíamos reunirnos de forma segura en la oficina y transmitir en vivo en lugares remotos. Lamentablemente, no se grabó. Puedes encontrar las diapositivas originales aquí, que enlazan con el código de ejemplo si quieres profundizar más. ¡Ahora, ve a probar las propiedades de tu código!

Share article.
News & insights

Latest Insights

Muttdata
Empresa

¿Cómo sabes que es hora de hacer evolucionar tu marca?

Evolucionar nuestra marca para que esté a la altura de nuestro crecimiento
Read Article
Modern Data Platform

El lenguaje natural se une a los datos en tiempo real: análisis sin cuellos de botella

Obtenga información sin necesidad de conocimientos técnicos
Read Article
Paid Media Optimizer

No todos los optimizadores de medios online están diseñados de la misma manera

5 razones por las que deberías solicitar una demo
Read Article

Listo para desbloquear

¿el poder de los datos?