Skip to content

Why did test_notifies_observers_when_cogs_change patch bicycle.observer.Observer.changed #2

Open
@gitgithan

Description

@gitgithan

Following the conventional wisdom that we should patch objects where they are looked up and not where they are defined,
I got confused by the code doing obs_mock = mocker.patch("bicycle.observer.Observer.changed") because it seemed to me bicycle.observer.Observer.changed is closer to where Observer was defined.

I thought the patching code should be obs_mock = mocker.patch("bicycle.gear.Observer.changed") instead.

I tested the change with pytest -sv tests/test_bicycle.py::test_notifies_observers_when_cogs_change.
To my surprise, it also passed.

So I added 2 lines to check for equivalence of the Observer class in 2 different files.
In gear.py i added print(f"In gear.py: {id(Observer) =}")
In test_bicycle.py i added print(f"In test_bicycle.py: {id(bicycle.gear.Observer) =}")

I got the same id when running the test.
This tells me it doesn't matter whether i patch bicycle.observer.Observer.changed or bicycle.gear.Observer.changed because they are the same object.

Then i thought why are they the same, and realized inside gear.py it does from bicycle.observer import Observer.
When pytest runs, it runs the entire test_bicycle.py which triggers from bicycle.gear import Gear.
This in turn causes the entire gear.py to run (an unintuitive part of python import system) even though only Gear class is imported.
When gear.py runs, it imports Observer.

Then to push my understanding, I deleted from bicycle.observer import Observer from test_bicycle.py and was glad to see both mocker.patch("bicycle.observer.Observer.changed") and mocker.patch("bicycle.gear.Observer.changed") passed the test.
Previous statement assumes i only run 1 test with pytest -sv tests/test_bicycle.py::test_notifies_observers_when_cogs_change, because other tests depending on Observer() will fail since it's not imported into test_bicycle.py after my edit.

I guess this works because the Observer in gear.py came from the Observer in observer.py through from bicycle.observer import Observer in gear.py.

Please correct my understanding if any of the above is wrong.
Also hoping to hear commentary of when to do what/not to do what, relating to my edits mentioned above

This example was confusing also because usually, the test function imports the object it uses (https://docs.python.org/3/library/unittest.mock.html#where-to-patch).

In this example,

    obs_mock = mocker.patch("bicycle.observer.Observer.changed")
    wheel = Wheel(rim=26, tire=1.5)
    gear = Gear(
        chainring=52, cog=11, wheel=wheel, observer=obs_mock
    )

the observer value used to initialize Gear came out of thin air instead of being imported.

Metadata

Metadata

Assignees

No one assigned

    Labels

    No labels
    No labels

    Type

    No type

    Projects

    No projects

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions