-
-
Notifications
You must be signed in to change notification settings - Fork 429
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
Convert unittest class base tests to pytest function tests #553
Conversation
Codecov Report
@@ Coverage Diff @@
## master #553 +/- ##
=========================================
- Coverage 83.8% 57.8% -25.9%
=========================================
Files 21 39 +18
Lines 1175 2464 +1289
Branches 60 62 +2
=========================================
+ Hits 984 1424 +440
- Misses 185 1032 +847
- Partials 6 8 +2
Flags with carried forward coverage won't be shown. Click here to find out more.
Continue to review full report at Codecov.
|
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.
Great job and thank you for contributing. We were talking in the past too separate classes in different files, could you separate tests that were in the same class in different files?
I did not review everything because it seems that the same suggestions will be made for the rest of the file and I will need anyway to review everything after the splitting and small fixes will be made
tests/test_backend.py
Outdated
res = self.cache.set("test_key_nx", 1, nx=True) | ||
self.assertTrue(res) | ||
res = default_cache.set("test_key_nx", 1, nx=True) | ||
assert res |
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 prefer it to be more specific
assert res | |
assert res == True |
tests/test_backend.py
Outdated
|
||
|
||
@pytest.fixture() | ||
def available_caches(settings): |
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.
could you add a little bit of type hints? and maybe also separate them? just so that it looks cleaner and you do not have to do the unpacking in every test
tests/test_backend.py
Outdated
res = self.cache.delete_pattern("*foo-a*") | ||
self.assertTrue(bool(res)) | ||
res = cache.delete_pattern("*foo-a*") | ||
assert bool(res) |
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.
better to check if it is True
assert bool(res) | |
assert res is True |
tests/test_backend.py
Outdated
self.assertFalse(res) | ||
default_cache.set("test_key_nx", 1) | ||
res = default_cache.set("test_key_nx", 2, timeout=2, nx=True) | ||
assert not res |
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.
assert not res | |
assert res is False |
tests/test_backend.py
Outdated
res = self.cache.delete("a") | ||
self.assertFalse(bool(res)) | ||
res = default_cache.delete("a") | ||
assert not bool(res) |
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.
assert not bool(res) | |
assert res is False |
tests/test_backend.py
Outdated
assert res | ||
res = default_cache.delete("b") | ||
assert type(res) == bool | ||
assert not res |
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.
assert not res | |
assert res is False |
tests/test_backend.py
Outdated
mocker.patch("django_redis.cache.DJANGO_VERSION", new=(3, 1, 0, "final", 0)) | ||
default_cache.set("a", 1) | ||
res = default_cache.delete("a") | ||
assert type(res) == bool |
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.
let's keep isinstance for simplicity and consistency
assert type(res) == bool | |
assert isinstance(res, bool) |
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.
Agree, I've been using https://github.com/pytest-dev/unittest2pytest to convert larger test classes and didn't thoroughly check results.
tests/test_backend.py
Outdated
assert type(res) == bool | ||
assert res | ||
res = default_cache.delete("b") | ||
assert type(res) == bool |
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.
let's keep isinstance for simplicity and consistency
assert type(res) == bool | |
assert isinstance(res, bool) |
tests/test_backend.py
Outdated
def test_delete_many(self, default_cache): | ||
default_cache.set_many({"a": 1, "b": 2, "c": 3}) | ||
res = default_cache.delete_many(["a", "b"]) | ||
assert bool(res) |
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.
assert bool(res) | |
assert res is True |
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.
Deletion of the multiple keys returns back the number of deleted objects and therefore I used assert bool(res) is True
to cover these cases.
tests/test_backend.py
Outdated
assert res == {"c": 3} | ||
|
||
res = default_cache.delete_many(["a", "b"]) | ||
assert not bool(res) |
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.
assert not bool(res) | |
assert res is False |
Hi @WisdomPill and thank you for the review.
Sure, I will work on it.
That totally makes sense and I also noticed that a lot of issues were related to the automatic unittest -> pytest migration tool that I used and I wasn't very careful reviewing results 😅 |
812cb2e
to
c9e06e1
Compare
It will take me some time to review the whole PR because during the week I do not have much time. Last but not least, I want to raise a question, is there a reason to test for type after all? Python 3.9.1 (default, Jan 8 2021, 17:17:43)
[Clang 12.0.0 (clang-1200.0.32.28)] on darwin
Type "help", "copyright", "credits" or "license" for more information.
>>> "1" == 1
False
>>> |
4ca48a4
to
151b8e1
Compare
I was probably a bit confused regarding your suggestion of adding type annotations to the provided fixtures and added them to all tests :) Updated version removes that checks and I assume should fix issues with
|
@WisdomPill would be great if you can approve workflow execution to check tests 🙇 |
Done |
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.
The change looks good to me, but I guess there's a little bit of a difference of opinion on the asserts 😅
tests/test_backend.py
Outdated
def make_key(key, prefix, version): | ||
return f"{prefix}#{version}#{key}" | ||
@pytest.fixture | ||
def cache() -> Generator[RedisCache, None, None]: |
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.
If you're not supplying the second two type variables this is a shorthand for the same thing
def cache() -> Generator[RedisCache, None, None]: | |
def cache() -> Iterable[RedisCache]: |
res = self.cache.set("test_key_nx", 1, nx=True) | ||
self.assertTrue(res) | ||
res = cache.set("test_key_nx", 1, nx=True) | ||
assert bool(res) is True |
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.
@WisdomPill do you strongly prefer assert bool(res) is True
over assert res
and assert res is False
over assert not res
? I find it a bit wordy since it means the same thing, but I understand you already requested the change in #553 (comment)
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 prefer assert res is True
because it more readable and correct.
From python zen
Python 3.9.1 (default, Jan 8 2021, 17:17:43)
[Clang 12.0.0 (clang-1200.0.32.28)] on darwin
Type "help", "copyright", "credits" or "license" for more information.
>>> import this
The Zen of Python, by Tim Peters
Beautiful is better than ugly.
Explicit is better than implicit.
Simple is better than complex.
Complex is better than complicated.
Flat is better than nested.
Sparse is better than dense.
Readability counts.
Special cases aren't special enough to break the rules.
Although practicality beats purity.
Errors should never pass silently.
Unless explicitly silenced.
In the face of ambiguity, refuse the temptation to guess.
There should be one-- and preferably only one --obvious way to do it.
Although that way may not be obvious at first unless you're Dutch.
Now is better than never.
Although never is often better than *right* now.
If the implementation is hard to explain, it's a bad idea.
If the implementation is easy to explain, it may be a good idea.
Namespaces are one honking great idea -- let's do more of those!
>>>
more specifically the two points,
- Explicit is better than implicit
- Readability counts.
It is more correct and more elegant (imho), because it also checks the type, like the following
>>> True is True
True
>>> bool([1])
True
>>> bool("hello")
True
>>> bool(1)
True
>>> 1 is True
<stdin>:1: SyntaxWarning: "is" with a literal. Did you mean "=="?
False
>>> [1] is True
False
>>> "hello" is True
<stdin>:1: SyntaxWarning: "is" with a literal. Did you mean "=="?
False
>>>
This is why I am used to not leave any doubt about why there should be
if len(li):
pass
instead of
if li:
pass
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'm not sure I agree about it being more readable. Both are quite readable. It's definitely more pedantic, and I know it enforces the type so it is more accurate. The question would be does the accuracy matter.
It sounds like the simple answer would have just been "yes", and we can just leave it as is (I know it might also be frustrating for @rootart to be asked to switch it back so they may just want to leave it as is too 😅 )
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.
no problem. let's get this through, if you have reviewed it it's okay for me, in case I will change the tests to be more pedantic when I will have time for it
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 think I might have made some mistakes, by saying that assert bool(res) is True
would be the right way.
I disagree, I al in favor of assert res is True
|
||
@patch("django_redis.cache.DJANGO_VERSION", (3, 1, 0, "final", 0)) |
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.
It should be OK to leave @patch
as a decorator (here and the rest of the PR).
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.
Alright, I will still keep mocker
for patching objects and properties if possible.
tests/test_cache_options.py
Outdated
|
||
|
||
@pytest.fixture | ||
def key_prefix_cache(settings: SettingsWrapper) -> Generator[RedisCache, None, None]: |
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.
def key_prefix_cache(settings: SettingsWrapper) -> Generator[RedisCache, None, None]: | |
def key_prefix_cache(settings: SettingsWrapper) -> Iterable[RedisCache]: |
caches_setting = copy.deepcopy(settings.CACHES) | ||
caches_setting["default"]["KEY_FUNCTION"] = "test_cache_options.make_key" | ||
caches_setting["default"]["REVERSE_KEY_FUNCTION"] = "test_cache_options.reverse_key" | ||
settings.CACHES = caches_setting |
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 don't believe you need the copy 🤔 or is this because the settings wrapper doesn't understand mutations to the underlying dict
?
caches_setting = copy.deepcopy(settings.CACHES) | |
caches_setting["default"]["KEY_FUNCTION"] = "test_cache_options.make_key" | |
caches_setting["default"]["REVERSE_KEY_FUNCTION"] = "test_cache_options.reverse_key" | |
settings.CACHES = caches_setting | |
settings.CACHES["default"]["KEY_FUNCTION"] = "test_cache_options.make_key" | |
settings.CACHES["default"]["REVERSE_KEY_FUNCTION"] = "test_cache_options.reverse_key" |
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 that direct mutation doesn't trigger the required signals to reload the configuration.
def test_close_disconnect_settings_cache( | ||
self, cache_client: DefaultClient, mocker: MockerFixture | ||
): | ||
settings.CACHES[DEFAULT_CACHE_ALIAS]["OPTIONS"]["CLOSE_CONNECTION"] = True |
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.
Thinking about the last comment, does settings
need to be a fixture so that this doesn't affect the global state for this config?
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.
You are right, will replace it with a fixture to avoid changing the global state.
…nction test classes to pytest
…tShardClient classes to pytest
151b8e1
to
2b820be
Compare
I reviewed everything and made some minor changes, but it looks good @rootart! Thanks for all your efforts 🙂 |
Many thanks for the great package folks 🤗!
I managed to convert the majority of tests from unittest class base tests to pytest (related issue #521 )and will appreciate your feedback and reviews.
SessionTests
tests were not migrated as they look to be very much coupled with ones from Django.Fixes #521