Try   HackMD

mock, patch 與 namespace

tags: unit-test

引言

如果你曾經用過unittest.mock.patch,不知道在使用時你有沒有一個疑問:
我這個patch用下去,究竟要patch哪裡?
我是誰,我在哪裡

案例

如果今天我們的資料夾結構長這樣

root/
----app/
    ----connection.py
----test/
    ----test_connection.py

在connection.py內,是一個簡單的request

# connection.py in ./app import requests def connection(): # try to call a website # default it return 200 response = requests.get('https://www.google.com.tw/') return response.status_code

然後今天在測試的時候,如果是使用mock.patch不久的人,應該會有個顯著的疑惑

# test_connection.py from app import connection def test_connection(mocker): # create mock object mock_response = mocker.Mock() mock_response.status_code = 200 # patch requests mock_requests = mocker.patch("app.connection.requests") mock_requests.get.return_value = mock_response # act response = connection.connection() assert response == 200

在寫到第11行的時候,到底patch要指到哪裡才是對的呢?
究竟是patch

  1. "app.connection.requests"
  2. "app.connection.requests.get"
  3. "requests"
  4. "requests.get"

究竟該patch誰才是對的?

如果在這個地方放棄思考,用try&error的方式硬幹當然是可以的,但沒有搞懂這裡的邏輯的話不說可惜,之後照寫的測試可能會怪怪的(汗

Namespace

在揭曉答案之前,有個基本的東西需要知道

在各位import module的時候,有沒有想過import到底做了什麼呢?

例如一個簡單的範例,我們在A.py裡import

import requests

這時python會把requests的名子抓過來,放到這個A.py的namespace裡

這時候如果在這個A.py內呼叫

dir()

就會看到

['__builtins__', '__doc__', '__file__', '__loader__', '__name__', '__package__', '__spec__', 'requests']

requests現在就會被A.py看到了

unittest.mock.patch

patch究竟做了什麼事情呢?

實際上的運作是,patch會把namespace內的某個指到的module換成的MagicMock object,所以要讓patch正確做事,我們必須

  1. 確保patch的東西是被測試的file(connection.py)看得到的人
  2. 確認在測試file(test_connection.py)中對應的module可以被import

我們以前列的connection.py為例子

# connection.py in ./app import requests def connection(): # try to call a website # default it return 200 response = requests.get('https://www.google.com.tw/') return response.status_code

這時候如果在這裡dir(),如同上個章節所言,我們會得到

[..., 'requests']

所以,如果在測試中要patch掉這裡的requests,以patch的定義來說,我們必須

patch("app.connection.requests")

最後,不管用得是patch還是patch.object,他們的souce code其實最後都一樣,所以如果用的是patch.object的注意事項也一樣

return _patch( getter, attribute, new, spec, create, spec_set, autospec, new_callable, kwargs, unsafe=unsafe )

有興趣的可以去這裡 https://github.com/python/cpython/blob/main/Lib/unittest/mock.py#L1317
https://docs.python.org/3/library/unittest.mock.html#unittest.mock.patch

後記

本篇是遠遠不到講完,甚至可能連講清楚都欠奉的一篇文章,所以理所當然的有下集(其實是寫不完)

下一篇文繼續深入講解這個主題

令人混淆的mock.patch,他還是高人氣的秘密在哪裡呢?

主要會講講where to patch跟一些案例