Introducción
En este laboratorio, aprenderás cómo utilizar el módulo incorporado unittest de Python. Este módulo ofrece un marco (framework) para organizar y ejecutar pruebas, que es una parte crucial del desarrollo de software para garantizar que tu código funcione correctamente.
También aprenderás a crear y ejecutar casos de prueba básicos, y a probar errores y excepciones esperados. El módulo unittest simplifica el proceso de creación de conjuntos de pruebas (test suites), ejecución de pruebas y verificación de resultados. El archivo teststock.py se creará durante este laboratorio.
Creando tu primera prueba unitaria
El módulo unittest de Python es una herramienta poderosa que ofrece una forma estructurada de organizar y ejecutar pruebas. Antes de sumergirnos en la escritura de nuestra primera prueba unitaria, entendamos algunos conceptos clave. Los test fixtures son métodos como setUp y tearDown que ayudan a preparar el entorno antes de una prueba y limpiarlo después. Los casos de prueba (test cases) son unidades individuales de prueba, los conjuntos de pruebas (test suites) son colecciones de casos de prueba, y los ejecutores de pruebas (test runners) son responsables de ejecutar estas pruebas y presentar los resultados.
En este primer paso, vamos a crear un archivo de prueba básico para la clase Stock, que ya está definida en el archivo stock.py.
- Primero, abramos el archivo
stock.py. Esto nos ayudará a entender la claseStockque vamos a probar. Al mirar el código enstock.py, podemos ver cómo está estructurada la clase, qué atributos tiene y qué métodos proporciona. Para ver el contenido del archivostock.py, ejecuta el siguiente comando en tu terminal:
cat stock.py
- Ahora, es el momento de crear un nuevo archivo llamado
teststock.pyutilizando tu editor de texto preferido. Este archivo contendrá nuestros casos de prueba para la claseStock. Aquí está el código que debes escribir en el archivoteststock.py:
## teststock.py
import unittest
import stock
class TestStock(unittest.TestCase):
def test_create(self):
s = stock.Stock('GOOG', 100, 490.1)
self.assertEqual(s.name, 'GOOG')
self.assertEqual(s.shares, 100)
self.assertEqual(s.price, 490.1)
if __name__ == '__main__':
unittest.main()
Desglosemos los componentes clave de este código:
import unittest: Esta línea importa el módulounittest, que proporciona las herramientas y clases necesarias para escribir y ejecutar pruebas en Python.import stock: Esto importa el módulo que contiene nuestra claseStock. Sin esta importación, no podríamos acceder a la claseStocken nuestro código de prueba.class TestStock(unittest.TestCase): Creamos una nueva clase llamadaTestStockque hereda deunittest.TestCase. Esto convierte nuestra claseTestStocken una clase de caso de prueba, que puede contener múltiples métodos de prueba.def test_create(self): Este es un método de prueba. En el marco (framework)unittest, todos los métodos de prueba deben comenzar con el prefijotest_. Este método crea una instancia de la claseStocky luego utiliza el métodoassertEqualpara verificar si los atributos de la instancia deStockcoinciden con los valores esperados.assertEqual: Este es un método proporcionado por la claseTestCase. Verifica si dos valores son iguales. Si no son iguales, la prueba fallará.unittest.main(): Cuando se ejecuta este script directamente,unittest.main()ejecutará todos los métodos de prueba en la claseTestStocky mostrará los resultados.
- Después de escribir el código en el archivo
teststock.py, guárdalo. Luego, ejecuta el siguiente comando en tu terminal para ejecutar la prueba:
python3 teststock.py
Deberías ver una salida similar a esta:
.
----------------------------------------------------------------------
Ran 1 test in 0.001s
OK
El punto único (.) en la salida indica que una prueba se ha pasado con éxito. Si una prueba falla, verás una F en lugar del punto, junto con información detallada sobre lo que salió mal en la prueba. Esta salida te ayuda a identificar rápidamente si tu código está funcionando como se espera o si hay algún problema que necesita ser solucionado.
Ampliar tus casos de prueba
Ahora que has creado un caso de prueba básico, es hora de ampliar el alcance de tus pruebas. Agregar más pruebas te ayudará a cubrir la funcionalidad restante de la clase Stock. De esta manera, puedes asegurarte de que todos los aspectos de la clase funcionen como se espera. Modificaremos la clase TestStock para incluir pruebas para varios métodos y propiedades.
- Abre el archivo
teststock.py. Dentro de la claseTestStock, vamos a agregar algunos nuevos métodos de prueba. Estos métodos probarán diferentes partes de la claseStock. Aquí está el código que debes agregar:
def test_create_keyword_args(self):
s = stock.Stock(name='GOOG', shares=100, price=490.1)
self.assertEqual(s.name, 'GOOG')
self.assertEqual(s.shares, 100)
self.assertEqual(s.price, 490.1)
def test_cost(self):
s = stock.Stock('GOOG', 100, 490.1)
self.assertEqual(s.cost, 49010.0)
def test_sell(self):
s = stock.Stock('GOOG', 100, 490.1)
s.sell(20)
self.assertEqual(s.shares, 80)
def test_from_row(self):
row = ['GOOG', '100', '490.1']
s = stock.Stock.from_row(row)
self.assertEqual(s.name, 'GOOG')
self.assertEqual(s.shares, 100)
self.assertEqual(s.price, 490.1)
def test_repr(self):
s = stock.Stock('GOOG', 100, 490.1)
self.assertEqual(repr(s), "Stock('GOOG', 100, 490.1)")
def test_eq(self):
s1 = stock.Stock('GOOG', 100, 490.1)
s2 = stock.Stock('GOOG', 100, 490.1)
self.assertEqual(s1, s2)
Echemos un vistazo más de cerca a lo que hace cada una de estas pruebas:
test_create_keyword_args: Esta prueba verifica si se puede crear un objetoStockutilizando argumentos de palabra clave. Comprueba que los atributos del objeto se establecen correctamente.test_cost: Esta prueba verifica si la propiedadcostde un objetoStockdevuelve el valor correcto, que se calcula como el número de acciones multiplicado por el precio.test_sell: Esta prueba verifica si el métodosell()de un objetoStockactualiza correctamente el número de acciones después de vender algunas.test_from_row: Esta prueba verifica si el método de clasefrom_row()puede crear una nueva instancia deStocka partir de una fila de datos.test_repr: Esta prueba verifica si el método__repr__()de un objetoStockdevuelve la representación de cadena esperada.test_eq: Esta prueba verifica si el método__eq__()compara correctamente dos objetosStockpara ver si son iguales.
- Después de agregar estos métodos de prueba, guarda el archivo
teststock.py. Luego, ejecuta las pruebas nuevamente utilizando el siguiente comando en tu terminal:
python3 teststock.py
Si todas las pruebas pasan, deberías ver una salida como esta:
......
----------------------------------------------------------------------
Ran 7 tests in 0.001s
OK
Los siete puntos en la salida representan cada prueba. Cada punto indica que una prueba se ha pasado con éxito. Entonces, si ves siete puntos, significa que las siete pruebas han pasado.
Pruebas de excepciones
Las pruebas son una parte crucial del desarrollo de software, y un aspecto importante de ellas es garantizar que tu código pueda manejar adecuadamente las condiciones de error. En Python, el módulo unittest proporciona una forma conveniente de probar si se generan excepciones específicas como se espera.
- Abre el archivo
teststock.py. Vamos a agregar algunos métodos de prueba diseñados para comprobar si se generan excepciones. Estas pruebas nos ayudarán a asegurarnos de que nuestro código se comporta correctamente cuando encuentra una entrada no válida.
def test_shares_type(self):
s = stock.Stock('GOOG', 100, 490.1)
with self.assertRaises(TypeError):
s.shares = '50'
def test_shares_value(self):
s = stock.Stock('GOOG', 100, 490.1)
with self.assertRaises(ValueError):
s.shares = -50
def test_price_type(self):
s = stock.Stock('GOOG', 100, 490.1)
with self.assertRaises(TypeError):
s.price = '490.1'
def test_price_value(self):
s = stock.Stock('GOOG', 100, 490.1)
with self.assertRaises(ValueError):
s.price = -490.1
def test_attribute_error(self):
s = stock.Stock('GOOG', 100, 490.1)
with self.assertRaises(AttributeError):
s.share = 100 ## 'share' is incorrect, should be 'shares'
Ahora, entendamos cómo funcionan estas pruebas de excepciones.
- La declaración
with self.assertRaises(ExceptionType):crea un gestor de contexto (context manager). Este gestor de contexto comprueba si el código dentro del bloquewithgenera la excepción especificada. - Si se genera la excepción esperada dentro del bloque
with, la prueba pasa. Esto significa que nuestro código está detectando correctamente la entrada no válida y generando el error adecuado. - Si no se genera ninguna excepción o se genera una excepción diferente, la prueba falla. Esto indica que nuestro código puede no estar manejando la entrada no válida como se espera.
Estas pruebas están diseñadas para verificar los siguientes escenarios:
- Establecer el atributo
sharesa una cadena debe generar unTypeErrorporquesharesdebe ser un número. - Establecer el atributo
sharesa un número negativo debe generar unValueErrorya que el número de acciones no puede ser negativo. - Establecer el atributo
pricea una cadena debe generar unTypeErrorporquepricedebe ser un número. - Establecer el atributo
pricea un número negativo debe generar unValueErrorya que el precio no puede ser negativo. - Intentar establecer un atributo inexistente
share(observa la falta de la 's') debe generar unAttributeErrorporque el nombre correcto del atributo esshares.
- Después de agregar estos métodos de prueba, guarda el archivo
teststock.py. Luego, ejecuta todas las pruebas utilizando el siguiente comando en tu terminal:
python3 teststock.py
Si todo está funcionando correctamente, deberías ver una salida que indique que las 12 pruebas se han pasado. La salida se verá así:
............
----------------------------------------------------------------------
Ran 12 tests in 0.002s
OK
Los doce puntos representan todas las pruebas que has escrito hasta ahora. Había 7 pruebas del paso anterior, y acabamos de agregar 5 nuevas. Esta salida muestra que tu código está manejando las excepciones como se espera, lo cual es una gran señal de un programa bien probado.
Ejecución de pruebas seleccionadas y uso de la detección de pruebas
El módulo unittest en Python es una herramienta poderosa que te permite probar tu código de manera efectiva. Proporciona varias formas de ejecutar pruebas específicas o descubrir y ejecutar automáticamente todas las pruebas en tu proyecto. Esto es muy útil porque te ayuda a centrarte en partes específicas de tu código durante las pruebas o a comprobar rápidamente el conjunto de pruebas de todo el proyecto.
Ejecución de pruebas específicas
A veces, es posible que desees ejecutar solo métodos de prueba o clases de prueba específicas en lugar de todo el conjunto de pruebas. Puedes lograr esto utilizando la opción de patrón con el módulo unittest. Esto te da más control sobre qué pruebas se ejecutan, lo cual puede ser útil cuando estás depurando una parte particular de tu código.
- Para ejecutar solo las pruebas relacionadas con la creación de un objeto Stock:
python3 -m unittest teststock.TestStock.test_create
En este comando, python3 -m unittest le dice a Python que ejecute el módulo unittest. teststock es el nombre del archivo de prueba, TestStock es el nombre de la clase de prueba y test_create es el método de prueba específico que queremos ejecutar. Al ejecutar este comando, puedes comprobar rápidamente si el código relacionado con la creación de un objeto Stock está funcionando como se espera.
- Para ejecutar todas las pruebas en la clase
TestStock:
python3 -m unittest teststock.TestStock
Aquí, omitimos el nombre del método de prueba específico. Por lo tanto, este comando ejecutará todos los métodos de prueba dentro de la clase TestStock en el archivo teststock. Esto es útil cuando quieres comprobar la funcionalidad general de los casos de prueba del objeto Stock.
Uso de la detección de pruebas
El módulo unittest puede descubrir y ejecutar automáticamente todos los archivos de prueba en tu proyecto. Esto te ahorra el problema de especificar manualmente cada archivo de prueba para ejecutarlo, especialmente en proyectos más grandes con muchos archivos de prueba.
- Renombra el archivo actual para que siga el patrón de nomenclatura de detección de pruebas:
mv teststock.py test_stock.py
El mecanismo de detección de pruebas en unittest busca archivos que sigan el patrón de nomenclatura test_*.py. Al renombrar el archivo a test_stock.py, facilitamos que el módulo unittest encuentre y ejecute las pruebas en este archivo.
- Ejecuta la detección de pruebas:
python3 -m unittest discover
Este comando le dice al módulo unittest que descubra y ejecute automáticamente todos los archivos de prueba que coincidan con el patrón test_*.py en el directorio actual. Buscará en el directorio y ejecutará todos los casos de prueba encontrados en los archivos que coincidan.
- También puedes especificar un directorio para buscar pruebas:
python3 -m unittest discover -s . -p "test_*.py"
Donde:
-s .especifica el directorio donde comenzar la detección (en este caso, el directorio actual). El punto (.) representa el directorio actual. Puedes cambiar esto a otra ruta de directorio si quieres buscar pruebas en una ubicación diferente.-p "test_*.py"es el patrón para coincidir con los archivos de prueba. Esto asegura que solo se consideren como archivos de prueba los archivos cuyo nombre comience contest_y tenga la extensión.py.
Deberías ver que se ejecutan y pasan las 12 pruebas, al igual que antes.
- Renombra el archivo de vuelta al nombre original para mantener la coherencia con el laboratorio:
mv test_stock.py teststock.py
Después de ejecutar la detección de pruebas, renombramos el archivo a su nombre original para mantener el entorno del laboratorio consistente.
Al utilizar la detección de pruebas, puedes ejecutar fácilmente todas las pruebas en un proyecto sin tener que especificar cada archivo de prueba individualmente. Esto hace que el proceso de prueba sea más eficiente y menos propenso a errores.
Resumen
En este laboratorio, has aprendido cómo utilizar el módulo unittest de Python para crear y ejecutar pruebas automatizadas. Has creado un caso de prueba básico extendiendo la clase unittest.TestCase, has escrito pruebas para verificar el funcionamiento normal de los métodos y propiedades de una clase, y has creado pruebas para comprobar si se generan las excepciones adecuadas en condiciones de error. También has aprendido cómo ejecutar pruebas específicas y utilizar la detección de pruebas.
Las pruebas unitarias son una habilidad fundamental en el desarrollo de software, ya que garantizan la confiabilidad y corrección del código. Escribir pruebas exhaustivas ayuda a detectar errores desde el principio y brinda confianza en el comportamiento de tu código. A medida que desarrolles aplicaciones en Python, considera adoptar un enfoque de desarrollo guiado por pruebas (Test-Driven Development, TDD), escribiendo pruebas antes de implementar la funcionalidad para obtener un código más robusto y mantenible.