-
Notifications
You must be signed in to change notification settings - Fork 113
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
assert_that does not issue warning when actual is not a bool #147
Comments
Any truthy or falsy value will do here - we don't need an actual boolean. |
We had several developers write assert_that(A, B), thinking it did an equality check, when in fact it always passes. Also, the existing warning message and documentation make it clear it is supposed to be a bool specifically. |
It wouldn't be very pythonic to require a boolean type, and it would break a lot of existing tests. If we were going to do anything so drastic, I'd be more inclined to deprecate and eventually remove the non-boolean signature of |
How is issuing a warning a breaking change? |
OK, maybe not break as such, but there will be loads of warnings issued for (currently) perfectly valid tests. |
Do you know that such occurrences are actually correct, and not people misunderstanding what assert_that does, and thus their tests have been silently broken the entire time? It would seem to me that a testing library ought to err on the side of caution in such matters, since false positives (i.e., tests that should fail but pass) are extremely problematic. |
Some of these tests may be broken, but most of them will not be. We should only be issuing warnings where things need fixing, not where they might need fixing. People need to be able to silence all warnings by fixing their code. As I say, I think the entire non-matcher version of the function is broken, and should be deprecated then removed. @offbyone - thoughts? |
I actually don't agree that the non-matcher version is intrinsically broken. For example, imagine I have a function However, I cannot think of a case in which one would want to know that a non-bool was truthy but not check its actual value, which is the only case negatively impacted by this warning. At worst, users could either cast to bool themselves, or use some sort of |
Every Python unit test framework I've used before that has provided comparison functions (nose and unittest) has used the pattern I would be stunned if there was a single instance where someone wrote a statement like As written, |
It's also not pythonic to do def assert_that(actual, matcher=None, reason=""):
if isinstance(matcher, Matcher):
_assert_match(actual=actual, matcher=matcher, reason=reason)
else:
if isinstance(actual, Matcher):
warnings.warn("arg1 should be boolean, but was {}".format(type(actual)))
_assert_bool(assertion=cast(bool, actual), reason=cast(str, matcher)) is less pythonic (and less safe) than def assert_that(actual, matcher=None, reason=""):
if matcher is not None:
_assert_match(actual=actual, matcher=matcher, reason=reason)
else:
if isinstance(actual, Matcher):
warnings.warn("arg1 should be boolean, but was {}".format(type(actual)))
_assert_bool(assertion=cast(bool, actual), reason=cast(str, matcher)) |
Ah, you let I think this function could be made substantially safer without adding a ton of complexity. I'll post a possible implementation when I have more time to think about it |
Oh, I couldn't agree more. The whole overloading, one function with multiple signatures thing is left over from when PyHamcrest was basically a transliteration of the Java version. It's nasty, but to get rid of it would break many people's tests, so I suppose we are stuck with it. I never make use of the non-matcher version myself. If I'm not using a matcher, I don't see the advantage over an |
If only |
Well you can, kind of: def assert_that(arg, matcher=None, reason=None):
return _assert_that(matcher, arg, reason)
@singledispatch
def _assert_that(matcher, arg, reason):
# do stuff But that's cool -- I didn't know about |
I don't see how I agree that the second form of |
It lets you break up the logic and write cleaner functions for the individual situations. In my opinion, there are 5 different scenarios that should be handled independently:
The first is unambiguous. The second is pretty clear to me, too, where
More details on the fourth scenario - I would not expect many people to really understand how binary logic operators work in Python: >>> 0 or []
[]
>>> 0 and []
0
>>> 0 or object()
<object object at 0x7f02c3874dd0>
>>> 0 and object()
0
def some_list_operation(arg=None):
arg = arg or [] You don't want to assign mutable arguments as defaults because of early binding in Python (hence why you should never see So basically, if the first argument is not a string (say it's a list), it may be that way because someone wrote something like: assert_that(list1 or list2, 'At least one of the lists should have something inside!') So if you do a type-check on the first argument and warn/throw if the type is not boolean, this test will warn/throw because the first argument is actually a |
I assume you mean "matcher is neither a matcher nor a string". However, there is another case to be considered - matcher is |
Yea, I'd thought about that. I did just now come up with a workaround: _singleton = object()
def assert_that(arg1, matcher=_singleton, reason=''):
if matcher is None:
warnings.warn('The matcher argument should be a Matcher instance, like is(None)')
...
if matcher is _singleton: # matcher wasn't specified
... The reason I didn't include that in my list of cases to handle was because I had thought it was going to require pretty complex processing of all |
PyHamcrest/src/hamcrest/core/assert_that.py
Lines 60 to 62 in 405b753
It would stand to reason that that should be
if not isinstance(actual, bool)
.The text was updated successfully, but these errors were encountered: