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

unittest: make obj work more like Function/Class #12089

Merged
merged 1 commit into from
Mar 8, 2024

Conversation

bluetech
Copy link
Member

@bluetech bluetech commented Mar 8, 2024

Previously, the obj of a TestCaseFunction (the unittest plugin item type) was the unbound method. This is unlike regular Class where the obj is a bound method to a fresh instance.

This difference necessitated several special cases in in places outside of the unittest plugin, such as FixtureDef and FixtureRequest, and made things a bit harder to understand.

Instead, match how the python plugin does it, including collecting fixtures from a fresh instance.

The downside is that now this instance for fixture-collection is kept around in memory, but it's the same as Class so nothing new. Users should only initialize stuff in setUp/setUpClass and similar methods, and not in __init__ which is generally off-limits in TestCase subclasses.

I am not sure why there was a difference in the first place, though I will say the previous unittest approach is probably the preferable one, but first let's get consistency.

@bluetech
Copy link
Member Author

bluetech commented Mar 8, 2024

Did this while looking into #12065.

Tested on some of my own (quite large) unittest+pytest projects, and also pytest-django which uses unittest internally (this required adding a backward compat property, but I will update pytest-django if this merged and we could remove the compat).

Copy link
Member

@nicoddemus nicoddemus left a comment

Choose a reason for hiding this comment

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

Looks great, thanks!

Left minor comments.

Comment on lines 68 to 69
# runTest is a special no-op name for unittest, can be used when a dummy
# instance is needed.
Copy link
Member

Choose a reason for hiding this comment

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

I don't quite get this comment... "name for unittest" you mean pytest's unittest, or stdlib unittest? Also is this recommending calling runTest to obtain a new dummy instance?

Copy link
Member Author

Choose a reason for hiding this comment

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

I will update the comment to make it clearer. Also we don't really need the parameter so I'll omit it.

@@ -63,6 +62,13 @@ class UnitTestCase(Class):
# to declare that our children do not support funcargs.
nofuncargs = True

def newinstance(self, method_name: str = "runTest"):
Copy link
Member

Choose a reason for hiding this comment

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

I understand we have been using _ to separate words in new methods/functions? Anyway this is murky waters anyway, perhaps you wanted to keep consistency within this class.

Copy link
Member Author

Choose a reason for hiding this comment

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

This overrides Class.newinstance so needs the same name

for obj in gc.get_objects():
assert type(obj).__name__ != "TestCaseObjectsShouldBeCleanedUp"
if type(obj).__name__ == "TestCaseObjectsShouldBeCleanedUp":
assert not hasattr(obj, "an_expensive_obj")
Copy link
Member

Choose a reason for hiding this comment

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

Where does this an_expensive_obj comes from? Perhaps leaving a comment explaining it.

Copy link
Member Author

Choose a reason for hiding this comment

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

I think it's maybe clearer when reading the entire test?

Previously, the `obj` of a `TestCaseFunction` (the unittest plugin item
type) was the unbound method. This is unlike regular `Class` where the
`obj` is a bound method to a fresh instance.

This difference necessitated several special cases in in places outside
of the unittest plugin, such as `FixtureDef` and `FixtureRequest`, and
made things a bit harder to understand.

Instead, match how the python plugin does it, including collecting
fixtures from a fresh instance.

The downside is that now this instance for fixture-collection is kept
around in memory, but it's the same as `Class` so nothing new. Users
should only initialize stuff in `setUp`/`setUpClass` and similar
methods, and not in `__init__` which is generally off-limits in
`TestCase` subclasses.

I am not sure why there was a difference in the first place, though I
will say the previous unittest approach is probably the preferable one,
but first let's get consistency.
@purajit
Copy link

purajit commented May 1, 2024

Fully understand this change, but this should really have be listed as a breaking change - upgrading pytest caused me to have to track down why collection was failing, and took me a good amount of debugging to find this.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
None yet
Projects
None yet
Development

Successfully merging this pull request may close these issues.

3 participants