Pruebas unitarias con unitest en Python usando nosetests y Docker/docker-compose
Ya se han tocado en el blog el tema de pruebas unitarias, y usando Docker, los artículos anteriores lo pueden encontrar en la etiqueta unittest.
En este artículo se mostrará una clase calculadora que hace una suma y raíz cuadrada, y se tiene la clase para las pruebas unitarias.
Estructura de archivos y directorios
La estructura de archivos y directorios del directorio de trabajo del repositorio es la siguiente:
├── app
│ ├──
│ └── __init__
├── docker-compose
├── Dockerfile
└── test
└── unit
├── calculadora_test
└── __init__
Archivo Dockerfile y docker-compose. yml
En el archivo Dockerfile se define como sistema base python, se pasa las dependencias para usar nosetests y convertura.
A continuación el código del archivo Dockerfile:
FROM python
WORKDIR /code/
RUN pip3 install --upgrade pip
RUN pip3 install nose
RUN pip3 install nose-
cov RUN pip3 install
rednose RUN pip3 install
pytest RUN pip3 install
- pytest cov RUN pip3 install mock
ADD
/ . / code COPY
/ . / code
CMD nosetests --with-coverage
#CMD nosetests -sv --rednose
Toca comentar y descomentar las dos líneas de CMD si se requiere usar covertura o no.
pruebas :
build : .
volumes :
- ".: /code"
Se define el microservicio pruebas que usará el archivo Dockerfile, y se define el volumen.Módulo cálculadora
Este módulo tiene la clase calculadora con los métodos suma y raíz cuadrada, el código se muestra a continuación:
#
/usr/bin/env python ! #Se
importa sqrt math de
math import sqrt from
#Clase
calculadora
Calculadora: class #Metodo suma de x y y, se evalua si son enteros si no, devuelve error.
def suma(self,x,y):
type if x) == ( and type int y) == ( : int
x + y return
: else
TypeError raise "Invalid type: {} and {}" ( format . type ( x) ( type , y))) (
#Metodo raizcuadrada de X, devuelve la raiz cuadrada si es entero positivo, si no
#devuelve mensaje de error.
def raizCuadrada self ( x): ,
type if x) == ( and x >= 0: int
math return . sqrt x) (
: else
TypeError raise "Invalid type: {} " ( format . type ( x))) (
__name__ if == '_ _main__':
#Se crea una inst a ncia de lac lase
= Calculadora calc ) ( #Se calcula la suma de 2 y 2, se muestra en pantalla.
= results calc sum . 2, 2) (
(results)
Módulo de las pruebas
El archivo calculadora_tests.py contiene las pruebas para la suma y para la raíz cuadrada, adicional muestra una serie de métodos de otras pruebas que existen en unittest (que no tiene que ver con las pruebas de calculadora, sólo con fines ilustrativos del uso de unittest).
A continuación se muestra el código:
#
/usr/bin/ ! python3 env
import unittest
app from . import Calculadora calculadora
TestCalculadora class ( unittest TestCase): .
def setUp self): (
self . = Calculadora calc ) (
test_suma_retorna_resultado_correcto def self): ( ##Asegura
sea que la operacion igual de 2+2 a 4 suma
self . assertEqual 4, self ( . calc . suma 2,2)) (
test_suma_devuelve_error_si_el_tipo_no_es_entero def self): ( #Asegura error, si es de tipo, cuando se le pasa dos string.
self . assertRaises TypeError, self ( . calc . , "Hello", "World") suma
test_asegura_que_sea_verdadero def self): ( #Se
que el valor 1 asegura es . true y un string
self . assertTrue 1) (
self . assertTrue "Hello, World") (
test_aseura_que_sea_falso def self): ( #Se asegura que el string vacio y cero son falso.
self . assertFalse 0) (
self . assertFalse "") (
test_asegura_que_es_mayor def self): ( #Se
asegura 2>1 que
self . assertGreater 2, 1) (
test_asegura_que_es_mayor_e_igual def self): ( #Se
asegura 2>=2 que
self . assertGreaterEqual 2, 2) (
test_asegura_que_es_casi_igual_a_delta_0_5 def self): ( #Se
que sea casi asegura 1 y 1.2 con igual delta 0.5 de
self . assertAlmostEqual 1, 1.2, delta=0 ( 5) .
test_asegura_lugares_casi_igual def self): ( #Se asegura que es casi igual 1 y 1.00001 por 4 lugares.
self . assertAlmostEqual 1, 1.00001, places=4) (
test_asegura_diccionario_contiene_subconjunto def self): (
= {'a': 'b'} esperado
= {'a': 'b', 'c': 'd', 'e': 'f'} actual #Se asegura que el diccionario actual contiene lo esperado.
self . assertDictContainsSubset ( , actual) esperado
test_asegura_diccionarios_iguales def self): (
= {'a': 'b', 'c': 'd'} esperado
= {'c': 'd', 'a': 'b'} actual #Se
asegra el que diccionario sea igual al actual. esperado
self . assertDictEqual ( , actual) esperado
test_asegura_que_esta_en def self): ( #Se asegura que 1 este en la lista
self . assertIn 1, [1,2,3,4,5]) (
test_asegura_expresiones_iguales def self): ( #Se asegura que las expresiones son iguales expre1 y expre2
self . assertIs "a", "a") (
test_asegura_objeto_es_instancia_de_una_clase def self): ( #Se asegura que el objeto 2 sea de la clase entero
self . assertIsInstance 2, ( ) int
test_asegura_objeto_no_es_instancia_de_una_clase def self): ( #Se
que el asegura 2 no sea una objeto clase str
self . assertNotIsInstance 2, ( ) str
test_asegura_que_es_none def self): ( #Se
asegura que None. es
self . assertIsNone None) (
test_asegura_expresiones_no_sean_iguales def self): (
self . assertIsNot [], []) (
test_asegura_que_no_sea_none def self): ( #Se
asegura 1 no que None. es
self . assertIsNotNone 1) (
test_asegura_que_es_menor def self): ( #Se asegura que 3 es menor que 5
self . assertLess 3, 5) (
test_asegura_que_es_menor_e_igual def self): ( #Se asegura que 7 es menor o igual que 7.
self . assertLessEqual 7, 7) (
__nam if e__ =
= '__main__':
unittest . m a in) (
Ejecución de las pruebas
Ejecución de pruebas unitarias
Para ejecutar las pruebas se deja descomentada la línea en el archivo Dockerfile que dice:
#CMD nosetests --with-coverage
CMD nosetests -sv --rednose
Se construye la imagen:
Se ejecuta el contenedor:
El resultado de la ejecución se muestra a continuación:
Recreating tutorialespruebas_pruebas_1
Attaching to tutorialespruebas_pruebas_1
pruebas_1 | test_asegura_diccionario_contiene_subconjunto (unit. calculadora_test. TestCalculadora) ... passed
pruebas_1 | test_asegura_diccionarios_iguales (unit. calculadora_test. TestCalculadora) ... passed
pruebas_1 | test_asegura_expresiones_iguales (unit. calculadora_test. TestCalculadora) ... passed
pruebas_1 | test_asegura_expresiones_no_sean_iguales (unit. calculadora_test. TestCalculadora) ... passed
pruebas_1 | test_asegura_lugares_casi_igual (unit. calculadora_test. TestCalculadora) ... passed
pruebas_1 | test_asegura_objeto_es_instancia_de_una_clase (unit. calculadora_test. TestCalculadora) ... passed
pruebas_1 | test_asegura_objeto_no_es_instancia_de_una_clase (unit. calculadora_test. TestCalculadora) ... passed
pruebas_1 | test_asegura_que_es_casi_igual_a_delta_0_5 (unit. calculadora_test. TestCalculadora) ... passed
pruebas_1 | test_asegura_que_es_mayor (unit. calculadora_test. TestCalculadora) ... passed
pruebas_1 | test_asegura_que_es_mayor_e_igual (unit. calculadora_test. TestCalculadora) ... passed
pruebas_1 | test_asegura_que_es_menor (unit. calculadora_test. TestCalculadora) ... passed
pruebas_1 | test_asegura_que_es_menor_e_igual (unit. calculadora_test. TestCalculadora) ... passed
pruebas_1 | test_asegura_que_es_none (unit. calculadora_test. TestCalculadora) ... passed
pruebas_1 | test_asegura_que_esta_en (unit. calculadora_test. TestCalculadora) ... passed
pruebas_1 | test_asegura_que_no_sea_none (unit. calculadora_test. TestCalculadora) ... passed
pruebas_1 | test_asegura_que_sea_verdadero (unit. calculadora_test. TestCalculadora) ... passed
pruebas_1 | test_aseura_que_sea_falso (unit. calculadora_test. TestCalculadora) ... passed
pruebas_1 | test_suma_devuelve_error_si_el_tipo_no_es_entero (unit. calculadora_test. TestCalculadora) ... passed
pruebas_1 | test_suma_retorna_resultado_correcto (unit. calculadora_test. TestCalculadora) ... passed
pruebas_1 |
pruebas_1 | -----------------------------------------------------------------------------
pruebas_1 | 19 tests run in 0.072 seconds (19 tests passed)
tutorialespruebas_pruebas_1 exited with code 0
Se ejecutaron 19 pruebas y todas pasaron, la ejecución se tardó 0.072 segundos.
Ejecución de pruebas de conbertura
La cobertura de código permite medir la calidad de las pruebas ( más información en wikipedia).
Para ejecutar la prueba es necesario modificar el archivo Dockerfile para que se use la cobertura:
CMD nosetests --with-coverage
#CMD nosetests -sv --rednose
Se ejecuta el contendor de la prueba:
Recreating tutorialespruebas_pruebas_1
Attaching to tutorialespruebas_pruebas_1
pruebas_1 | ...................
pruebas_1 | Name Stmts Miss Cover
pruebas_1 | ----------------------------------------
pruebas_1 | app. py 0 0 100%
pruebas_1 | app/calculadora . py 14 6 57%
pruebas_1 | unit. py 0 0 100%
pruebas_1 | ----------------------------------------
pruebas_1 | TOTAL 14 6 57%
pruebas_1 | ----------------------------------------------------------------------
pruebas_1 | Ran 19 tests in 0.019s
pruebas_1 |
pruebas_1 | OK
tutorialespruebas_pruebas_1 exited with code 0
Como se ve el módulo calculadora cubre un 57% de cobertura.
El código de este proyecto se encuentra en el repo tutoriales de pruebas en la rama nosetests.
No hay comentarios:
Publicar un comentario