Métodos de Assert en UnitTest para Pruebas Efectivas
Resumen
UnitTest nos proporciona una amplia gama de métodos de aserción que mejoran la forma en que validamos nuestras pruebas. En esta clase, hemos explorado algunos de ellos y cómo utilizarlos en diferentes escenarios.
¿Cómo se usa el assertEqual en Unit Test?
El método assertEqual compara dos valores para verificar si son iguales. Acepta dos parámetros para comparar y opcionalmente un mensaje personalizado que se mostrará en la terminal si la prueba falla. Este método se integra bien con los editores, permitiendo ejecutar y depurar pruebas de manera eficiente.
Parámetros: valor esperado, valor obtenido, mensaje de error (opcional)
Uso típico: Validar igualdad de números, cadenas, o cualquier otro objeto comparable.
¿Qué otros métodos de aserción existen en Unit Test?
Además de assertEqual, Unit Test incluye muchos otros métodos de aserción útiles:
assertTrue: Verifica que una expresión sea verdadera. No compara valores, solo evalúa si una condición es cierta.
assertRaises: Valida que se lance una excepción específica dentro de un bloque de código, utilizando la palabra clave with como contexto.
assertIn y assertNotIn: Comprueban si un elemento está o no está dentro de una secuencia, como una lista o un conjunto.
¿Cómo se manejan excepciones en Unit Test?
Con assertRaises, se puede verificar que una excepción se lance correctamente. Este método es especialmente útil para manejar errores esperados, como cuando un usuario no tiene suficientes fondos para completar una transferencia.
Se utiliza con with para capturar la excepción dentro de un bloque de código.
Ejemplo: Capturar un ValueError al pasar un argumento no válido a una función.
¿Cómo comparar listas, diccionarios y sets en Unit Test?
Unit Test ofrece métodos para comparar estructuras de datos más complejas:
assertDictEqual: Compara dos diccionarios.
assertSetEqual: Compara dos sets para validar que contengan los mismos elementos, independientemente del orden.
Estos métodos también cuentan con variantes negativas, como assertNotEqual, para validar desigualdades.
¿Por qué validar excepciones en pruebas unitarias?
Detección temprana de errores: Identifica fallos en la lógica de tu código antes de que causen problemas en producción.
Código más robusto: Hace que tu código sea más resistente a errores inesperados.
Documentación implícita: Indica las condiciones de error y los tipos de errores posibles.
Facilita la depuración: Aísla la causa de los errores de manera más efectiva.
Mejora la calidad del código: Contribuye a un código más limpio y confiable.
Algunosimport unittest
import unittest
classAllAssertsTests(unittest.TestCase): def test_assert_equal(self): self.assertEqual(5,5,"Values should be equal") self.assertEqual("hello","hello","Strings should be equal") self.assertEqual([1,2,3],[1,2,3],"Lists should be equal") self.assertEqual((1,2),(1,2),"Tuples should be equal") self.assertEqual({'a':1},{'a':1},"Dictionaries should be equal") self.assertAlmostEqual(0.1+0.2,0.3, places=7, msg="Floats should be almost equal") self.assertEqual(10,10) self.assertEqual("Hola","Hola") def test_assert_not_equal(self): self.assertNotEqual(5,3,"Values should not be equal") def test_assert_true_or_false(self): self.assertTrue(3<5,"Condition should be true") self.assertFalse(5<3,"Condition should be false") self.assertTrue(True,"Value should be true") self.assertFalse(False,"Value should be false") self.assertTrue(True) self.assertFalse(False) def test_assert_true(self): self.assertTrue(3<5,"Condition should be true") def test_assert_false(self): self.assertFalse(5<3,"Condition should be false") def test_assert_is_none(self): self.assertIsNone(None,"Value should be None") def test_assert_is_not_none(self): self.assertIsNotNone(5,"Value should not be None") def test_assert_is(self): a = b =[1,2,3] self.assertIs(a, b,"Both should refer to the same object") def test_assert_is_not(self): a =[1,2,3] b =[1,2,3] self.assertIsNot(a, b,"Both should not refer to the same object") def test_assert_raises(self):with self.assertRaises(ZeroDivisionError): _ =1/0 # Division by zero
def test_assert_raises_with_message(self):with self.assertRaises(ValueError, msg="Should raise ValueError"):int("invalid")with self.assertRaises(ValueError):int("I am not a number") def test_assert_raises_regex(self):with self.assertRaisesRegex(ValueError,"invalid literal"):int("invalid") def test_assert_greater(self): self.assertGreater(5,3,"First value should be greater than second") def test_assert_greater_equal(self): self.assertGreaterEqual(5,5,"First value should be greater than or equal to second") def test_assert_in(self): self.assertIn(3,[1,2,3],"Value should be in the list") self.assertIn('a','apple',"Character should be in the string") self.assertIn(1,{1,2,3},"Value should be in the set") self.assertIn('key',{'key':'value'},"Key should be in the dictionary") self.assertIn(2,(1,2,3),"Value should be in the tuple") self.assertIn(10,[2,4,5,10]) self.assertIn('H','Hola') def test_assert_not_in(self): self.assertNotIn(4,[1,2,3],"Value should not be in the list") self.assertNotIn('b','apple',"Character should not be in the string") self.assertNotIn(4,{1,2,3},"Value should not be in the set") self.assertNotIn('missing_key',{'key':'value'},"Key should not be in the dictionary") self.assertNotIn(4,(1,2,3),"Value should not be in the tuple") self.assertNotIn(5,[2,4,10]) self.assertNotIn('z','Hola') def test_assert_is_empty(self): self.assertEqual(len([]),0,"List should be empty") self.assertEqual(len(''),0,"String should be empty") self.assertEqual(len({}),0,"Dictionary should be empty") self.assertEqual(len(set()),0,"Set should be empty") self.assertEqual(len(()),0,"Tuple should be empty") self.assertEqual(len([]),0) self.assertEqual(len(''),0) self.assertEqual(len({}),0) self.assertEqual(len(set()),0) self.assertEqual(len(()),0) def test_assert_is_not_empty(self): self.assertNotEqual(len([1]),0,"List should not be empty") self.assertNotEqual(len('a'),0,"String should not be empty") self.assertNotEqual(len({'key':'value'}),0,"Dictionary should not be empty") self.assertNotEqual(len({1}),0,"Set should not be empty") self.assertNotEqual(len((1,)),0,"Tuple should not be empty") self.assertNotEqual(len([1]),0) self.assertNotEqual(len('a'),0) self.assertNotEqual(len({'key':'value'}),0) self.assertNotEqual(len({1}),0) self.assertNotEqual(len((1,)),0) def test_assert_count_equal(self): self.assertCountEqual([1,2,2,3],[3,2,1,2],"Both lists should have the same elements with the same frequency") self.assertCountEqual(['a','b','b'],['b','a','b'],"Both lists should have the same elements with the same frequency") self.assertCountEqual([1,2,3],[1,2,3],"Both lists should have the same elements with the same frequency") self.assertCountEqual([],[],"Both lists should be empty") def test_assert_multi_line_equal(self): self.assertMultiLineEqual("Hello\nWorld","Hello\nWorld","Both multi-line strings should be equal") self.assertMultiLineEqual("Line1\nLine2\nLine3","Line1\nLine2\nLine3","Both multi-line strings should be equal") def test_assert_sequence_equal(self): self.assertSequenceEqual([1,2,3],[1,2,3],"Both sequences should be equal") self.assertSequenceEqual((1,2),(1,2),"Both sequences should be equal") self.assertSequenceEqual("abc","abc","Both sequences should be equal") def test_assert_list_equal(self): self.assertListEqual([1,2,3],[1,2,3],"Both lists should be equal") self.assertListEqual([],[],"Both lists should be empty") def test_assert_tuple_equal(self): self.assertTupleEqual((1,2),(1,2),"Both tuples should be equal") self.assertTupleEqual((),(),"Both tuples should be empty") def test_assert_dict_equal(self): self.assertDictEqual({'a':1,'b':2},{'a':1,'b':2},"Both dictionaries should be equal") self.assertDictEqual({},{},"Both dictionaries should be empty") def test_assert_set_equal(self): self.assertSetEqual({1,2,3},{3,2,1},"Both sets should be equal") self.assertSetEqual(set(),set(),"Both sets should be empty") def test_assert_count(self): self.assertEqual(len([1,2,3]),3,"List should have 3 elements") self.assertEqual(len('abc'),3,"String should have 3 characters") self.assertEqual(len({'a':1,'b':2}),2,"Dictionary should have 2 key-value pairs") self.assertEqual(len({1,2,3}),3,"Set should have 3 elements") self.assertEqual(len((1,2)),2,"Tuple should have 2 elements") def test_assert_almost_equal(self): self.assertAlmostEqual(0.1+0.2,0.3, places=7, msg="Floats should be almost equal") self.assertAlmostEqual(1.0000001,1.0000002, places=6, msg="Floats should be almost equal") def test_assert_not_almost_equal(self): self.assertNotAlmostEqual(0.1+0.2,0.4, places=7, msg="Floats should not be almost equal") self.assertNotAlmostEqual(1.0001,1.0002, places=4, msg="Floats should not be almost equal") def test_assert_dicts(self): self.assertDictEqual({'a':1,'b':2},{'a':1,'b':2},"Both dictionaries should be equal") self.assertDictEqual({},{},"Both dictionaries should be empty") user ={'name':'Alice','age':30} expected_user ={'name':'Alice','age':30} self.assertDictEqual(user, expected_user,"User dictionaries should be equal") self.assertDictEqual({'key':'value'},{'key':'value'}) user ={"first_name":"Luis","last_name":"Martinez"} self.assertDictEqual({"first_name":"Luis","last_name":"Martinez"}, user
) self.assertSetEqual({1,2,3},{1,2,3}) self.assertSetEqual({"python","java","c++"},{"c++","java","python"}) def test_assert_is_instance(self): self.assertIsInstance(5, int,"Value should be an instance of int") def test_assert_not_is_instance(self): self.assertNotIsInstance(5, str,"Value should not be an instance of str")
¡Solución Reto Clases Anteriores!!
classBankAccount: def __init__(self, initial_balance=0, log_file=None): self.balance= initial_balance
self.log_file= log_file
self._log_transaction(f"Account created with initial balance: {self.balance}") def _log_transaction(self, message):if self.log_file:withopen(self.log_file,'a')asf: f.write(f"{message} \n") def deposit(self, amount):if amount >0: self.balance+= amount
self._log_transaction(f"Deposited: {amount}. New balance: {self.balance}")else: raise ValueError("Deposit amount must be positive")return self.balance def withdraw(self, amount):if amount > self.balance: raise ValueError("Insufficient funds") elif amount <=0: raise ValueError("Withdrawal amount must be positive")else: self.balance-= amount
self._log_transaction(f"Withdrew: {amount}. New balance: {self.balance}")return self.balance def get_balance(self): self._log_transaction(f"Check balance. Current balance: {self.balance}")return self.balance def transfer(self, amount, target_account):if amount <=0: raise ValueError("Transfer amount must be positive")if amount > self.balance: self._log_transaction("Transfer failed: No balance available") raise ValueError("Insufficient funds for transfer") self.balance-= amount
target_account.deposit(amount) self._log_transaction(f"Transferred: {amount} to {target_account}. New balance: {self.balance}")return self.balance
import unittest, os
from src.bank_accountimportBankAccountclassBankAccountTests(unittest.TestCase): def setUp(self)->None: self.account=BankAccount(initial_balance=1000, log_file="transaction_log.txt") def tearDown(self)->None:if os.path.exists(self.account.log_file): os.remove(self.account.log_file) def _count_lines(self, filename):withopen(filename,'r')asf:returnlen(f.readlines()) def test_deposit(self): new_balance = self.account.deposit(500) self.assertEqual(new_balance,1500,"The balance sheet is not equal") def test_withdraw(self): new_balance = self.account.withdraw(200) self.assertEqual(new_balance,800,"The balance sheet is not equal") def test_withdraw_with_deposit(self): account =BankAccount() account.deposit(200) account.withdraw(50) assert account.balance==150 def test_get_balance(self): self.assertEqual(self.account.get_balance(),1000) def test_withdraw_insufficient_funds(self): account =BankAccount() account.deposit(50)with self.assertRaises(ValueError): account.withdraw(100) # ✅ Transferencia exitosa
def test_transfer_successful(self): target =BankAccount(initial_balance=200) self.account.transfer(300, target) assert self.account.get_balance()==700 assert target.get_balance()==500 # ❌ Transferencia fallida por fondos insuficientes
def test_transfer_insufficient_funds(self): target =BankAccount(initial_balance=100)with self.assertRaises(ValueError): self.account.transfer(2000, target) def test_transaction_log(self): self.account.deposit(500) self.assertTrue(os.path.exists('transaction_log.txt')) def test_count_transactions(self): assert self._count_lines(self.account.log_file)==1 # Creación de la cuenta
self.account.deposit(500) assert self._count_lines(self.account.log_file)==2 def test_transfer_insufficient_funds_logs_failure(self): target =BankAccount(initial_balance=100)try: self.account.transfer(2000, target) except ValueError: pass # Se espera la excepción
withopen(self.account.log_file,'r')asf: log_content = f.read() assert "No balance available"in log_content
El reto usando with:
defdivision(a,b):if b ==0:raise ValueError("La divisón por cero no esta permitida")else:return a/b
#test:deftest_division_by_zero(self):with self.assertRaises(ZeroDivisionError): division(10,0)```def division(a,b):if b ==0:raise ValueError("La divisón por cero no esta permitida")else:return a/b
Estás lanzando una excepción ValueError con el mensaje "La división por cero no está permitida", en lugar de una excepción ZeroDivisionError. Como tu prueba está esperando un ZeroDivisionError, la prueba falla porque se lanza una excepción diferente.
@Germán Salina buenas tardes, si, me fije corriendo las pruebas en github actions..... 🤣
self._log_transaction(f"Transferencia de {amount} realizada a {target_account_receiver.name}. Nuevo saldo: {self.balance}")
target_account_receiver._log_transaction(f"Transferencia de {amount} recibida de {self.name}. Nuevo saldo: {target_account_receiver.balance}")
else:
self._log_transaction(f"Transferencia de {amount} rechazada. Saldo insuficiente: {self.balance}")
target_account_receiver._log_transaction(f"Transferencia de {amount} rechazada por parte de {self.name}. Saldo insuficiente: {target_account_receiver.balance}")
raise ValueError("Saldo insuficiente")
Solución Reto Clases Anteriores
Yo lo resolví de la siguiente manera:
Que tal Team, dejo una lista de los asserts mas usados en unitest se que es dificil aprenderlos todos, pero con leerlos y comprenderlos podras recordar que hacen y luego buscar la documentación para usarlos.
Dejare la lista por aqui
assertEqual(a, b): Verifica si a y b son iguales.
assertNotEqual(a, b): Verifica si a y b no son iguales.
assertTrue(x): Verifica si x es verdadero.
assertFalse(x): Verifica si x es falso.
assertIs(a, b): Verifica si a y b son el mismo objeto.
assertIsNot(a, b): Verifica si a y b no son el mismo objeto.
assertIsNone(x): Verifica si x es None.
assertIsNotNone(x): Verifica si x no es None.
assertIn(a, b): Verifica si a está en b (para contenedores como listas, tuplas, diccionarios, conjuntos o cadenas).
assertNotIn(a, b): Verifica si a no está en b.
assertIsInstance(a, b): Verifica si a es una instancia de la clase b (o de una subclase).
assertNotIsInstance(a, b): Verifica si a no es una instancia de la clase b.
assertRaises(exc, fun, *args, **kwargs): Verifica que la llamada a fun(*args, **kwargs) lanza la excepción específica exc. También se puede usar como un administrador de contexto con la sintaxis with self.assertRaises(SomeException):.
assertRaisesRegex(exc, r, fun, *args, **kwargs) (disponible desde Python 3.2): Similar a assertRaises, pero también verifica que el mensaje de la excepción coincida con la expresión regular r. También se puede usar como administrador de contexto.
assertAlmostEqual(a, b, places=7, msg=None, delta=None): Verifica si a y b son aproximadamente iguales, considerando un número específico de lugares decimales (places, por defecto 7) o una diferencia máxima (delta).
assertNotAlmostEqual(a, b, places=7, msg=None, delta=None): Verifica si a y b no son aproximadamente iguales.
assertGreater(a, b): Verifica si a es mayor que b.
assertGreaterEqual(a, b): Verifica si a es mayor o igual que b.
assertLess(a, b): Verifica si a es menor que b.
assertLessEqual(a, b): Verifica si a es menor o igual que b.
assertRegex(text, regex) (disponible desde Python 3.1): Verifica si la cadena text coincide con la expresión regular regex.
assertNotRegex(text, regex) (disponible desde Python 3.2): Verifica si la cadena text no coincide con la expresión regular regex.
assertCountEqual(first, second) (disponible desde Python 3.2): Verifica si la secuencia first y la secuencia second contienen los mismos elementos, sin importar el orden.
#!/usr/bin/env pythonimport unittest, os
from src.bank_accountimportBankAccountclassBankAccountTests(unittest.TestCase): # setUp se ejecuta SIEMPREANTES de ejecutar una prueba
def setUp(self)->None: self.test_account=BankAccount(balance=1000, log_file="first_account.log") self.second_test_account=BankAccount(balance=200, log_file="second_account.log")
# tearDown elimina algo tras cada prueba
def tearDown(self)->None:if os.path.exists(self.test_account.log_file): os.remove(self.test_account.log_file)if os.path.exists(self.second_test_account.log_file): os.remove(self.second_test_account.log_file) # El tener el _ como funcionalidad deja a unitest saber que esto no es una prueba
def _count_lines(self, filename):withopen(filename,"r")asf:returnlen(f.readlines()) def test_get_balance(self): self.assertEqual(self.test_account.balance,1000,"El balance no puede obtenerse") def test_deposit(self): new_balance = self.test_account.deposit(500) assert new_balance ==1500 def test_withdraw(self): new_balance = self.test_account.withdraw(500) assert new_balance ==500 def test_transference(self): new_balance_first_account = self.test_account.transference(500, self.second_test_account) new_balance_second_account = self.second_test_account.balance assert new_balance_first_account ==500 assert new_balance_second_account ==700 def test_transaction_log(self): assert os.path.exists(self.test_account.log_file) def test_count_transaction(self): assert self._count_lines(self.test_account.log_file)==1 self.test_account.deposit(500) assert self._count_lines(self.test_account.log_file)==2 def test_not_enought_money(self):with self.assertRaises(ValueError): self.test_account.withdraw(1000000)withopen(self.test_account.log_file,"r")asf:lastline:str = f.readlines()[-1] message ="Not enought money to do withraw or transfere" self.assertTrue(message in lastline,"No se loguea la falta de fondos")
Estas fueron mis soluciones a los diferentes retos de las clases anteriores:
bank_calculator module:
test_banl_calculator module:
Sin querer había estado utilizando el assertRaises desde que dejaste el reto antepasado.
def test_insufficient_funds_transfer(self): target =BankAccount(balance=500)with self.assertRaises(ValueError)ascontext: self.account.transfer(2000, target) self.assertEqual(str(context.exception),"You do not have enough money")
Equivalencias de pytest con unittest
assertEqual(a, b) → assert a == b
assertNotEqual(a, b) → assert a != b
assertTrue(x) → assert x is True
assertFalse(x) → assert x is False
assertIsNone(x) → assert x is None
assertIsNotNone(x) → assert x is not None
assertIn(a, b) → assert a in b
assertNotIn(a, b) → assert a not in b
Ejemplo en código:
import pytest
def test_addition(): result =1+1 assert result ==2 # Verifica que el resultado sea 2def test_list(): my_list =[1,2,3] assert 2in my_list # Verifica que 2 esté en la lista
assert 4 not in my_list # Verifica que 4 no esté en la lista
def test_string(): my_string ="Hello, world!" assert "Hello"in my_string # Verifica que 'Hello' esté en la cadena
assert "hello" not in my_string # Verifica que 'hello' no esté en la cadena
def test_dict(): my_dict ={'player':'Ronaldo'} assert my_dict.get('player')=='Ronaldo' # Verifica que el valor de la clave sea 'value'def test_zero_division():with pytest.raises(ZeroDivisionError):1/0 # Verifica que la excepción ZeroDivisionError se lance
def test_float_comparison(): result =0.1+0.2 assert result == pytest.approx(0.3, rel=1e-9) # Compara números flotantes con un margen de error
def test_string_error(): my_string ="Error: Something went wrong" assert "Error"in my_string # Verifica si "Error" está en la cadena
with pytest.raises(ValueError, match="Something went wrong"): raise ValueError("Something went wrong") # Verifica que el mensaje de la excepción coincida