Skip to content
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

gh-122102: Improve tests for descriptor-related tools in inspect #122144

Open
wants to merge 12 commits into
base: main
Choose a base branch
from

Conversation

zuo
Copy link
Contributor

@zuo zuo commented Jul 22, 2024

Improve (and refactor) the tests of ismethoddescriptor() (test.test_inspect.test_inspect.TestIsMethodDescriptor) and isdatadescriptor() (test.test_inspect.test_inspect.TestIsDataDescriptor), making them more comprehensive and consistent.

@@ -0,0 +1,2 @@
Improve the tests for :func:`inspect.ismethoddescriptor` and
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I don't think we need a News entry but I may be wrong. Let's leave it like this for now.

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Should I remove this file?

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

(I don't know whether the thumb up by Serhiy was to tell me that the first sentence was correct or if it was for the second sentence...).

@serhiy-storchaka Should we keep it or remove it? (I'm in favor of removing it because I don't think people are very interested in that and it's only about coverage).

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

OK, I remove it then.

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

@serhiy-storchaka Then probably the skip news label will be needed, won't it?

class MethodDescriptor:
"""with __get__ and no __set__/__delete__"""
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I'd prefer havig those descriptions at the test level rather at the docstring level.

Copy link
Contributor Author

@zuo zuo Jul 23, 2024

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Hm. I find attaching them directly to the classes they describe most direct and readable.

However, if you insist they should be listed in the for-loop's source collection, I'll move them.

Do you? :)

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Well, it's generally easier when you read the code so I'll leave that decision to the one merging your PR (I don't have those rights). For now, you can keep them as is I think.

Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

If they are not used in other tests, and there is no other reason for them to be global, I think that it is easy to read if all related classes are defined in close proximity to the testing code.

Copy link
Contributor Author

@zuo zuo Jul 25, 2024

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

So, should those classes and/or their short descriptions (currently being their docstrings) stay as-is, or be moved/reorganized somehow?

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I personally don't like the descriptions at the docstring level because you need to go back and forth with the class whenever you see the actual test. Alternatively, you can just rename the class to include whatever is being done, namely:

  • MethodDescriptor -> MethodDescriptorWith_Get_NoSet_NoDelete.
  • MethodDescriptorWithGetNone -> MethodDescriptor_GetIsNone_NoSet_NoDelete.

Or, as I said before, do something like:

class MethodDescriptor:
	def __get__(self, *_): pass

for cls, desc in [
	(MethodDescriptor, "__get__ and no __set__/__delete__"),
    ...
]:
	with self.subTest(f'ismethoddescriptor: {desc}', cls=cls):
		...

I see that you also use some classes twice but the tests have a different check (one is on cls() and the other is on cls. Since you don't include the docstring in the second test, you don't need to repeat "__get__ and no __set__/__delete__" the second time as well.

I think it's clearer that way, because when you read the test, you likely forget about the docstrings after you've read them, whereas when you read the list of classes being tested, it's easy to see which description correponds to which class (in addition, with a meaningful name, it's easier to remember when you use them twice).

One thing that I would say: try to keep the diff small (so if you can reduce the number of changes, it's better) and try to be consistent with what has been done (the tests use explicit assertion messages, and not docstrings, so I think there was a reason to).

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I have not hard feelings about this matter... @serhiy-storchaka What do you think?

Lib/test/test_inspect/test_inspect.py Outdated Show resolved Hide resolved
Lib/test/test_inspect/test_inspect.py Outdated Show resolved Hide resolved
(use `self.subTest()` in loops)
@zuo zuo requested a review from picnixz July 23, 2024 15:01
(further minor tweaks/improvements...)
@zuo zuo requested a review from picnixz July 23, 2024 19:14
Copy link
Contributor

@picnixz picnixz left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Except the docstrings on the classes, I think it's fine.

@serhiy-storchaka Do you want to take a look?

Lib/test/test_inspect/test_inspect.py Outdated Show resolved Hide resolved
Comment on lines 1929 to 1930
attribute_slot = Slotermeyer.__dict__['a_slot']
frame_locals = types.FrameType.f_locals
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I still think those should be closer to the test they are used in.

Copy link
Contributor Author

@zuo zuo Jul 25, 2024

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

That is the trade-off: to make, within this test method, all definitions/assignments of the descriptors used in the test be grouped in one place (and then only use them in consecutive assertions), or to place each of those definitions as close to the assertions they are used in as possible.

I prefer the former variant, but if you insist on the latter one, I'll move the definitions. One problem I see is that it is hard to do that consistently without splitting some of the definitions (in particular, moving a_property from Owner to a separate class...).; and if I do split them and do all necessary movements, then things may become unnecessarily "fragmented" (and, therefore, harder to read/comprehend).

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I don't understand. attribute_slot is a variable, so you can move that variable where it's being used (line 1976), same for frame_locals. Don't move existing code, only move newest code.

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

The problem with consistency, as perceived by me, is that if I move attribute_slot and frame_locals, I should also move e.g. a_lambda, and probably also the local def/class statements... But I find it a minor one, so I have no problem with adjusting the code according to your demand.

Copy link
Contributor Author

@zuo zuo Aug 3, 2024

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Hm... I propose, however, to get rid of the entire a_lambda variable – it's redundant, as the code without it does not seem less readable (rather quite contrary, actually...).

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

You are improving the tests. While consistency is important, readability is usually more for the tests. You declare a variable that is only used once and quite far from it. It's better to 1) not declare the variable 2) or declare it and use it just after. In general, we don't touch old code if there is no need to (e.g., there is no issue with it) but we can improve new code. I'm not sure I'll have time to review your changes today and won't be there for the next 10 days.

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

@picnixz For the record, according to your suggestion, the assignments to attribute_slot and frame_locals as well as (for consistency) some other stuff – have been moved, so that now they are as close as possible to the places when they are actually used.

class MethodDescriptor:
"""with __get__ and no __set__/__delete__"""
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

If they are not used in other tests, and there is no other reason for them to be global, I think that it is easy to read if all related classes are defined in close proximity to the testing code.

Comment on lines 1913 to 1914
with self.subTest(f"a descriptor class (not a descriptor "
f"itself) {cls.__doc__} ", cls=cls):
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Try to change assertFalse to assertTrue and look at the errors report. It may be only me, but I think that too long failure messages are difficult to read.

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Indeed, they become too verbose.

Now I've carefully prepared more concise messages. I hope you find them more readable.

MethodDescriptorSub,
]:
with self.subTest(f"a descriptor {cls.__doc__}", cls=cls):
self.assertTrue(inspect.ismethoddescriptor(cls()))
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Method descriptors and data descriptors are mutually exclusive. For every case where ismethoddescriptor() returns True we can test that isdatadescriptor() returns False and vice verse. Of course, there are objects for which both predicates return False.

Copy link
Contributor Author

@zuo zuo Jul 25, 2024

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Generally, both those functions – ismethoddescriptor() and isdatadescriptor() – are tested in similar ways, but separately: each in a dedicated test class (respectively: TestIsMethodDescriptor and TestIsDataDescriptor).

Do you suggest that those test classes should be merged into one class? That seems reasonable from the DRY point of view, and to emphasize the mutual exclusion you pointed to. Yet, on the other hand, I am afraid if that wouldn't result in tests whose essence would be harder to grasp (because of being less focused).

zuo and others added 2 commits July 24, 2024 13:49
(further rearrangements/improvements...)
@zuo
Copy link
Contributor Author

zuo commented Aug 3, 2024

@serhiy-storchaka @picnixz
The latest commit adjusts the stuff to the changes from gh-122087 (namely, from the PR gh-122219 – i.e., the Python-3.14-dedicated part of the gh-122087's changes).

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
awaiting core review tests Tests in the Lib/test dir
Projects
None yet
Development

Successfully merging this pull request may close these issues.

3 participants