caronc / apprise

Apprise - Push Notifications that work with just about every platform!
https://hub.docker.com/r/caronc/apprise
BSD 2-Clause "Simplified" License
10.9k stars 384 forks source link

Enhancement: Refactor Test Code for Efficiency and Quality #1100

Closed freddiewanah closed 2 months ago

freddiewanah commented 2 months ago

Description:

As a first-time contributor with a background in researching Python unit tests, I conducted a thorough static analysis of the project's test code. This examination revealed various "test smells" that could potentially degrade both the efficiency and quality of our tests. To address these issues, I've refactored the relevant code segments. The modifications I propose not only aim to eliminate these inefficiencies but are also expected to reduce execution time within GitHub Actions. This reduction could contribute to lower operational costs for the project.

Test smells refactored:

I didn't change any of the testing logic or testing values. The refactoring only focuses on reducing the test smells.

Please feel free to let me know if you are interested in more refactoring like this or if there're any changes that you don't wish.

Checklist

Testing

Anyone can help test this source code as follows:

# Create a virtual environment to work in as follows:
python3 -m venv apprise

# Change into our new directory
cd apprise

# Activate our virtual environment
source bin/activate

# Install the branch
pip install git+https://github.com/freddiewanah/apprise.git
caronc commented 2 months ago

I totally support the with pytest.raises() changes; those are great. 🚀🙂

However, i disagree with the removal of is True everywhere. Anything that isn't None, False or doesn't have a __class__.__bool__() defined will always pass this condition with your new change.

# consider that all of these statements are True:
assert 1
assert 2
assert 'hello' 
assert True
assert object()
assert 0.00001

By removing the is True, you're removing the check of additional verifying the returned object itself (you reduce the scope of expression being validated)

# The following checks 2 very different distinct things 
assert (1 + 1)
assert (1 + 1) == 2
freddiewanah commented 2 months ago

Thank you so much for the feedback, I'm sorry for being reckless and removed all the is True check. I will go over and revise the corresponding code.

caronc commented 2 months ago

No need to appologize at all. I appreciate your interest in trying to improve the code base! 🏆

freddiewanah commented 2 months ago

Hi, I've reverted most of the assert xxx is True, but for statements like isInstance or startswith, I think it's find to remove the is True check.

Another thing I noticed is some of the test cases are too big, with duplicated asserts repeated in one test case (e.g., test_plugin_dbus_general_success). Do you want me to refactor it with the @pytest.parameterize to make the code clean and also efficient to execute?

caronc commented 2 months ago

That's totally up to you. Consider the some tests appear as though they repeat, but they actually test sections of the code that previous calls created a cache entry for. So the additional calls test the handling of cached responses.

Apprise has 100% test coverage except for the NotifyDBus section which needs a complete refactoring. Another kind person already started a PR on this already... The point is that if you start cleaning up and refactoring tests; outside of the above Service/Plugin, there should be no change in code coverage. 😉

freddiewanah commented 2 months ago

Thanks. I will see how I can refactor some of the cases and make sure that won't affect the test coverage and the basic test logic :)

freddiewanah commented 2 months ago

It has been some time since our last discussion on this PR. Regarding the test style implemented here, I'm unsure if it aligns with the project's preferences and would appreciate your feedback. If this approach meets your expectations, I'm ready to extend these refactorings further.

For instance, in the test_is_email, currently, if one assertion fails, the subsequent tests do not execute, potentially introducing additional issues. I propose adopting a pytest.mark.parametrize approach to enhance the robustness of our test suite. Below is my proposed methodology for your review.

@pytest.mark.parametrize(
    "input_email, expected",
    [
        ('test@gmail.com', {'name': '', 'email': 'test@gmail.com', 'full_email': 'test@gmail.com', 'domain': 'gmail.com', 'user': 'test', 'label': ''}),
        ('test@my-valid_host.com', {'name': '', 'email': 'test@my-valid_host.com', 'full_email': 'test@my-valid_host.com', 'domain': 'my-valid_host.com', 'user': 'test', 'label': ''}),
        ('tag+test@gmail.com', {'name': '', 'email': 'test@gmail.com', 'full_email': 'tag+test@gmail.com', 'domain': 'gmail.com', 'user': 'test', 'label': 'tag'}),
        ('Bill Gates: bgates@microsoft.com', {'name': 'Bill Gates', 'email': 'bgates@microsoft.com', 'full_email': 'bgates@microsoft.com', 'domain': 'microsoft.com', 'user': 'bgates', 'label': ''}),
        ('Bill Gates <bgates@microsoft.com>', {'name': 'Bill Gates', 'email': 'bgates@microsoft.com', 'full_email': 'bgates@microsoft.com', 'domain': 'microsoft.com', 'user': 'bgates', 'label': ''}),
        ('Bill Gates: <bgates@microsoft.com>', {'name': 'Bill Gates', 'email': 'bgates@microsoft.com', 'full_email': 'bgates@microsoft.com', 'domain': 'microsoft.com', 'user': 'bgates', 'label': ''}),
        ('Sundar Pichai <ceo+spichai@gmail.com>', {'name': 'Sundar Pichai', 'email': 'spichai@gmail.com', 'full_email': 'ceo+spichai@gmail.com', 'domain': 'gmail.com', 'user': 'spichai', 'label': 'ceo'}),
        ('"Chris Hemsworth" <ch@test.com>', {'name': 'Chris Hemsworth', 'email': 'ch@test.com', 'full_email': 'ch@test.com', 'domain': 'test.com', 'user': 'ch', 'label': ''}),
        ('      <spichai@gmail.com>', {'name': '', 'email': 'spichai@gmail.com', 'full_email': 'spichai@gmail.com', 'domain': 'gmail.com', 'user': 'spichai', 'label': ''}),
        ("Name valid@example.com", {'name': 'Name', 'email': 'valid@example.com', 'full_email': 'valid@example.com', 'domain': 'example.com', 'user': 'valid', 'label': ''}),
        ("Руслан Эра russian+russia@example.ru", {'name': 'Руслан Эра', 'email': 'russia@example.ru', 'full_email': 'russian+russia@example.ru', 'domain': 'example.ru', 'user': 'russia', 'label': 'russian'}),
        ('invalid.com', False),
        (object(), False),
        (None, False),
        ("Just A Name", False),
        ("Name <bademail>", False),
        ('a-z0-9_!#$%&*+/=?%`{|}~^.-@gmail.com', {'name': '', 'email': '/=?%`{|}~^.-@gmail.com', 'full_email': 'a-z0-9_!#$%&*+/=?%`{|}~^.-@gmail.com', 'domain': 'gmail.com', 'user': '/=?%`{|}~^.-', 'label': 'a-z0-9_!#$%&*'}),
        ('a-z0-9_!#$%&*/=?%`{|}~^.-@gmail.com', {'name': '', 'email': 'a-z0-9_!#$%&*/=?%`{|}~^.-@gmail.com', 'full_email': 'a-z0-9_!#$%&*/=?%`{|}~^.-@gmail.com', 'domain': 'gmail.com', 'user': 'a-z0-9_!#$%&*/=?%`{|}~^.-', 'label': ''}),
    ]
)
def test_is_emaill(input_email, expected):
    assert utils.is_email(input_email) == expected

Maybe I should submit a new PR for such changes?

caronc commented 2 months ago

@freddiewanah I don't mind merging what you have if you want. I'm not sure if you're not just giving yourself extra work to do your suggested changes. But if you see value in it (and the code coverage doesn't suffer), by all means go for it. :rocket:

freddiewanah commented 2 months ago

Thanks @caronc . Please proceed with merging this one.

It may take me a while to complete and create a new PR, but I'm willing to spend some time working on this.

caronc commented 2 months ago

Thank you for all of your great work!

dgtlmoon commented 1 month ago

great PR, nice work!