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

Migrate to pytest #1149

Closed
wants to merge 7 commits into from
Closed

Conversation

Glandos
Copy link
Member

@Glandos Glandos commented Feb 25, 2023

Fix #1148

Well for now, this is just experiment, but showing good signs of success.

@Glandos Glandos marked this pull request as ready for review February 26, 2023 10:11
@Glandos
Copy link
Member Author

Glandos commented Feb 26, 2023

Here it is!
Flask-Testing is now removed, we will be able to upgrade everything else! And write more pytest-esque tests.

@zorun
Copy link
Collaborator

zorun commented Feb 26, 2023

Thanks for working on this.

What's the rationale for moving to pytest-style tests? Honestly, I find pytest fixtures a bit confusing.

From what I understand, pytest understands unitttest-style tests, so we can just keep using that?

Btw, does pytest have native support for MagicMock and patch?

@Glandos
Copy link
Member Author

Glandos commented Feb 26, 2023

Ah, we need an explanation/discussion/debate. Good!
Disclaimer: I like the pytest pythonic approach of writing tests. This is purely subjective, so I think it's good to say it before reading.

fixtures are reusable setUp()/teardown(). You just write some code in a function (the setUp), then yield (the value, or nothing), then some code (the teardown). And you can write small functions, they all contains, in one go, their setUp/teardown phases. This is a huge win for me.

Now, the main disadvantage for me are the following implicit behavior:

  • Globally available fixtures must be in a file called conftest.py. It took me an hour to find it.
  • Using fixtures can be automatic, based on the test function argument names. E.g., a function test_my_client(app, client) will try to use fixtures called app and client. This can be confusing.

Honestly, for now, I took the lazy approach: don't modify every existing tests, since they use a class and self everywhere. So I declared some small fixtures (to make them a bit independent), and use them wherever needed.

MagickMock and patch: as far as I know, nothing like this exist in pytest. They have a module for monkeypatching, but the one from unittest is far more useful in my opinion, so I'll stick to that. Since unittest is part of standard library, we are free to use when we want to.

@Glandos Glandos mentioned this pull request Jul 13, 2023
@zorun
Copy link
Collaborator

zorun commented Jul 13, 2023

Yeah, I don't like implicit fixtures. If we do move to pytest, we should document/enforce that we only use explicit fixtures (like the code you propose here)

I don't understand why you need to wrap some test code with a with self.app.app_context():, can you explain?

Copy link
Member

@almet almet left a comment

Choose a reason for hiding this comment

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

Hey, looks good to me! I'm glad to see the tests are moving a bit. I guess they could be refactored a lot and reworked to be simpler now.

I approve the changes. I've a few comments about the docs, because I feel it would be clearer this way. Happy to go use pytest now rather than the old unittest way.

@@ -144,6 +144,18 @@ If you are introducing a new feature, you need to either add tests to
existing classes, or add a new class (if your new feature is
significantly different from existing code).

#### Old tests and new tests
Copy link
Member

Choose a reason for hiding this comment

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

Nit: you need a newline before the paragraph starts.

possible to pytest fixtures. However, a lot of tests were already written,
and are still using class-based tests, albeit without setUp()/teardown().

If you write new tests, please **try to write them with pytest in mind**:
Copy link
Member

Choose a reason for hiding this comment

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

Seems to be missing a newline here as well.

Historically, tests were written with unittest and Flask-Testing.
Because the latter was dying, we removed it and switch as much as
possible to pytest fixtures. However, a lot of tests were already written,
and are still using class-based tests, albeit without setUp()/teardown().
Copy link
Member

Choose a reason for hiding this comment

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

put the method names within backticks for formatting.

and are still using class-based tests, albeit without setUp()/teardown().

If you write new tests, please **try to write them with pytest in mind**:
- standalone function (not in class)
Copy link
Member

Choose a reason for hiding this comment

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

