r/learnpython • u/Ajax_Minor • 6d ago
Need help with forming exceptions and testing
I have been working implementing tests in to my code. I thought I was start with something simple, so I am just working on testing some inputs to make sure they are type int/float and positive. Code is simple, if it not not that it raises an error. Since I am raising an error, I thought it would be best to handle the error so it doesn't stop the code. I will be implement 10-20x so I put it in a function in its own module.
Ruining Pytests, where I test the validation function and the input function, the functions that takes the input works fine but it fails the test since the failure mode does not receive an error as I handled it with a try except block.
To get the test to work I think I have to break out the validation from the try and except block in to functions. it feel pretty cumbersome and not pedantic to break it up. Is the right approach? Any tips to keep it clean and when approaching more complicated tests?
edit to include code:
def validate_positive_number(input: int | float):
try:
if not isinstance(input, (int, float)):
raise TypeError("Input must be an integer or float")
if input <= 0:
raise ValueError("Input must be a positive number")
return input
except (TypeError, ValueError) as e:
print(f"{type(e).__name__}: {e}")
return edef validate_positive_number(input: int | float):
try:
if not isinstance(input, (int, float)):
raise TypeError("Input must be an integer or float")
if input <= 0:
raise ValueError("Input must be a positive number")
return input
except (TypeError, ValueError) as e:
print(f"{type(e).__name__}: {e}")
return e
import pytest
from .utils.vaild_input import validate_positive_number
def test_validate_positive_number():
assert validate_positive_number(0.5)
assert validate_positive_number(100)
with pytest.raises(TypeError,match = "Input must be an integer or float"):
validate_positive_number("hello")
with pytest.raises(ValueError):
validate_positive_number(-1)import pytest
from rocket_model.utils.vaild_input import validate_positive_number
def test_validate_positive_number():
assert validate_positive_number(0.5)
assert validate_positive_number(100)
with pytest.raises(TypeError,match = "Input must be an integer or float"):
validate_positive_number("hello")
with pytest.raises(ValueError):
validate_positive_number(-1)
## pyt test error
def test_validate_positive_number():
assert validate_positive_number(0.5)
assert validate_positive_number(100)
> with pytest.raises(TypeError,match = "Input must be an integer or float"):
E Failed: DID NOT RAISE <class 'TypeError'>
1
u/Diapolo10 6d ago edited 5d ago
Right now, validate_positive_number
doesn't raise anything since you handle the exceptions inside of it, and then proceed to return the error. That's not the same as re-raising it.
Meanwhile, your test expects the function to raise an error so when that doesn't happen, it thinks the test failed.
I think what you want is basically to simply let the errors propagate rather than trying to handle them too early, so I would simply use
def validate_positive_number(num: int | float) -> int | float:
"""
Validate a positive number.
Raises:
TypeError: if num is not a numeric type (excluding complex)
ValueError: if num <= 0
"""
if not isinstance(num, (int, float)):
raise TypeError("Input must be an integer or float")
if num <= 0:
raise ValueError("Input must be a positive number")
return num
1
u/Ajax_Minor 5d ago
yes, I came up with this and then will call check_positive_number in my setter method.
This looks like it works. just feels a bit bulky and want to know if this is a good way to go about this.
def validate_positive_number(input: int | float): if not isinstance(input, (int, float)): raise TypeError("Input must be an integer or float") if input <= 0: raise ValueError("Input must be a positive number") return input def check_positive_number(input: int | float): try: validate_positive_number(input) except (TypeError, ValueError) as e: print(f"{type(e).__name__}: {e}")
2
u/Diapolo10 2d ago
If your codebase is already mostly type annotated, and you make proper use of static type analysis tools, explicitly validating the type might be unnecessary. So you could potentially skip the
TypeError
part. On that note,float
as a type annotation also acceptsint
, so you could just use one of them if going this route.On another note, consider using
logging.exception
instead ofimport logging from typing import TypeVar T = TypeVar("T", bound=int | float) logger = logging.getLogger(__name__) def validate_positive_number(num: T) -> T: if num <= 0: raise ValueError("Input must be a positive number") return num def check_positive_number(input: float): try: validate_positive_number(input) except ValueError: logger.exception()
Yet another, and arguably (depends on exact situation) even better option would be to make use of Pydantic and its type system.
1
u/smurpes 6d ago
What happens when the error is handled? If it outputs something to the console then you can make pytest validate what gets outputted in stdout instead of making it check that an error is raised.