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

[MAINT] Refactor fixtures #3905

Merged
merged 16 commits into from Aug 24, 2023
Merged

[MAINT] Refactor fixtures #3905

merged 16 commits into from Aug 24, 2023

Conversation

Remi-Gau
Copy link
Collaborator

Changes proposed in this pull request:

In nilearn/conftest.py adds:

  • constants for common affines and shapes
  • functions to return those constant
  • fixtures to use those constants
  • functions and fixtures to use 3D or 4D NifTiImages filled with zeros, ones, or random numbers

Apply those changes in the tests that have already been refactored

@github-actions
Copy link
Contributor

github-actions bot commented Aug 11, 2023

👋 @Remi-Gau Thanks for creating a PR!

Until this PR is ready for review, you can include the [WIP] tag in its title, or leave it as a github draft.

Please make sure it is compliant with our contributing guidelines. In particular, be sure it checks the boxes listed below.

  • PR has an interpretable title.
  • PR links to Github issue with mention Closes #XXXX (see our documentation on PR structure)
  • Code is PEP8-compliant (see our documentation on coding style)
  • Changelog or what's new entry in doc/changes/latest.rst (see our documentation on PR structure)

For new features:

  • There is at least one unit test per new function / class (see our documentation on testing)
  • The new feature is demoed in at least one relevant example.

For bug fixes:

  • There is at least one test that would fail under the original bug conditions.

We will review it as quick as possible, feel free to ping us with questions if needed.

@Remi-Gau
Copy link
Collaborator Author

oops... introduced some test pollution
will see if https://github.com/asottile/detect-test-pollution can help

@Remi-Gau Remi-Gau marked this pull request as ready for review August 11, 2023 22:53
@codecov
Copy link

codecov bot commented Aug 12, 2023

Codecov Report

Merging #3905 (e0bcc40) into main (457c79e) will not change coverage.
The diff coverage is n/a.

@@           Coverage Diff           @@
##             main    #3905   +/-   ##
=======================================
  Coverage   91.76%   91.76%           
=======================================
  Files         134      134           
  Lines       15747    15747           
  Branches     3283     3283           
=======================================
  Hits        14451    14451           
  Misses        752      752           
  Partials      544      544           
Flag Coverage Δ
macos-latest_3.10 91.68% <ø> (?)
macos-latest_3.11 91.68% <ø> (?)
macos-latest_3.8 91.64% <ø> (?)
macos-latest_3.9 91.64% <ø> (?)
ubuntu-latest_3.10 91.68% <ø> (ø)
ubuntu-latest_3.11 91.68% <ø> (ø)
ubuntu-latest_3.8 91.64% <ø> (?)
ubuntu-latest_3.9 91.64% <ø> (ø)
windows-latest_3.10 91.62% <ø> (?)
windows-latest_3.11 91.62% <ø> (ø)
windows-latest_3.8 91.58% <ø> (ø)
windows-latest_3.9 91.58% <ø> (?)

Flags with carried forward coverage won't be shown. Click here to find out more.

📣 We’re building smart automated test selection to slash your CI/CD build times. Learn more

@Remi-Gau Remi-Gau mentioned this pull request Aug 12, 2023
1 task
Copy link
Member

Choose a reason for hiding this comment

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

@Remi-Gau why is it that for mni affine the fixture returns the AFFINE_MNI array itself whereas with the eye affine the fixture returns a function that returns AFFINE_EYE array? Same question for shape_3d_default vs shape_4d_default

Copy link
Collaborator Author

Choose a reason for hiding this comment

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

General answer.

I went for

# global variable
X = "some value"

# function
def _x()
  return X

# fixture
@pytest.mark.fixture()
def x():
  return _x()

When some other fixtures needed to access the value of X and change it.

So to avoid having that fixture change the global (AKA "best way to make things break") I added a function as a go between.

I have not done that systematically because I have not had the need for it for all of those constants. But I think that it would make sense to have the same pattern for all, to avoid this confusion.

@ymzayek
Copy link
Member

ymzayek commented Aug 18, 2023

@Remi-Gau thanks for the explanations. I don't necessarily think you have to change the way you did things if you have a good reason but a comment or docstrings for the helper functions and fixtures might be sufficient. And this could take a bit of extra work to get a good "base" for fixtures that we can then iterate on but I think it will improve testing quite a bit so thanks!

@Remi-Gau
Copy link
Collaborator Author

@Remi-Gau thanks for the explanations. I don't necessarily think you have to change the way you did things if you have a good reason but a comment or docstrings for the helper functions and fixtures might be sufficient. And this could take a bit of extra work to get a good "base" for fixtures that we can then iterate on but I think it will improve testing quite a bit so thanks!

Good point will add some comments and doc strings.

nilearn/conftest.py Outdated Show resolved Hide resolved
nilearn/plotting/tests/test_img_plotting/test_plot_roi.py Outdated Show resolved Hide resolved
@@ -89,7 +94,114 @@ def close_all():
)


def _mni_3d_img(affine=MNI_AFFINE):
def _affine_mni():
Copy link
Member

Choose a reason for hiding this comment

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

Does anything prevent from always using this even inside conftest? What is the benefit of still having the global?

Copy link
Collaborator Author

Choose a reason for hiding this comment

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

yeah I was thinking that the global was progressively becoming useless.

will try to simplify.

Copy link
Collaborator Author

Choose a reason for hiding this comment

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

I actually removed all constants.

Copy link
Member

Choose a reason for hiding this comment

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

But isn't that a breaking change ? I'd rather have deprecation cycles...

Copy link
Collaborator Author

Choose a reason for hiding this comment

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

looking through all the files changed, all those constants only appear in tests so I don't think deprecation is necessary there.

are we actually storing anything in conftest.py that is not just for testing? if so we should probably move it somewhere else

@Remi-Gau Remi-Gau added this to the release 0.10.2 milestone Aug 21, 2023
Copy link
Member

@bthirion bthirion left a comment

Choose a reason for hiding this comment

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

LGTM. Simply woried about API breakage in conftest.py

@@ -89,7 +94,114 @@ def close_all():
)


def _mni_3d_img(affine=MNI_AFFINE):
def _affine_mni():
Copy link
Member

Choose a reason for hiding this comment

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

But isn't that a breaking change ? I'd rather have deprecation cycles...

@Remi-Gau
Copy link
Collaborator Author

I was thinking of adding a bit of contributing doc regarding the use of fixtures: should I do it in this PR? It feels more appropriate but it may make the PR bigger.

@bthirion
Copy link
Member

I was just thinking that some extra package may rely on it ...

@bthirion
Copy link
Member

I was thinking of adding a bit of contributing doc regarding the use of fixtures: should I do it in this PR? It feels more appropriate but it may make the PR bigger.

Should be in this PR I think.

@Remi-Gau
Copy link
Collaborator Author

I was just thinking that some extra package may rely on it ...

except pytest plugins I think that no other package would expect its config information to go in there (at least I kind of hope so)

@ymzayek
Copy link
Member

ymzayek commented Aug 22, 2023

I was just thinking that some extra package may rely on it ...

except pytest plugins I think that no other package would expect its config information to go in there (at least I kind of hope so)

In addition the changes in conftest.py have not been released in a stable version yet

Copy link
Collaborator Author

Choose a reason for hiding this comment

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

Updated the doc on the tests to talk about fixtures and improve this section in general with a couple of extra links.

CONTRIBUTING.rst Outdated Show resolved Hide resolved
CONTRIBUTING.rst Outdated Show resolved Hide resolved
CONTRIBUTING.rst Outdated Show resolved Hide resolved
CONTRIBUTING.rst Outdated Show resolved Hide resolved
CONTRIBUTING.rst Outdated Show resolved Hide resolved
CONTRIBUTING.rst Outdated Show resolved Hide resolved
Comment on lines +414 to +421
(for example you need to generate some data, or a NiftiImage object or a file...)
you can use `pytest fixtures <https://docs.pytest.org/en/6.2.x/fixture.html>`_
to help you mock this data
(more information on pytest fixtures in `this video <https://www.youtube.com/watch?v=ScEQRKwUePI>`_).

Fixture are recognizable because they have a `@pytest.fixture` decorator.
Fixtures that are shared by many tests modules can be found in `nilearn/conftest.py`
but some fixures specific to certain modules can also be kept in that testing module.
Copy link
Member

Choose a reason for hiding this comment

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

maybe we can be more explicit in instructing to first see if any of the available fixtures can be used before writing a new one

Copy link
Collaborator Author

Choose a reason for hiding this comment

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

added one sentence to make it explicit.

CONTRIBUTING.rst Show resolved Hide resolved
CONTRIBUTING.rst Outdated
Tests must be seeded to avoid random failures.
For objects using random seeds (e.g. scikit-learn estimators), pass either
a `np.random.RandomState` or an `int` as the seed.
When your test use random numbers, those must be generated through:
a `np.random.RandomState` or an `int` as the seed.
Copy link
Member

Choose a reason for hiding this comment

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

Maybe for a separate PR but shouldn't we update to use the Generator API as mentioned in #3331 (comment)?

Copy link
Collaborator Author

Choose a reason for hiding this comment

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

I updated it here already: what do you think?

Copy link
Member

Choose a reason for hiding this comment

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

Yes, good with me! At some point we can update our tests to use it but that really should be in a separate PR

Copy link
Collaborator Author

Choose a reason for hiding this comment

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

agreed

I will take care of it soon but it should be done in a separate or several PRs

Copy link
Member

@ymzayek ymzayek left a comment

Choose a reason for hiding this comment

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

Thx @Remi-Gau !

@Remi-Gau Remi-Gau merged commit 9d5409c into nilearn:main Aug 24, 2023
29 checks passed
@Remi-Gau Remi-Gau deleted the refactor_fixtures branch August 24, 2023 13:42
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.

None yet

3 participants