Andres Bernal

Andres Bernal

Developer.

© 2019

Hablemos de testing -python 馃悕

Que es testing

Programar es escribir c贸digo para resolver problemas馃槑. La ingenier铆a de software es la pr谩ctica de utilizar un proceso estructurado para resolver problemas.

Tener una base de c贸digo que podamos cambiar, ampliar y refactorizar seg煤n sea necesario.

Las pruebas aseguran que nuestro programa funciona seg煤n lo previsto y que los cambios en la base de c贸digo no rompen la funcionalidad existente.

Que es testing

Se trata de ser pragm谩tico en lo que probamos, 馃槍 c贸mo lo hacemos y cu谩ndo lo hacemos debemos aprovechar las herramientas y t茅cnicas que nos permiten probar nuestro c贸digo de la manera m谩s eficiente posible. Las pruebas deben ser f谩ciles y sin barreras

Tenemos que entrar en el c贸digo porque no hay pruebas o las pruebas que existen son tan debiles que nos vemos obligados a reescribir las pruebas a medida que escribimos el c贸digo. De esto no se trata la Ingenier铆a del Software.

Las pruebas deber铆an permitir la refactorizaci贸n, no obstaculizar nuestra capacidad de realizar cambios en la base de c贸digo. Deber铆amos pasar nuestro tiempo escribiendo 馃捇 l贸gica de negocios, no luchando con pruebas.

En mi experiencia parece que hay muchos consejos contradictorios, y eso es porque los hay. Las pruebas son dificiles, m谩s que cualquier otra disciplina de ingenier铆a de software. con mis colegas siempre estamos discutiendo sobre 馃摑 Qu茅 evaluamos, c贸mo evaluamos y cu谩ndo evaluamos.

Aqui escribo mi proceso de pensamiento sobre c贸mo hago para agregar pruebas a una base de c贸digo.

antes de esto hago un resumen de como funciona la cosa,para que en lo posible hablemos el mismo lenguaje

QUE DIABLOS ES TESTING!

Que es testing

Cuando escribimos c贸digo, necesitamos ejecutarlo para asegurarnos de que est谩 haciendo lo que esperamos.

Las pruebas son un contrato con nuestro c贸digo: dado un valor, esperamos que se devuelva un determinado resultado.

Las pruebas en ejecuci贸n pueden considerarse como un mecanismo de retroalimentaci贸n que nos informa si nuestro programa funciona seg煤n lo previsto

no he escuchado a nadie decir

Escrib铆 muchas pruebas, pero no vali贸 la pena, as铆 que las elimin茅,el programa funciona como pense.

Si bien pasar las pruebas no puede probar los errores de ausencia, nos informan que nuestro c贸digo funciona de la manera definida por la prueba. En contraste, una prueba fallida indica que algo no est谩 bien. Necesitamos entender por qu茅 nuestra prueba fall贸 para poder modificar el c贸digo y / o las pruebas, seg煤n sea necesario.

馃懆 => Corro Test => 鈽戯笍 ok, 鉁栵笍falla entonces resultado del proceso => 馃懆 y de nuevo鈥

Si bien pasar las pruebas no puede probar los errores de ausencia, nos informan que nuestro c贸digo funciona de la manera definida por la prueba. En contraste, una prueba fallida indica que algo no est谩 bien. Necesitamos entender por qu茅 nuestra prueba fall贸 para poder modificar el c贸digo y / o las pruebas, seg煤n sea necesario.

