Testing exceptions in detail
pytest.raises(ValueError) passes for any ValueError, including one raised by a bug on a different line. Two more tools make a test say which error it means.
import pytest
class OutOfStock(Exception):
def __init__(self, item, short):
self.short = short
super().__init__(f"{item} short")
def take(stock, item, n):
if stock[item] < n:
short = n - stock[item]
raise OutOfStock(item, short)
stock[item] -= n
stock = {"tea": 2}
with pytest.raises(OutOfStock,
match="tea") as info:
take(stock, "tea", 5)
print(info.value.short, stock)matchis a regular expression, searched for in the message. Wrap literal text inre.escapeif it has special characters.as infokeeps the exception. After the block,info.valueis the exception object, with its attributes.
What did not happen
A refused operation should usually leave things as they were, so check the state after the block too: here stock must still hold 2. Keep only the raising call inside the with, because nothing after it in the block runs.
Your task
withdraw and InsufficientFunds are written for you. Write test_rejects_zero_amount, using match to check it is the ValueError about a positive amount, and test_insufficient_funds, which withdraws 50 from {"ann": 30} and checks that the exception's needed is 20 and the balance is still 30.
The explanation opens here once every check passes.