El framework unittest es una herrmaienta robusta integrada en la biblioteca estándar de Python, diseñada originalmente para pruebas unitarias. Sin embargo, su versatilidad permite extender su uso a la automatización de pruebas web, proporcionando mecanismos para organizar casos de prueba, realizar aserciones detalaldas y generar reportes de resultados.
Componentes fundamentales de unittest
Para dominar este framework, es esencial comprender sus clases y métodos principales:
- unittest.TestCase: Es la clase base de la que deben heredar todos los casos de prueba. Proporciona la estructura necesaria para ejecutar métodos de prueba individuales.
- unittest.main(): Una interfaz de línea de comandos que busca automáticamente métodos que comiencen con el prefijo
testy los ejecuta. El orden de ejecución se basa en el orden lexicográfico de los nombres de los métodos. - unittest.TestSuite: Permite agrupar múltiples casos de prueba en una "suite" para ejecutarlos de forma conjunta.
- unittest.TextTestRunner: El motor encargado de orquestar la ejecución de las pruebas y mostrar los resultados en la salida estándar.
- unittest.defaultTestLoader: Utilizado para cargar pruebas desde módulos o directorios. El método
discover()es especialmente útil para encontrar archivos de prueba que sigan un patrón específico (ej.test_*.py).
Decoradores para el control de ejecución
Existen situaciones donde es necesario omitir ciertas pruebas basándose en condiciones específicas:
@unittest.skip(motivo): Omite la prueba incondicionalmente.@unittest.skipIf(condicion, motivo): Omite la prueba si la condición se evalúa como verdadera.@unittest.skipUnless(condicion, motivo): Omite la prueba a menos que la condición sea verdadera.@unittest.expectedFailure: Marca la prueba como un fallo esperado.
Ciclo de vida y Aserciones
Cada clase que hereda de TestCase puede implementar métodos para gestionar el estado antes y después de cada prueba:
- setUp(): Se ejecuta antes de cada método de prueba. Ideal para inicializar controladores de bases de datos o abrir sesiones de navegador.
- tearDown(): Se ejecuta después de cada método de prueba, sin importar si falló o tuvo éxito. Se utiliza para limpieza de datos o cerrar procesos.
Las aserciones permiten validar los resultados esperados frente a los obtenidos:
self.assertEqual(a, b) # Verifica si a == b
self.assertIn(a, b) # Verifica si a está contenido en b
self.assertTrue(x) # Verifica si x es verdadero
self.assertIsNone(x) # Verifica si x es None
self.assertIsInstance(a, b) # Verifica la instancia del objeto
Implementación práctica: Lógica y estructura
A continuación, se presenta un ejemplo de cómo estructurar una clase de prueba con diferentes escenarios y métodos de ejecución:
import unittest
class ValidadorMatematico(unittest.TestCase):
def setUp(self):
# Preparación del entorno de prueba
self.valor_referencia = 50
def test_verificar_igualdad(self):
resultado = 25 * 2
self.assertEqual(resultado, self.valor_referencia, msg="La multiplicación no coincide")
def test_verificar_rango(self):
valor_test = 60
self.assertGreater(valor_test, self.valor_referencia)
@unittest.skip("Demostración de omisión")
def test_operacion_omitida(self):
self.assertEqual(1, 2)
def tearDown(self):
# Limpieza de recursos
pass
if __name__ == '__main__':
# Opción 1: Ejecución automática
# unittest.main()
# Opción 2: Suite personalizada
suite_pruebas = unittest.TestSuite()
suite_pruebas.addTest(ValidadorMatematico('test_verificar_igualdad'))
ejecutor = unittest.TextTestRunner(verbosity=2)
ejecutor.run(suite_pruebas)
Integración con Selenium para Pruebas Web
El uso de unittest junto con Selenium WebDriver permite crear scripts de automatización estructurados. A continuación, se muestra una implementación para validar un flujo básico de búsqueda:
from selenium import webdriver
from selenium.webdriver.common.by import By
from selenium.webdriver.common.keys import Keys
import unittest
import time
class SuiteNavegacion(unittest.TestCase):
def setUp(self):
self.driver = webdriver.Chrome()
self.driver.maximize_window()
self.driver.implicitly_wait(15)
self.url_destino = "https://www.google.com"
def test_busqueda_termino(self):
navegador = self.driver
navegador.get(self.url_destino)
# Localización de elementos y acción
cuadro_busqueda = navegador.find_element(By.NAME, "q")
cuadro_busqueda.send_keys("Python unittest")
cuadro_busqueda.send_keys(Keys.RETURN)
time.sleep(2)
# Validación del título de la página
self.assertIn("unittest", navegador.title)
def tearDown(self):
if self.driver:
self.driver.quit()
if __name__ == "__main__":
unittest.main()
Organización Dinámica de Pruebas
En proyectos de gran escala, es ineficiente añadir manualmente cada caso a una suite. La mejor práctica es utilizar el descubrimiento automático de módulos:
import unittest
def ejecutar_todas_las_pruebas():
# Define la ruta donde se encuentran los archivos .py de prueba
directorio_pruebas = './pruebas_modulos'
# Descubre y carga todas las pruebas que coincidan con el patrón
loader = unittest.defaultTestLoader
suite_descubierta = loader.discover(start_dir=directorio_pruebas, pattern='test_*.py')
# Ejecuta la suite resultante
runner = unittest.TextTestRunner(verbosity=2)
runner.run(suite_descubierta)
if __name__ == "__main__":
ejecutar_todas_las_pruebas()