Módulo unittest de Python

Beginner

This tutorial is from open-source community. Access the source code

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.

  1. Primero, abramos el archivo stock.py. Esto nos ayudará a entender la clase Stock que vamos a probar. Al mirar el código en stock.py, podemos ver cómo está estructurada la clase, qué atributos tiene y qué métodos proporciona. Para ver el contenido del archivo stock.py, ejecuta el siguiente comando en tu terminal:
cat stock.py
  1. Ahora, es el momento de crear un nuevo archivo llamado teststock.py utilizando tu editor de texto preferido. Este archivo contendrá nuestros casos de prueba para la clase Stock. Aquí está el código que debes escribir en el archivo teststock.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ódulo unittest, que proporciona las herramientas y clases necesarias para escribir y ejecutar pruebas en Python.
  • import stock: Esto importa el módulo que contiene nuestra clase Stock. Sin esta importación, no podríamos acceder a la clase Stock en nuestro código de prueba.
  • class TestStock(unittest.TestCase): Creamos una nueva clase llamada TestStock que hereda de unittest.TestCase. Esto convierte nuestra clase TestStock en 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 prefijo test_. Este método crea una instancia de la clase Stock y luego utiliza el método assertEqual para verificar si los atributos de la instancia de Stock coinciden con los valores esperados.
  • assertEqual: Este es un método proporcionado por la clase TestCase. 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 clase TestStock y mostrará los resultados.
  1. 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.

  1. Abre el archivo teststock.py. Dentro de la clase TestStock, vamos a agregar algunos nuevos métodos de prueba. Estos métodos probarán diferentes partes de la clase Stock. 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 objeto Stock utilizando argumentos de palabra clave. Comprueba que los atributos del objeto se establecen correctamente.
  • test_cost: Esta prueba verifica si la propiedad cost de un objeto Stock devuelve el valor correcto, que se calcula como el número de acciones multiplicado por el precio.
  • test_sell: Esta prueba verifica si el método sell() de un objeto Stock actualiza correctamente el número de acciones después de vender algunas.
  • test_from_row: Esta prueba verifica si el método de clase from_row() puede crear una nueva instancia de Stock a partir de una fila de datos.
  • test_repr: Esta prueba verifica si el método __repr__() de un objeto Stock devuelve la representación de cadena esperada.
  • test_eq: Esta prueba verifica si el método __eq__() compara correctamente dos objetos Stock para ver si son iguales.
  1. 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.

  1. 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 bloque with genera 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 shares a una cadena debe generar un TypeError porque shares debe ser un número.
  • Establecer el atributo shares a un número negativo debe generar un ValueError ya que el número de acciones no puede ser negativo.
  • Establecer el atributo price a una cadena debe generar un TypeError porque price debe ser un número.
  • Establecer el atributo price a un número negativo debe generar un ValueError ya que el precio no puede ser negativo.
  • Intentar establecer un atributo inexistente share (observa la falta de la 's') debe generar un AttributeError porque el nombre correcto del atributo es shares.
  1. 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.

  1. 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.

  1. 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.

  1. 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.

  1. 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.

  1. 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 con test_ y tenga la extensión .py.

Deberías ver que se ejecutan y pasan las 12 pruebas, al igual que antes.

  1. 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.