This article discusses enhancing the BankAccount class with exception handling for withdrawals to prevent overdrafts and improve error management.
In this article, we enhance our BankAccount class by introducing exception handling for withdrawal operations. This update ensures that users cannot withdraw more money than is available in their account. The guide covers the original implementation, the issue with insufficient balance checks, testing adjustments, and the introduction of a custom exception for better error handling.
PASSEDtests/test_calculations.py::test_bank_transaction[50-10-40] creating empty bank accountPASSEDtests/test_calculations.py::test_bank_transaction[1200-200-1000] creating empty bank account================================== 14 passed in 0.09s ==================================
Upon inspection, the withdraw method does not verify if the account has sufficient funds. For example, if the account has 100andawithdrawalof500 is attempted, the operation should be blocked. To address this, we add a check in the withdraw method that raises an exception when necessary.
Re-running the tests produces the following output:
Copy
Ask AI
PASSEDtests/test_calculations.py::test_bank_transaction[50-10-40] creating empty bank accountPASSEDtests/test_calculations.py::test_bank_transaction[1200-200-1000] creating empty bank accountPASSED================= 14 passed in 0.09s =================
The test output indicated a failure for the case where 10 is deposited and 200 is withdrawn:
Copy
Ask AI
self = <app.calculations.BankAccount object at 0x0000018C971BC940>, amount = 50def withdraw(self, amount): if amount > self.balance: raise Exception("Insufficient funds in account")E Exception: Insufficient funds in accountapp/calculations.py:27: Exception================================= short test summary info =================================FAILED tests/test_calculations.py::test_bank_transaction[10-50-40] - Exception: Insufficient funds in account
Since an exception is expected for insufficient funds, it’s better to remove this failing test case from the parameterized group and create a dedicated test.
We use Pytest’s raises context manager for this purpose. For an account with an initial balance of zero (using the zero_bank_account fixture), attempting to withdraw money should trigger an exception:
Here, the bank_account fixture initializes an account with a balance (for instance, 50) to ensure that withdrawing $200 raises the expected exception. Running the tests now produces:
Copy
Ask AI
tests/test_calculations.py::test_bank_transaction[50-10-40] creating empty bank accounttests/test_calculations.py::test_bank_transaction[1200-200-1000] creating empty bank accounttests/test_calculations.py::test_insufficient_funds PASSED15 passed in 0.09s
For better clarity and maintainability in production code, we define a custom exception called InsufficientFunds that inherits from Python’s built-in Exception class. This approach allows our tests to verify that the correct exception is raised.
We also update the insufficient funds test to assert that an InsufficientFunds exception is raised:
Copy
Ask AI
def test_insufficient_funds(bank_account): with pytest.raises(InsufficientFunds): bank_account.withdraw(200)
Running the tests now confirms that the proper exception is raised:
Copy
Ask AI
tests/test_calculations.py::test_bank_transaction[200-100-100] creating empty bank account PASSEDtests/test_calculations.py::test_bank_transaction[50-10-40] creating empty bank account PASSEDtests/test_calculations.py::test_bank_transaction[1200-200-1000] creating empty bank account PASSEDtests/test_calculations.py::test_insufficient_funds PASSED15 passed in 0.08s
Specifying the exact exception type ensures that our code not only raises an error but also raises the correct, expected type. This improves test precision and code reliability.
Demonstrating the Importance of the Correct Exception Type
To highlight the importance of using a custom exception, consider a scenario where ZeroDivisionError is raised instead of InsufficientFunds. For example, changing the withdraw method as shown below would trigger a test failure:
Copy
Ask AI
def withdraw(self, amount): if amount > self.balance: # raise InsufficientFunds("Insufficient funds in account") raise ZeroDivisionError() self.balance -= amount
This failure confirms that our tests correctly check for a specific exception, and any deviation from the expected behavior will be flagged immediately.
By defining and raising a custom exception for insufficient funds, our BankAccount class now operates in a robust and predictable manner. The updated tests validate this behavior precisely, ensuring that the internal logic of our application remains stable and error-free.Happy testing!