No tienes acceso a esta clase

隆Contin煤a aprendiendo! 脷nete y comienza a potenciar tu carrera

Uso de Patching para Modificar Comportamientos en Python

13/20
Recursos

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.

  • Se implement贸 la l贸gica en el m茅todo de retiro de la clase BankAccount.
  • La restricci贸n se basa en comparar la hora actual obtenida con datetime.now().hour.
  • Si la hora es menor que las 8 AM o mayor que las 5 PM, se lanza la excepci贸n.

驴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().

  • Con patch, podemos definir un valor de retorno espec铆fico para now(), como las 7 AM o las 10 AM.
  • De esta forma, se puede validar que la excepci贸n se lance correctamente si el retiro ocurre fuera del horario permitido.
  • En caso de que el retiro sea dentro del horario, la prueba verificar谩 que el saldo de la cuenta se actualice correctamente.

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

Aportes 6

Preguntas 0

Ordenar por:

驴Quieres ver m谩s aportes, preguntas y respuestas de la comunidad?

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 ```python 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. ```python 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`. ```python 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: ```python 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: ```python 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.
Para solucionar el reto, lo pueden hacer implementando esta verificaci贸n en la funcion withdraw de la clase BankAccount: ```python 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)
Mi soluci贸n al reto cree otra excepci贸n para los d铆as ```python 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) ```
```js 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 ```
Hola a todos , se modifico el m茅todo withdraw, para incluir la restricci贸n del d铆a ```js 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 ```js @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. ```python 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)