Maybe a sentence would be better, like "tests are done in standalone functions rather than in class and class methods".


If you write new tests, please **try to write them with pytest in mind**:
- standalone function (not in class)
- plain asserts
Copy link
Member

Choose a reason for hiding this comment

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

Use plain assert rather tha this.assertTrue, for instance.

@@ -144,6 +144,18 @@ If you are introducing a new feature, you need to either add tests to
existing classes, or add a new class (if your new feature is
significantly different from existing code).

#### Old tests and new tests
Historically, tests were written with unittest and Flask-Testing.
Copy link
Member

Choose a reason for hiding this comment

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

Rather than starting by explaining how it was in the past, it might be better to explain how it's supposed to be now, and then explain why we have legacy code around. Maybe something like :

Tests are now using pytest. However, [...].

@@ -119,6 +119,7 @@ def test_invite(self):
resp = self.client.get("/raclette/join/token.invalid", follow_redirects=True)
self.assertIn("Provided token is invalid", resp.data.decode("utf-8"))

@pytest.mark.usefixtures("app_ctx")
Copy link
Member

Choose a reason for hiding this comment

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

Is there a way to avoid repeating this line every so often?

Copy link
Contributor

Choose a reason for hiding this comment

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

I would recommend having a look at pytest-flask. I think this is not documented, but this allows among other things to access the flask app context directly in the tests (without with app.app_context(): ...

yield


@pytest.fixture
Copy link
Member

Choose a reason for hiding this comment

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

I'm not sure if it's possible with pytest, but any chance this could be in the same file as the tests?

Copy link
Contributor

Choose a reason for hiding this comment

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

conftest.py is the place for shared fixtures, but pytest allows to define fixtures in test files 👌



class BaseTestCase(TestCase):
@pytest.mark.usefixtures("client", "converter")
class BaseTestCase(unittest.TestCase):
Copy link
Contributor

@azmeuk azmeuk Jul 28, 2023

Choose a reason for hiding this comment

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

Inherinting from unittest.TestCase is not required with pytest.

https://docs.pytest.org/en/7.1.x/getting-started.html#group-multiple-tests-in-a-class

However requires following the class naming convention (start test class names with Test) and abandon unittest assertions self.assert*. For this unittest2pytest can be useful.

@azmeuk
Copy link
Contributor

azmeuk commented Jul 28, 2023

Any reason why sticking to self.assert* unittest assertions? Nowadays pytest assertions produce legible error messages, are easier to write and easier to read, though I know not everybody shares this opinion in the python community. Is this a unit test style choice in IHM or do you plan to move on in a second step?

@almet
Copy link
Member

almet commented Jul 28, 2023

No, there are no reason. If we are to decide to go the pytest way, I believe it's just a migration we have to do, but which is not done yet.

@zorun, you seem to be a against using pytest. Would that be a big problem for you? Is it mainly a problem with fixtures?

On my side, I don't have a strong opinion about this. I like pytest and have been using it in other projects without trouble. I would feel more confortable with it, but I can also keep things as they currently are.

@zorun
Copy link
Collaborator

zorun commented Jul 28, 2023

I'm not strongly opposed to pytest, I just don't like implicit fixtures. If we can avoid them, then let's move to pytest.

The other thing I find really strange is the need for the app_context sometimes, but @azmeuk mentioned that pytest-flask should solve this point.

More work for @Glandos I guess :D

@azmeuk
Copy link
Contributor

azmeuk commented Jul 28, 2023

More work for @Glandos I guess :D

I can gladly help with the pytest migration if ever you don't have a lot of time to spend @Glandos.

@azmeuk azmeuk mentioned this pull request Aug 12, 2023
@zorun
Copy link
Collaborator

zorun commented Oct 1, 2023

Superseded by #1213, thanks @Glandos for the initial work!

@zorun zorun closed this Oct 1, 2023
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.

Migrate to pytest
4 participants