Propiedades de los test

  • Rapido: Las pruebas nos dan la confianza de que nuestro c贸digo funciona seg煤n lo previsto. Un ciclo de retroalimentaci贸n m谩s lento dificulta el desarrollo ya que nos lleva m谩s tiempo descubrir si nuestro cambio fue correcto. Si nuestro flujo de trabajo est谩 plagado de pruebas lentas, no las ejecutaremos con tanta frecuencia. Esto conducir谩 a problemas en el futuro.

  • Determinista: Las pruebas deben ser deterministas, es decir, la misma entrada siempre dar谩 como resultado la misma salida. Si las pruebas no son deterministas, tenemos que encontrar una manera de explicar el comportamiento aleatorio dentro de nuestras pruebas. Si bien definitivamente hay un c贸digo no determinista en producci贸n (es decir, Machine learning o IA), debemos tratar de hacer que todo nuestro c贸digo no probabil铆stico sea lo m谩s determinista posible. No tiene sentido hacer trabajo adicional a menos que nuestro programa lo requiera.

  • Automatizado Podemos confirmar que nuestro programa funciona ejecut谩ndolo. Esto podr铆a ejecutarse manualmente un comando en REPL o actualizar una p谩gina web; en ambos casos, estamos buscando ver si nuestro programa hace lo que se supone que debe hacer.

    Si bien las pruebas manuales est谩n bien para proyectos peque帽os, se vuelven inmanejables a medida que nuestro proyecto crece en complejidad. Al automatizar nuestro conjunto de pruebas, podemos verificar r谩pidamente que nuestro programa funciona a pedido.

Vocabulario en comun

input > System under Test (sut) > Output > Salidas validas > 鈽戯笍 test ok, 鉁栵笍 test falla

Un sistema bajo prueba (SUT) es la entidad que se est谩 probando actualmente. Esto podr铆a ser una l铆nea de c贸digo, un m茅todo o un programa completo.

Los criterios de aceptaci贸n se refieren a la verificaci贸n que realizamos que nos permite aceptar la salida del sistema. La especificidad y el rango de los criterios de aceptaci贸n dependen de lo que estamos probando: los dispositivos m茅dicos y el sector aeroespacial requieren que las pruebas sean espec铆ficas, ya que hay mucho menos margen de error.

Si Amazon hace una mala recomendaci贸n, no es el fin del mundo. Si Watson de IBM sugiere una cirug铆a incorrecta, puede ser mortal.

Las pruebas se refieren al proceso de ingresar entradas en nuestro sistema bajo prueba y validar las salidas seg煤n nuestros criterios de aceptaci贸n:

  • Si la salida est谩 bien, nuestra prueba pasa.
  • Si la salida no est谩 bien, nuestra prueba falla y tenemos que depurar.

Idenficar fallos temprano: Los errores cuestan dinero. 馃挵 Cu谩nto depende de cu谩ndo los encuentres.

La correcci贸n de errores se vuelve m谩s costosa cuanto m谩s se encuentre en el Ciclo de vida del desarrollo de software (SDLC). El verdadero costo de un error de software profundiza en este problema.

Mejorar el dise帽o del sistema

Este es un poco controvertido, pero creo que escribir c贸digo teniendo en cuenta las pruebas mejora el dise帽o del sistema.

Un completo conjunto de pruebas muestra que el desarrollador realmente ha pensado en el problema con cierta profundidad. Escribir pruebas te obliga a usar tu propia API; Con suerte, esto da como resultado una mejor interfaz.

Todos los proyectos tienen limitaciones de tiempo y es bastante f谩cil acostumbrarse a tomar atajos que aumentan el acoplamiento entre m贸dulos que conducen a interdependencias complejas. Tenemos que ser conscientes de resolver problemas con el c贸digo de espagueti.

Saber que tenemos que probar nuestro c贸digo nos obliga a escribir c贸digo modular. Si algo es torpe para probar, podr铆a haber una mejor interfaz que podamos implementar. Tomarse el tiempo para escribir ex谩menes nos obliga a estar atentos; respiramos profundamente antes de ver el problema desde la perspectiva de un usuario.

Una vez que escriba c贸digo comprobable utilizando patrones como la inyecci贸n de dependencias, ver谩 c贸mo agregar una estructura que hace m谩s f谩cil verificar que nuestro c贸digo est谩 haciendo lo que esperamos.

Black Box contra White Box

pyramid test

Las pruebas se pueden clasificar ampliamente en dos grandes categor铆as: pruebas de Black Box y pruebas de White Box.

