Samuel Alejandro Hurtado
Luis Martinez
ProfesorQué te pareció usar patch?
Samuel Alejandro Hurtado
excelente ya estoy agregando nuevas pruebas en el proyecto de mi trabajo jeje
En esta lección, hemos aprendido a modificar el comportamiento de objetos y funciones dentro de nuestras pruebas en Python, utilizando técnicas como el patch para simular situaciones específicas, como el control del horario de retiro en una cuenta bancaria. Esta habilidad es crucial cuando necesitamos validar restricciones temporales o cualquier otra lógica de negocio que dependa de factores externos, como el tiempo.
¿Cómo podemos restringir el horario de retiros en una cuenta bancaria?
Para implementar la restricción de horario, se utilizó la clase datetime para obtener la hora actual. Definimos que los retiros solo pueden realizarse durante el horario de oficina: entre las 8 AM y las 5 PM. Cualquier intento fuera de este horario lanzará una excepción personalizada llamada WithdrawalError.
BankAccount.datetime.now().hour.¿Cómo podemos probar la funcionalidad de manera efectiva?
Las pruebas unitarias permiten simular diferentes horas del día para validar que las restricciones funcionen correctamente. Para lograrlo, usamos el decorador patch del módulo unittest.mock, el cual modifica temporalmente el comportamiento de la función datetime.now().
patch, podemos definir un valor de retorno específico para now(), como las 7 AM o las 10 AM.¿Cómo corregimos errores en la lógica de negocio?
Durante la implementación, encontramos un error en la condición lógica del horario. Inicialmente, se utilizó un operador and incorrecto para verificar si la hora estaba dentro del rango permitido. Este error se corrigió cambiando la condición a un or, asegurando que la lógica prohibiera retiros antes de las 8 AM y después de las 5 PM.
El **patching** es una técnica comúnmente utilizada en las pruebas unitarias en Python para modificar temporalmente el comportamiento de un objeto o función durante la ejecución de las pruebas. Esto se hace principalmente para simular situaciones específicas o para evitar que el código real se ejecute, lo que permite que las pruebas sean más rápidas y controladas.
En Python, la biblioteca unittest.mock proporciona la función patch que se usa para este propósito. Aquí hay un desglose de cómo usar el patching:
### 1. Importar las Herramientas Necesarias
from unittest import TestCase from unittest.mock import patch
### 2. Usar patch como Decorador
Puedes usar patch como un decorador para reemplazar un objeto o función durante la prueba.
class MyTestCase(TestCase):       @patch('nombre\_del\_modulo.funcion\_o\_clase\_a\_mockear')   def test\_mi\_funcion(self, mock\_func):   mock\_func.return\_value = 'valor simulado'   resultado = mi\_funcion\_que\_llama\_a\_mock\_func()   self.assertEqual(resultado, 'valor simulado')
### 3. Usar patch como Context Manager
También puedes usar patch como un administrador de contexto, lo que te permite restaurar automáticamente el objeto original después de salir del bloque with.
class MyTestCase(TestCase):       def test\_mi\_funcion(self):   with patch('nombre\_del\_modulo.funcion\_a\_mockear') as mock\_func:   mock\_func.return\_value = 'valor simulado'   resultado = mi\_funcion\_que\_llama\_a\_mock\_func()   self.assertEqual(resultado, 'valor simulado')
### 4. Parchear Métodos de Clases
Si necesitas parchear un método de una clase, también puedes hacerlo. Por ejemplo:
class MiClase:   def metodo(self):   return 'valor real' class MyTestCase(TestCase):       @patch.object(MiClase, 'metodo')   def test\_metodo\_mock(self, mock\_metodo):   mock\_metodo.return\_value = 'valor simulado'   instancia = MiClase()   resultado = instancia.metodo()   self.assertEqual(resultado, 'valor simulado')
### 5. Comportamiento de Llamadas
Puedes verificar cómo se ha llamado un objeto simulado:
class MyTestCase(TestCase):       @patch('nombre\_del\_modulo.funcion\_a\_mockear')   def test\_mi\_funcion(self, mock\_func):   mi\_funcion\_que\_llama\_a\_mock\_func()   mock\_func.assert\_called\_once()
### Consideraciones
- **Rendimiento**: Usar patching evita la ejecución de código real que podría ser lento o tener efectos secundarios no deseados.
- **Aislamiento**: Permite pruebas unitarias aisladas sin depender de la implementación interna de las funciones o métodos que estás probando.
- **Simulación de Errores**: Puedes simular excepciones o errores que podrían ser difíciles de reproducir en situaciones del mundo real.
Usar patch es una excelente forma de escribir pruebas más efectivas y específicas al modificar comportamientos de manera temporal y controlada.
def withdraw(self, amount): now = datetime.now() if now.weekday() > 4: raise OutOfScheduleError() if now.hour < 8 or now.hour > 17: raise OutOfScheduleError() @patch("src.BankAccount.datetime") def test_withdraw_on_business_days_(self, mock_datetime): mock_datetime.now.return_value.hour = 9 mock_datetime.now.return_value.weekday.return_value = 0 new_balance = self.account.withdraw(100) self.assertEqual(new_balance, 900) @patch("src.BankAccount.datetime") def test_withdraw_off_business_days_fails(self, mock_datetime): mock_datetime.now.return_value.hour = 9 mock_datetime.now.return_value.weekday.return_value = 6 with self.assertRaises(OutOfScheduleError): self.account.withdraw(1000)
¡Mi solución, al Reto de la clase!
import unittest, os from unittest.mock import patch from src.exceptions import InsufficientFundsError, WithdrawalTimeRestrictionError from src.bank_account import BankAccount from sys import path path.append(".") from src.bank_account import BankAccount class BankAccountTests(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): with open(filename, 'r') as f: return len(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 with open(self.account.log_file, 'r') as f: log_content = f.read() assert "No balance available" in log_content def test_deposit_various_amounts(self): for amount, expected in [(100, 1100), (0, None), (-50, None)]: with self.subTest(amount=amount): if amount <= 0: with self.assertRaises(ValueError): self.account.deposit(amount) else: self.assertEqual(self.account.deposit(amount), expected) def test_transfer_with_currency_conversion(self): usd = BankAccount(initial_balance=100, currency='USD') cop = BankAccount(initial_balance=0, currency='COP') usd.transfer(50, cop) self.assertLess(usd.get_balance(), 100) self.assertGreater(cop.get_balance(), 0) def test_transfer_back_and_forth(self): a = BankAccount(initial_balance=500) b = BankAccount(initial_balance=0) a.transfer(200, b) b.transfer(100, a) self.assertEqual(a.get_balance(), 400) self.assertEqual(b.get_balance(), 100) def test_log_sequence(self): self.account.deposit(100) self.account.withdraw(50) with open(self.account.log_file, 'r') as f: lines = f.readlines() self.assertIn("Deposited: 100", lines[1]) self.assertIn("Withdrew: 50", lines[2]) @patch("src.bank_account.datetime") def test_withdraw_during_business_hour(self, mock_datetime): mock_datetime.now.return_value.hour = 8 # 8 AM new_balance = self.account.withdraw(100) self.assertEqual(new_balance, 900) @patch("src.bank_account.datetime") def test_withdraw_disallow_before_business_hour(self, mock_datetime): mock_datetime.now.return_value.hour = 7 # 7 AM with self.assertRaises(WithdrawalTimeRestrictionError): self.account.withdraw(100) @patch("src.bank_account.datetime") def test_withdraw_disallow_after_business_hour(self, mock_datetime): mock_datetime.now.return_value.hour = 18 # 18 PM with self.assertRaises(WithdrawalTimeRestrictionError): self.account.withdraw(100) @patch("src.bank_account.datetime") def test_withdraw_disallowed_on_sunday(self, mock_datetime): mock_datetime.now.return_value.weekday.return_value = 6 # Domingo mock_datetime.now.return_value.hour = 10 # Dentro del horario permitido with self.assertRaises(WithdrawalTimeRestrictionError) as context: self.account.withdraw(100) self.assertIn("weekends", str(context.exception)) @patch("src.bank_account.datetime") def test_withdraw_allowed_on_monday(self, mock_datetime): mock_datetime.now.return_value.weekday.return_value = 0 # Lunes mock_datetime.now.return_value.hour = 10 # Dentro del horario permitido new_balance = self.account.withdraw(100) self.assertEqual(new_balance, 900)
import requests from datetime import datetime from src.exceptions import InsufficientFundsError, WithdrawalTimeRestrictionError class BankAccount: def __init__(self, initial_balance=0, currency='USD', log_file=None): self.balance = initial_balance self.currency = currency self.log_file = log_file self._log_transaction(f"Account created with {self.balance} {self.currency}") def _log_transaction(self, message): if self.log_file: with open(self.log_file, 'a') as f: f.write(f"{message} \n") def deposit(self, amount): if amount > 0: self.balance += amount self._log_transaction(f"Deposited: {amount} {self.currency}. New balance: {self.balance}") else: raise ValueError("Deposit amount must be positive") return self.balance def withdraw(self, amount): now = datetime.now() if now.weekday() >= 5: # 5 = sábado, 6 = domingo raise WithdrawalTimeRestrictionError("Withdrawals are not allowed on weekends") if now.hour < 8 or now.hour > 17: raise WithdrawalTimeRestrictionError("Withdrawals are only allowed between 8 AM and 5 PM") if amount > self.balance: raise InsufficientFundsError( f"Withdrawal of {amount} {self.currency} exceeds available balance of {self.balance} {self.currency}" ) # 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} {self.currency}. New balance: {self.balance}") return self.balance def get_balance(self): self._log_transaction(f"Check balance. Current balance: {self.balance} {self.currency}") 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") # Si las monedas son diferentes, convertir if self.currency != target_account.currency: rate = self.get_exchange_rate(self.currency, target_account.currency) converted_amount = amount * rate self._log_transaction(f"Converted {amount} {self.currency} to {converted_amount:.2f} {target_account.currency}") else: converted_amount = amount self.balance -= amount target_account.deposit(converted_amount) self._log_transaction(f"Transferred: {amount} {self.currency} to {target_account.currency}. New balance: {self.balance}") return self.balance def get_exchange_rate(self, from_currency, to_currency): response = requests.get(f"http://localhost:5000/rate?from={from_currency}&to={to_currency}") if response.status_code == 200: return response.json().get("rate", 1.0) else: raise ConnectionError("Exchange rate API not available")
Samuel Alejandro Hurtado
Luis Martinez
ProfesorQué te pareció usar patch?
Samuel Alejandro Hurtado
excelente ya estoy agregando nuevas pruebas en el proyecto de mi trabajo jeje
Carlos Eduardo Castillo Ibarra
Johan Moises Ariza Mahecha
Aporto la solución que propongo para el reto:
Para la clase bank_account.py
if now.weekday() > 4: raise WithdrawalDayRestrictionError("Withdrawal are only allowed laboral Week ") ```La nueva excepción: ```js class WithdrawalDayRestrictionError(Exception): pass ```Lo nuevo en test\_bank\_account ```js @patch("src.bank_account.datetime") def test_withdraw_disallow_weekends(self, mock_datetime): mock_datetime.now.return_value.hour = 10 # Hay que poner la hora valida mock_datetime.now.return_value.weekday.return_value = 5 # Ahora si poner lo necesario para que falle en los dias correspondientes with self.assertRaises(WithdrawalDayRestrictionError): self.account.withdraw(100)
Laura Bedoya
Juan Camilo Noreña López
Para solucionar el reto, lo pueden hacer implementando esta verificación en la funcion withdraw de la clase BankAccount:
if now.weekday() > 4: ```if now.weekday() > 4: \#Aqui ej del test durante horarios permitidos:  ```python @patch("src.bank_account.datetime") def test_withdraw_during_bussiness_days(self, mock_datetime): mock_datetime.now.return_value = datetime(2024, 11, 11, 10) self.account.withdraw(100) ```@patch("src.bank\_account.datetime") def test\_withdraw\_during\_bussiness\_days(self, mock\_datetime): mock\_datetime.now.return\_value = datetime(2024, 11, 11, 10) self.account.withdraw(100)
Paolo Quintero
Yetzojhanan Varela Sierra
Prueba: @patch("src.bank_account.datetime") def test_withdraw_disallow_on_weekends(self, mock_datetime): mock_now = unittest.mock.Mock() mock_now.hour = 8 mock_now.weekday.return_value = 5 mock_datetime.now.return_value = mock_now with self.assertRaises(WithdrawalTimeRestrictionError): self.account.withdraw(100) código: def withdraw(self, amount): now = datetime.now() if now.hour < 8 or now.hour > 17: raise WithdrawalTimeRestrictionError("Withdrawal are not allowed before 8am or after 5 pm") elif now.weekday() in [5,6]: raise WithdrawalTimeRestrictionError("Withdrawal are not allowed on weekends") if amount > 0: self.balance -= amount self._log_transaction(f"Withdrew {amount}. New balance: {self.balance}") return self.balance
John Neil Sevillano Colina
jesus manuel vergara arias
Mi solucion al reto:
class BankAccountTest(unittest.TestCase): # Con el metodo setUp de unittest podemos preparar el entorno de pruebas, creando datos, objetos o configuraciones que se usaran en cada test. def setUp(self): self.account = BankAccount(balance=1000, log_file='transaction_log.txt') self.account2 = BankAccount(balance=800) def tearDown(self): if os.path.exists(self.account.log_file): os.remove(self.account.log_file) def _count_lines(self, filename): with open(filename, 'r') as f: return len(f.readlines()) def _check_transaction_log_message(self, filename): with open(filename, 'r') as f: result = f.readlines() return result[1] def test_deposit(self): new_balance = self.account.deposit(200) # Unittest nos provee de metodos de assert para poder hacer las pruebas, generando un código más limpio # Con assertEqual verificamos si dos paramteros son iguales self.assertEqual(new_balance, 1200) # Acepta un tercer parametro que es un mensaje en caso de que falle, pero este es opcional def test_withdraw(self): new_balance = self.account.withdraw(500) self.assertEqual(new_balance, 500) def test_get_balance(self): result = self.account.get_balance() self.assertEqual(result, 1000) def test_transfer(self): new_balance = self.account.transfer(400, self.account2) self.assertEqual(new_balance, 600) @unittest.expectedFailure def test_transfer_amount_failure(self): result = self.account.transfer(1200, self.account2) assert result == ValueError def test_log_transaction(self): self.account.withdraw(500) # En este caso estamos verificando que un archivo exista, para esto podemos utilizar assertTrue self.assertTrue(os.path.exists(self.account.log_file)) def test_count_transactions(self): assert self._count_lines(self.account.log_file) == 1 self.account.deposit(900) assert self._count_lines(self.account.log_file) == 2 def test_message_transfer_amount_failure(self): # Con el método assertRaises le decimos a unittest que estamos esperando un error, para que pueda continuar la prueba y así verificar nuestro log, que es el objetivo de esta prueba. with self.assertRaises(ValueError): self.account.transfer(1200, self.account2) self.assertEqual( self._check_transaction_log_message(self.account.log_file), 'There is not enough money for this transaction.\n' ) @patch("src.bank_account.datetime") def test_withdraw_during_bussines_hours(self, mock_datetime): mock_datetime.now.return_value.hour = 10 new_balance = self.account.withdraw(200) self.assertEqual(new_balance, 800) @patch("src.bank_account.datetime") def test_withdraw_disallow_before_bussines_hours(self, mock_datetime): mock_datetime.now.return_value.hour = 7 with self.assertRaises(WithdrawalHourRestrictionError): self.account.withdraw(200) @patch("src.bank_account.datetime") def test_withdraw_disallow_after_bussines_hours(self, mock_datetime): mock_datetime.now.return_value.hour = 18 with self.assertRaises(WithdrawalHourRestrictionError): self.account.withdraw(200) @patch("src.bank_account.datetime") def test_withdraw_during_bussines_days(self, mock_datetime): mock_datetime.now.return_value.hour = 10 mock_datetime.now.return_value.strftime.return_value = 'Monday' new_balance = self.account.withdraw(400) self.assertEqual(new_balance, 600) @patch("src.bank_account.datetime") def test_withdraw_disallow_during_saturday(self, mock_datetime): mock_datetime.now.return_value.hour = 10 mock_datetime.now.return_value.strftime.return_value = 'Saturday' with self.assertRaises(WithdrawalDayRestrictionError): self.account.withdraw(400) @patch("src.bank_account.datetime") def test_withdraw_disallow_during_sunday(self, mock_datetime): mock_datetime.now.return_value.hour = 10 mock_datetime.now.return_value.strftime.return_value = 'Sunday' with self.assertRaises(WithdrawalDayRestrictionError): self.account.withdraw(400)
Esta fue mi solución obvi con ayudagpt, ánimo si eres principiante hay formas de lograrlo:
1. en bank_account agregé las líneas de condición if para el retiro en día de la semana (weekday):
def withdraw(self, amount): now = datetime.now() if now.hour < 8 or now.hour > 17: raise WithdrawalTimeRestrictionError("Los retiros solo están permitidos de 8am a 5pm") if now.weekday() in [5, 6]: # 5 = Sábado, 6 = Domingo raise WithdrawalTimeRestrictionError("Los retiros solo están permitidos de lunes a viernes") if amount > self.balance: self._log_transaction(f"Fondos insuficientes {self.balance} para retirar el monto {amount}") raise InsufficientFundsError(f"Retiro de {amount} excede el saldo {self.balance}") if amount > 0: self.balance -= amount self._log_transaction(f"Retiro de {amount}. Nuevo balance: {self.balance}") return self.balance ``` 2. En test\_bank\_account agregué las dos pruebas para entre semana y para fin de semana. Lo más desafiante fue entender como tenía que mockear el weekday, pues no se podía hacer de la misma manera que la hora. ```js @patch("src.bank_account.datetime") def test_withdraw_allow_on_weekday(self, mock_datetime): mock_datetime.now.return_value = datetime(2025, 2, 28, 10, 0) new_balance = self.account.withdraw(100) self.assertEqual(new_balance, 900) @patch("src.bank_account.datetime") def test_withdraw_disallow_on_weekend(self, mock_datetime): mock_datetime.now.return_value = datetime(2025, 2, 22, 10, 0) with self.assertRaises(WithdrawalTimeRestrictionError): self.account.withdraw(100)
Mi solución al reto cree otra excepción para los días
class DayRestrictionError(Exception): pass ```Aunque creo que con la que teníamos se pudo haber hecho luego de haberlo terminado. Luego en la clase de BankAccount añadí un una validación para los días de la semana con now.weekday (que nos regresa los días de las semana en una lista de 0-6) ```python from datetime import datetime from src.exceptions import InsufficientFundError, WithdrawralTimeRestrictionError, DayRestrictionError class BankAccount: def __init__(self, balance = 0, log_file = None): self.balance = balance self.log_file = log_file self._log_transaction("Cuenta creada") def _log_transaction(self,message): if self.log_file: with open(self.log_file, "a") as f: f.write(f"{message}\n") def deposit(self, amount): if amount > 0: self.balance += amount self._log_transaction(f"Cantidad depositada {amount}. Nuevo balance: {self.balance}") return self.balance def withdraw(self, amount): now = datetime.now() if now.hour < 8 or now.hour > 17: raise WithdrawralTimeRestrictionError("Withdrawal are only allowed from 8am to 5pm") now = datetime.now() if now.weekday() >= 5: raise DayRestrictionError("Withdrawals are not allowed on weekends") if amount > self.balance: raise InsufficientFundError( f"Withdrawal of {amount} exceeds balance {self.balance}" ) if amount > 0 and self.balance >= amount: self.balance -= amount self._log_transaction(f"Retiro {amount}. Nuevo balance: {self.balance}") return self.balance def transfer(self, amount, target_account): if amount > 0 and self.balance >= amount: self.balance -= amount target_account.deposit(amount) else: self._log_transaction(f"Transferencia fallida por monto {amount}. Saldo disponible {self.balance}") raise ValueError("No tienes saldo suficiente para transferir") return self.balance def get_balance(self): self._log_transaction(f"Revision de balance. Balance actual {self.balance}") return self.balance ```Luego en las pruebas tuvimos que ser exactos en los días para comprobar si era día de semana o fin de semana lo que nos hizo también tener que hacerle mock a la validación withdraw  ```js import unittest, os from unittest.mock import patch from datetime import datetime from src.exceptions import InsufficientFundError, WithdrawralTimeRestrictionError, DayRestrictionError from src.bank_account import BankAccount class BankAccountTest(unittest.TestCase): def setUp(self) -> None: self.account = BankAccount(balance = 1000, log_file = "transaction_log.txt") self.target_account = BankAccount(balance=500) def tearDown(self) -> None: if os.path.exists(self.account.log_file): os.remove(self.account.log_file) def _count_lines(self, filename): with open(filename, "r") as f: return len(f.readlines()) def test_deposit(self): new_balance = self.account.deposit(500) self.assertEqual(new_balance, 1500, "El balance no es igual") @patch("src.bank_account.datetime") def test_withdraw(self, mock_datetime): mock_datetime.now.return_value = datetime(2023, 10, 23, 10) new_balance = self.account.withdraw(500) self.assertEqual(new_balance, 500, "El balance no es igual") def test_transfer(self): self.account.transfer(200, self.target_account) self.assertEqual(self.account.get_balance(), 800) self.assertEqual(self.target_account.get_balance(), 700) def test_get_balance(self): self.assertEqual(self.account.get_balance(), 1000) 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 self.account.deposit(500) assert self._count_lines(self.account.log_file) == 2 @patch("src.bank_account.datetime") def test_withdraw_during_bussines_hours(self,mock_datetime): mock_datetime.now.return_value = datetime(2023, 10, 23, 10) new_balance = self.account.withdraw(100) self.assertEqual(new_balance, 900) @patch("src.bank_account.datetime") def test_withdraw_disallow_before_business_hours(self,mock_datetime): mock_datetime.now.return_value = datetime(2023, 10, 23, 7) with self.assertRaises(WithdrawralTimeRestrictionError): self.account.withdraw(100) @patch("src.bank_account.datetime") def test_withdraw_disallow_after_business_hours(self,mock_datetime): mock_datetime.now.return_value = datetime(2023, 10, 23, 20) with self.assertRaises(WithdrawralTimeRestrictionError): self.account.withdraw(100) @patch("src.bank_account.datetime") def test_withdraw_on_saturday(self, mock_datetime): mock_datetime.now.return_value = datetime(2023, 10, 28, 10) with self.assertRaises(DayRestrictionError): self.account.withdraw(100) @patch("src.bank_account.datetime") def test_withdraw_on_sunday(self, mock_datetime): mock_datetime.now.return_value = datetime(2023, 10, 29, 10) with self.assertRaises(DayRestrictionError): self.account.withdraw(100) @patch("src.bank_account.datetime") def test_withdraw_on_weekday(self, mock_datetime): mock_datetime.now.return_value = datetime(2023, 10, 23, 10) self.account.withdraw(100) self.assertEqual(self.account.get_balance(), 900)
Hola a todos , se modifico el método
withdraw, para incluir la restricción del día
def withdraw(self, amount): current_dateTime = datetime.now() week_day = current_dateTime.weekday() if week_day == 5 or week_day == 6 : raise WithdrawalDayRestrictionError("Withdrawals not allowed Saturday and Sunday") if current_dateTime.hour < 8 or current_dateTime.hour > 17: raise WithdrawalTimeRestrictionError("Withdrawals are only allowed from 8am to 5pm") if amount > 0: self.balance -= amount self._log_transaction(f"Withdrew {amount}. New balance: {self.balance}") return self.balance
y se incluyeron las siguientes pruebas unitarias
@patch("src.bank_account.datetime") def test_withdraw_day_allowed(self,mock_datetime): # 8 am monday mock_datetime.now.return_value.hour = 8 mock_datetime.now.return_value.weekday.return_value = 0 new_balance = self.account.withdraw(100) self.assertEqual(new_balance,900) @patch("src.bank_account.datetime") def test_withdraw_disallowed_in_saturday(self,mock_datetime): # 8 am saturday mock_datetime.now.return_value.hour = 8 mock_datetime.now.return_value.weekday.return_value = 5 with self.assertRaises(WithdrawalDayRestrictionError): self.account.withdraw(100) @patch("src.bank_account.datetime") def test_withdraw_disallowed_in_sunday(self,mock_datetime): # 8 am sunday mock_datetime.now.return_value.hour = 8 mock_datetime.now.return_value.weekday.return_value = 6 with self.assertRaises(WithdrawalDayRestrictionError): self.account.withdraw(100) ```  
El reto:
Cree una excepción para el día que llame WithdrawalDayRestrictionError, modifique withdraw para obtener el dia de la semana por medio del now.weekday, luego hice un array days_not_allows = [5,6] y con un in levante el primer error. El problema fue la anidación, testear el dia sábado y domingo fue lo facil. El resto de dias daban error por la anidación, ya que una vez pasaba esta condición = if today in days_not_allows, luego se encontraba con el if now.hour < 8 or now.hour > 17 y ahi el test no tenia nada que resolver. La solución fue simple, pero no se si adecuada.
def withdraw(self, amount): now = datetime.now() today = now.weekday() days_not_allows = [5,6] if today in days_not_allows: raise WithdrawalDayRestrictionError("Saturday and Sunday withdraw is not allow it") else: if now.hour < 8 or now.hour > 17: raise WithdrawalTimeRestrictionError("Time not allo it") else: if amount > 0: self.balance -= amount self._log_transaction(f'Withdraw {amount}. New balance: {self.balance}') return self.balance @patch("src.bank_account.datetime") def test_Withdrawal_DayRestriction(self, mock_datetime): mock_datetime.now.return_value.weekday.return_value = 0 mock_datetime.now.return_value.hour = 9 new_balance = self.account.withdraw(100) self.assertEqual(new_balance, 900) @patch("src.bank_account.datetime") def test_Withdrawal_DayRestriction_raises(self, mock_datetime): mock_datetime.now.return_value.weekday.return_value = 6 with self.assertRaises(WithdrawalDayRestrictionError): self.account.withdraw(100) ```def withdraw(self, amount): now = datetime.now() today = now.weekday() days\_not\_allows = \[5,6] if today in days\_not\_allows: raise WithdrawalDayRestrictionError("Saturday and Sunday withdraw is not allow it") else: if now.hour < 8 or now.hour > 17: raise WithdrawalTimeRestrictionError("Time not allo it") else: if amount > 0: self.balance -= amount self.\_log\_transaction(f'Withdraw {amount}. New balance: {self.balance}') return self.balance   @patch("src.bank\_account.datetime") def test\_Withdrawal\_DayRestriction(self, mock\_datetime): mock\_datetime.now.return\_value.weekday.return\_value = 0 mock\_datetime.now.return\_value.hour = 9 new\_balance = self.account.withdraw(100) self.assertEqual(new\_balance, 900)   @patch("src.bank\_account.datetime") def test\_Withdrawal\_DayRestriction\_raises(self, mock\_datetime): mock\_datetime.now.return\_value.weekday.return\_value = 6 with self.assertRaises(WithdrawalDayRestrictionError): self.account.withdraw(100)