You don't have access to this class

Keep learning! Join and start boosting your career

Aprovecha el precio especial y haz tu profesi贸n a prueba de IA

Antes: $249

Currency
$209
Suscr铆bete

Termina en:

1 D铆as
10 Hrs
43 Min
37 Seg

Uso de Patching para Modificar Comportamientos en Python

13/20
Resources

In this lesson, we have learned how to modify the behavior of objects and functions within our Python tests, using techniques such as patching to simulate specific situations, such as controlling the withdrawal time in a bank account. This skill is crucial when we need to validate temporal constraints or any other business logic that depends on external factors, such as time.

How can we restrict the withdrawal schedule in a bank account?

To implement the time restriction, we used the datetime class to get the current time. We defined that withdrawals can only be made during office hours: between 8 AM and 5 PM. Any attempt outside these hours will throw a custom exception called WithdrawalError.

  • The logic was implemented in the Withdrawal method of the BankAccount class.
  • The constraint is based on comparing the current time obtained with datetime.now().hour.
  • If the time is less than 8 AM or greater than 5 PM, the exception is thrown.

How can we test the functionality effectively?

Unit tests allow us to simulate different times of the day to validate that the constraints work correctly. To achieve this, we use the patch decorator of the unittest.mock module, which temporarily modifies the behavior of the datetime.now() function.

  • With patch, we can define a specific return value for now(), such as 7 AM or 10 AM.
  • In this way, we can validate that the exception is thrown correctly if the withdrawal occurs outside the allowed time.
  • In case the withdrawal is within the schedule, the test will verify that the account balance is updated correctly.

How did we correct errors in the business logic?

During implementation, we found an error in the schedule logic condition. Initially, an incorrect and operator was used to check if the time was within the allowed range. This error was corrected by changing the condition to an or, ensuring that the logic prohibited withdrawals before 8 AM and after 5 PM.

Contributions 8

Questions 0

Sort by:

Want to see more contributions, questions and answers from the community?

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.
```python 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) ```
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): ```python 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) ```
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)