Black Box Testing: se refiere a t茅cnicas de prueba en las que el tester no puede ver el funcionamiento interno del elemento que se est谩 probando.

entrada > 馃摝 > salida

White Box Testing: es la t茅cnica en la que el tester puede ver el funcionamiento interno del elemento que se est谩 probando.

entrada > 馃摛 > salida

Como desarrolladores, realizamos pruebas de White Box. Escribimos el c贸digo dentro de la caja y sabemos c贸mo probarlo a fondo. Esto no quiere decir que no sea necesario realizar pruebas de caja negra, a煤n deber铆amos hacer que alguien realice las pruebas a un nivel superior; La proximidad al c贸digo puede conducir a puntos ciegos en nuestras pruebas.

Piramide de prueba

La Pir谩mide de prueba no es una regla dif铆cil y r谩pida, pero proporciona un buen lugar para comenzar a pensar en una estrategia de prueba. Una buena regla general es escribir tantas pruebas en cada nivel como sea necesario para tener confianza en su sistema. Deber铆amos escribir pruebas a medida que escribimos c贸digo, iterando hacia una estrategia de prueba que funcione para el proyecto en el que estamos trabajando

pyramid test

La Pir谩mide de prueba no es una regla dif铆cil y r谩pida, pero proporciona un buen lugar para comenzar a pensar en una estrategia de prueba.

Una buena regla general es escribir tantas pruebas en cada nivel como sea necesario para tener confianza en su sistema. Deber铆amos escribir pruebas a medida que escribimos c贸digo, iterando hacia una estrategia de prueba que funcione para el proyecto en el que estamos trabajando

UNIT test

Las pruebas unitarias son pruebas de bajo nivel que se centran en probar una parte espec铆fica de nuestro sistema. Son baratos de escribir y r谩pidos de ejecutar. Las fallas en las pruebas deben proporcionar suficiente informaci贸n contextual para identificar la fuente del error. Los desarrolladores suelen escribir estas pruebas durante la fase de implementaci贸n del Ciclo de vida del desarrollo de software (SDLC).

Las pruebas unitarias deben ser independientes y aisladas; interactuar con componentes externos aumenta tanto el alcance de nuestras pruebas como el tiempo que tardan en ejecutarse. reemplazar las dependencias con dobles de prueba da como resultado pruebas deterministas que se ejecutan r谩pidamente.

驴Qu茅 tan grande debe ser nuestra prueba de unidad? Como todo lo dem谩s en programaci贸n, depende de lo que estamos tratando de hacer. Pensar en t茅rminos de una unidad de comportamiento nos permite escribir pruebas alrededor de bloques l贸gicos de c贸digo.

Test Pyramid recomienda realizar muchas pruebas unitarias en nuestro conjunto de pruebas. Estas pruebas nos dan la confianza de que nuestro programa funciona como se esperaba. Escribir un c贸digo nuevo o modificar el c贸digo existente puede requerir que reescribamos algunas de nuestras pruebas. Esta es una pr谩ctica est谩ndar, nuestro conjunto de pruebas crece con nuestra base de c贸digo.

Intente ser consciente de que nuestro conjunto de pruebas crece en complejidad. Recuerde, el c贸digo que prueba nuestro c贸digo de producci贸n tambi茅n es c贸digo de producci贸n. T贸mese el tiempo para refactorizar sus pruebas para asegurarse de que sean eficientes y efectivas.

vamos al codigo!

Supongamos que tenemos la siguiente funci贸n que toma una lista de palabras y devuelve la palabra m谩s com煤n y el n煤mero de apariciones de esa palabra:

 def buscar_palabra_principal(palabras)
    # Devuelve las palabras y ocurrencias m谩s comunes
    contador_palabras = Counter(palabras)
    return contador_palabras.most_common(1)[0]

Podemos probar esta funci贸n creando una lista, ejecutando la funci贸n buscar_palabra_principal sobre esa lista y comparando los resultados de la funci贸n con el valor que esperamos:

