-
-
Notifications
You must be signed in to change notification settings - Fork 2.7k
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
Detailed assert failure introspection for attrs and dataclasses objects #3776
Detailed assert failure introspection for attrs and dataclasses objects #3776
Conversation
testing/test_assertion.py
Outdated
|
||
@dataclass | ||
class SimpleDataObject(object): | ||
field_a = field() |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I initially used type annotations here, but tests failed for 2.7 so decided to use field
as we only care about output, not the type.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Looks like 3.7 requires type annotation. Thoughts on a good way of getting both in there?
I can edit the class dict's __annotations__
item
src/_pytest/assertion/util.py
Outdated
@@ -120,6 +120,12 @@ def isdict(x): | |||
def isset(x): | |||
return isinstance(x, (set, frozenset)) | |||
|
|||
def isdatacls(obj): | |||
return hasattr(obj, "__dataclass_fields__") |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
please use getattr(x, ..., None) is not None
or something like that instead, hasattr
hides errors
c8f5c44
to
5252680
Compare
Codecov Report
@@ Coverage Diff @@
## features #3776 +/- ##
============================================
+ Coverage 95.84% 95.88% +0.04%
============================================
Files 111 111
Lines 24928 25126 +198
Branches 2438 2462 +24
============================================
+ Hits 23892 24092 +200
+ Misses 737 730 -7
- Partials 299 304 +5
Continue to review full report at Codecov.
|
@RonnyPfannschmidt @nicoddemus I got this feature working and (mostly) tested, but am not sure how to test Python 3.7 code. I used the This is what I have so far: @pytest.mark.skipif(sys.version_info < (3, 7), reason="Dataclasses in Python3.7+")
def test_dataclasses(self, testdir):
testdir.makepyfile(
"""
pytest_plugins = ["_pytest.assertion"]
from dataclasses import dataclass, field
def mock_config():
class Config(object):
verbose = False
def getoption(self, name):
if name == "verbose":
return self.verbose
raise KeyError("Not mocked out: %s" % name)
return Config()
def callequal(left, right, verbose=False):
config = mock_config()
config.verbose = verbose
return plugin.pytest_assertrepr_compare(config, "==", left, right)
@dataclass
class SimpleDataObject(object):
field_a: int = field()
field_b: str = field()
left = SimpleDataObject(1, "b")
right = SimpleDataObject(1, "c")
lines = callequal(left, right)
assert lines[1].startswith("Omitting 1 identical item")
assert "Common attributes" not in lines
for line in lines[1:]:
assert "field_a" not in line
"""
)
result = testdir.runpytest()
result.assert_outcomes(passed=4) Am I structuring the tests correctly? Not sure if I'm thinking about this the right way. Would appreciate if you could point me in the right direction. Thank you so much for your help! |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
overall good work, the core of the implementation and the testing done look solid to me,
when the extraction part is re-factored this one is merge-able, even if we need to get back at the diff style later on
src/_pytest/assertion/util.py
Outdated
@@ -319,6 +329,41 @@ def _compare_eq_dict(left, right, verbose=False): | |||
return explanation | |||
|
|||
|
|||
def _compare_eq_class(left, right, verbose, type=None): | |||
if type == "data": |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
lets please change the magical strings to named functions that do the extraction and always require passing them
class_name = left.__class__.__name__ | ||
explanation += [("Differing attributes:")] | ||
for k in diff: | ||
explanation += [ |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
i disagree with the chosen display style but cant provide any meaningful alternative yet - i would like to see some kind of diff style display using multiple lines
@alysivji gentle ping - i'd like this to get into a mergable state - aka named functions done, and created a follow-up issue for the display of the diffs would you like to do that, or can we pick it up? |
Thanks for the ping. I will be sprinting at PyCon Canada on Monday and Tuesday next week and hope to get it done then. I got the requested changes completed am not sure how to test Python3.7 code. I posted a question earlier but looking at it now, it's not too clear in what I am asking. I'll take some time to refamiliarize myself with the code to clarify the question I have before this weekend. Hope to hit the ground first thing Monday morning. |
@alysivji the example you posted looks good to go/use wrt python3.7 - we might want to consider using the copy_example feature of pytester later on to have the actual file reside in the examples dir instead of a multiline sting in python also thanks for the update and have a great time at the sprint |
5252680
to
2bffd68
Compare
@RonnyPfannschmidt Got most of the changes done. Wanted to throw it into CI and get eyes on it before I get back to polishing it tomorrow. Will include output + other info in a message tmrw. |
Been waiting a few hours for the AppVeyor builds to start, but sprints are winding down and I think I'm gonna have the same error pop up in the CI. Might as well ask the question. My commit from yesterday failed CI: I also changed the formatting of the diffs. Looks a lot more readable to me. def test_equal_data_classes():
example1 = SimpleDataObject(1, 'b')
example2 = SimpleDataObject(1, 'b')
assert example1 == example2
example3 = SimpleDataObject(1, 'xc')
> assert example1 == example3
E AssertionError: assert SimpleDataObj..., field_b='b') == SimpleDataObje... field_b='xc')
E Omitting 1 identical items, use -vv to show
E Differing attributes:
E field_b: 'b' != 'xc'
def test_equal_attrs():
example1 = SimpleDataObjectAttrs(1, 'b')
example2 = SimpleDataObjectAttrs(1, 'b')
assert example1 == example2
example3 = SimpleDataObjectAttrs(2, 'xc')
> assert example1 == example3
E AssertionError: assert SimpleDataObj..., field_b='b') == SimpleDataObje... field_b='xc')
E Differing attributes:
E field_a: 1 != 2
E field_b: 'b' != 'xc' with def test_equal_data_classes():
example1 = SimpleDataObject(1, 'b')
example2 = SimpleDataObject(1, 'b')
assert example1 == example2
example3 = SimpleDataObject(1, 'xc')
> assert example1 == example3
E AssertionError: assert SimpleDataObj..., field_b='b') == SimpleDataObje... field_b='xc')
E Matching attributes:
E ['field_a']
E Differing attributes:
E field_b: 'b' != 'xc'
def test_equal_attrs():
example1 = SimpleDataObjectAttrs(1, 'b')
example2 = SimpleDataObjectAttrs(1, 'b')
assert example1 == example2
example3 = SimpleDataObjectAttrs(2, 'xc')
> assert example1 == example3
E AssertionError: assert SimpleDataObj..., field_b='b') == SimpleDataObje... field_b='xc')
E Differing attributes:
E field_a: 1 != 2
E field_b: 'b' != 'xc' |
@RonnyPfannschmidt I need a hand getting this into a mergeable state All the changes have been made, but it looks like How can I fix this? |
We some examples now use type annotations
@alysivji thanks for following up with this. I've committed a change so |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
LGTM, great work!
@RonnyPfannschmidt anything else you would like to add here or can we merge this? |
Closes #3632
Adds richer equality comparison on
AssertionError
for objects creating using either attrs or dataclasses.Thanks for submitting a PR, your contribution is really appreciated!
Here's a quick checklist that should be present in PRs (you can delete this text from the final description, this is just a guideline):
changelog
folder, with a name like<ISSUE NUMBER>.<TYPE>.rst
. See changelog/README.rst for details.features
branch for new features and removals/deprecations.Unless your change is trivial or a small documentation fix (e.g., a typo or reword of a small section) please:
AUTHORS
in alphabetical order;