def buscar_palabra_principal():
    palabras = ["foo", "bar", "bat", "baz", "foo", "baz", "foo"]
    resultado = buscar_palabra_principal(palabras)
    assert resultado[0] == "foo"
    assert resultado[1] == 3 

Si alguna vez quisimos cambiar la implementaci贸n de buscar_palabra_principal, podemos hacerlo sin temor. Nuestra prueba asegura que la funcionalidad de buscar_palabra_principal no puede cambiar sin causar una falla en la prueba.

Test de integracion

pyramid test

Cada aplicaci贸n compleja tiene componentes internos y externos que trabajan juntos para hacer algo interesante. A diferencia de las pruebas de unidades que se centran en componentes individuales, las pruebas de integraci贸n combinan varias partes del sistema y las prueban juntas como un grupo. Las pruebas de integraci贸n tambi茅n pueden referirse a las pruebas en los l铆mites de servicio de nuestra aplicaci贸n, es decir, cuando salen a la base de datos, sistema de archivos o API externa.

Estas pruebas suelen ser escritas por desarrolladores, pero no tienen que ser as铆. Por definici贸n, las pruebas de integraci贸n tienen un alcance mayor y tardan m谩s en ejecutarse que las pruebas unitarias. Esto significa que las fallas de la prueba requieren cierta investigaci贸n: sabemos que uno de los componentes de nuestra prueba no funciona, pero es necesario encontrar la ubicaci贸n exacta de la falla. Esto contrasta con las pruebas unitarias que son de menor alcance e indican exactamente d贸nde fallaron las cosas.

Deber铆amos intentar ejecutar pruebas de integraci贸n en un entorno similar a la producci贸n; Esto minimiza la posibilidad de que las pruebas fallen debido a diferencias en la configuraci贸n. Ejemplo de prueba de integraci贸n

Supongamos que tenemos la siguiente funci贸n que toma una URL y una tupla de (palabra, ocurrencias). Nuestra funci贸n crea un registro y lo guarda en la base de datos:

def save_to_db(url, top_word):
    record = TopWord()
    record.url = url
    record.word = top_word[0]
    record.num_occurrences = top_word[1]

    db.session.add(record)
    db.session.commit()

    return record

Probamos esta funci贸n pasando informaci贸n conocida; la funci贸n debe guardar la informaci贸n que ingresamos en la base de datos. Nuestro c贸digo de prueba extrae el registro reci茅n guardado de la base de datos y confirma que sus campos coinciden con la entrada que ingresamos.

def test_save_to_db():
    url = "http://test_url.com"
    most_common_word_details = ("Python", 42)

    word = save_to_db(url, most_common_word_details)

    inserted_record = TopWord.query.get(word.id)
    assert inserted_record.url == "http://test_url.com"
    assert inserted_record.word == "Python"
    assert inserted_record.num_occurrences == 42

Observe c贸mo este es el tipo de prueba que hacemos manualmente para confirmar que las cosas funcionan como se esperaba. Automatizar esta prueba nos evita tener que verificar repetidamente esta funcionalidad cada vez que hacemos un cambio en el c贸digo.

End-to-End

pyramid test

Las pruebas de End to End verifican si el sistema cumple con nuestros requisitos comerciales definidos. Una prueba com煤n es trazar una ruta a trav茅s del sistema de la misma manera que un usuario lo experimentar铆a. Por ejemplo, podemos probar un nuevo flujo de trabajo del usuario: simular la creaci贸n de una cuenta, 鈥渉acer clic鈥 en el enlace en el correo electr贸nico de activaci贸n, iniciar sesi贸n por primera vez e interactuar con la ventana emergente modal de nuestra aplicaci贸n web.

Podemos realizar pruebas de extremo a extremo a trav茅s de nuestra interfaz de usuario (UI) aprovechando una herramienta de automatizaci贸n del navegador como Selenium. Esto crea una dependencia entre nuestra interfaz de usuario y nuestras pruebas, lo que hace que nuestras pruebas sean fr谩giles: un cambio en el front-end requiere que cambiemos las pruebas. Esto no es sostenible ya que nuestro front-end se volver谩 est谩tico o nuestras pruebas no se ejecutar谩n.

Una mejor soluci贸n es probar la capa subcut谩nea, es decir, la capa justo debajo de nuestra interfaz de usuario. Para una aplicaci贸n web, esto ser铆a probar la API REST, tanto enviando JSON como sacando JSON.

Nuestras pruebas subcut谩neas son nuestros contratos con nuestro front-end; pueden ser utilizados por nuestros desarrolladores front-end como una especificaci贸n de la API REST. Las herramientas, como swagger-meqa, que se basan en la especificaci贸n OpenAPI pueden ayudarnos a automatizar este proceso. Tambi茅n podr铆amos contar con herramientas completas como Postman para probar, depurar y validar nuestra API.

Las pruebas de extremo a extremo se consideran recuadro negro, ya que no necesitamos saber nada sobre la implementaci贸n para realizar las pruebas. Esto tambi茅n significa que las fallas en las pruebas no proporcionan una indicaci贸n de lo que sali贸 mal; necesitar铆amos usar registros para ayudarnos a rastrear el error y diagnosticar la falla del sistema.

vamos al codigo!

Aqu铆 estamos utilizando el cliente Flask Test para ejecutar pruebas subcut谩neas en nuestra API REST. Hay muchas cosas que suceden detr谩s de escena y el resultado que recibimos (c贸digo de estado HTTP) nos permite saber que la prueba pas贸 o fall贸.

def test_end_to_end():
    client = app.test_client()

    body = {"url": "https://www.python.org"}
    response = client.post("/top-word", json=body)

    assert response.status_code == HTTPStatus.OK

Structuring Tests

Cada caso de prueba se puede separar en las siguientes fases:

  • configurar el sistema bajo prueba (SUT) en el entorno requerido por el caso de prueba (condiciones previas)
  • realizando la acci贸n que queremos probar en SUT verificar si se produjo el resultado esperado (postcondiciones)
  • derribar SUT y devolver el medio ambiente al estado en que lo encontramos

Hay dos marcos ampliamente utilizados para estructurar pruebas: Arrange-Act-Assert y Given-When-Then. Organizar-Actuar-Afirmar (AAA)

El patr贸n AAA es una abstracci贸n para separar las diferentes partes de nuestras pruebas:

- Organizar todas las condiciones previas necesarias
- Actuar sobre el SUT
- Afirmar que se cumplen nuestras condiciones posteriores

Vamos la codigo!

def test_find_top_word():
    # Arrange
    words = ["foo", "bar", "bat", "baz", "foo", "baz", "foo"]

    # Act
    result = find_top_word(words)

    # Assert
    assert result[0] == "foo"
    assert result[1] == 3

GWT

GWT proporciona una abstracci贸n 煤til para separar las diferentes fases de nuestra prueba:

- Dado un conjunto de condiciones previas
- Cuando realizamos una acci贸n en el SUT
- Entonces nuestras condiciones posteriores deben ser las siguientes

GWT es ampliamente utilizado en Behavior Driven Development (BDD).

def test_find_top_word():
    # Given a list of word
    words = ["foo", "bar", "bat", "baz", "foo", "baz", "foo"]

    # When we run the function over the list
    result = find_top_word(words)

    # Then we should see `foo` occurring 3 times
    assert result[0] == "foo"
    assert result[1] == 3

Que testear!

pyramid test

Para probar que nuestro programa es correcto, tenemos que probarlo contra cada combinaci贸n concebible de valores de entrada. Este tipo de prueba exhaustiva no es pr谩ctica, por lo que debemos emplear estrategias de prueba que nos permitan seleccionar casos de prueba en los que es m谩s probable que se produzcan errores.

Los desarrolladores experimentados pueden equilibrar la escritura de c贸digo para resolver problemas comerciales con pruebas de escritura para garantizar la correcci贸n y evitar la regresi贸n. Encontrar este equilibrio y saber qu茅 probar puede sentirse m谩s como un arte que como una ciencia. Afortunadamente, hay algunas reglas generales que podemos seguir para asegurarnos de que nuestras pruebas sean exhaustivas. Requerimientos funcionales

Queremos asegurarnos de que se hayan implementado todos los requisitos relevantes. Nuestros casos de prueba deben ser lo suficientemente detallados para verificar los requisitos comerciales. No tiene sentido construir algo si no cumple con los criterios establecidos. Prueba de ruta de base

Tenemos que probar cada declaraci贸n al menos una vez. Si la declaraci贸n tiene un condicional (si o mientras), tenemos que variar nuestras pruebas para asegurarnos de probar todas las ramas del condicional. Por ejemplo, si tenemos el siguiente c贸digo:

if x > 18:
    # statement1
elif 18 >= x >= 35:
    # statement2
else:
    # statement3

Para asegurarnos de alcanzar todas las ramas del condicional anterior, necesitamos escribir las siguientes pruebas:

  1. x < 18
  2. 18 <= x <= 35
  3. x > 35

Partici贸n de equivalencia

Se dice que dos casos de prueba que resultan en la misma salida son equivalentes. Solo requerimos uno de los casos de prueba para cubrir esa clase de errores. An谩lisis de l铆mites

  • 鈥淗ay 2 problemas dif铆ciles en Ciencias de la Computaci贸n: invalidaci贸n de cach茅, nombres de cosas y errores fuera de 1鈥.

Este es uno de los chistes m谩s antiguos de la programaci贸n, pero hay mucha verdad detr谩s, a menudo confundimos si necesitamos un < o un <=. Es por eso que siempre debemos probar las condiciones de emtorno.

if x > 18:
    # statement1
else:
    # statement2

Para asegurarnos de probar a fondo las condiciones de entorno del fragmento de c贸digo anterior, deber铆amos tener casos de prueba para x = 17, x = 18 yx = 19. Tenga en cuenta que escribir casos de prueba se vuelve m谩s complicado si nuestro l铆mite tiene condicionales compuestos.

Clases de datos incorrectos

Esto se refiere a cualquiera de los siguientes casos:

  • Muy pocos datos (o sin datos) - Demasiados datos - Datos inv谩lidos - Tama帽o incorrecto de datos - Datos no inicializados

Prueba de flujo de datos

Se enfoca en rastrear el flujo de control del programa con un enfoque en explorar la secuencia de eventos relacionados con el estado de los objetos de datos. Por ejemplo, recibimos un error si intentamos acceder a una variable que ha sido eliminada. Podemos utilizar las pruebas de flujo de datos para obtener casos de prueba adicionales para variables que no han sido probadas por otras pruebas.

Error Adivinando

La experiencia pasada proporciona informaci贸n sobre partes de nuestra base de c贸digo que puede conducir a errores. Mantener un registro de los errores anteriores puede mejorar la probabilidad de que no vuelva a cometer el mismo error en el futuro. Resumen

Averiguar qu茅 probar y hacerlo de manera eficiente es a lo que me refiero cuando digo Art of Developer Testing. La 煤nica manera de mejorar en las pruebas es escribiendo pruebas, presentando mejores estrategias de prueba y aprendiendo sobre diferentes t茅cnicas de prueba. Al igual que en el desarrollo de software, cuanto m谩s sepa sobre algo, mejor se convertir谩 en eso.

Cuando escribir pruebas Si bien hay mucha discusi贸n interesante sobre cu谩ndo escribir pruebas, creo que se lleva lejos del punto de prueba. No importa cuando escribes pruebas, solo importa que escribas pruebas.

Tip

El problema con las pruebas unitarias a menudo es que se realizan demasiadas pruebas triviales, que es solo un c贸digo adicional que debe mantenerse 鈥 si se hace mal, es m谩s un obst谩culo que un beneficio 鈥 es por eso que decidir qu茅 probar es tan importante 鈥 pensar antes de actuar.

y esta en es una breve introduccion nos vemos en el siguiente post

Hasta pronto!