亚洲在线久爱草,狠狠天天香蕉网,天天搞日日干久草,伊人亚洲日本欧美

為了賬號安全,請及時綁定郵箱和手機立即綁定
已解決430363個問題,去搜搜看,總會有你想問的

如何在 Python 的 unittest 框架中模擬返回 self 的方法

如何在 Python 的 unittest 框架中模擬返回 self 的方法

慕容森 2022-05-24 16:29:50
我正在使用一個類,該類具有一個方法,該方法shuffle返回調用它的實例的洗牌版本。這是:shuffled_object = unshuffled_object.shuffle(buffer_size)我想模擬這個方法,這樣當它被調用時,它會簡單地返回自身,而不需要任何改組。以下將是這種情況的簡化:# my_test.pyclass Test():    def shuffle(self, buffer_size):        return self# test_mockimport unittestimport unittest.mock as mkimport my_testdef mock_test(self, buffer_size):    return selfclass TestMock(unittest.TestCase):    def test_mock(self):        with mk.patch('my_test.Test.shuffle') as shuffle:            shuffle.side_effect = mock_test            shuffled_test = my_test.Test().shuffle(5)但是,當我嘗試此操作時,出現以下錯誤:TypeError: mock_test() missing 1 required positional argument: 'buffer_size'該方法僅使用參數5調用,調用實例并未將自身作為self參數傳遞給該方法。是否可以使用模塊實現這種行為unittest.mock?編輯:真正的代碼是這樣的:# input.pydef create_dataset():    ...    raw_dataset = tf.data.Dataset.from_generator(data_generator, output_types, output_shapes)    shuffled_dataset = raw_dataset.shuffle(buffer_size)    dataset = shuffled_dataset.map(_load_example)    ...    return dataset# test.pydef shuffle(self, buffer_size):    return selfwith mk.patch(input.tf.data.Dataset.shuffle) as shuffle_mock:    shuffle_mock.side_effect = shuffle    dataset = input.create_dataset()這里最大的問題是我只想模擬該shuffle方法,因為我不希望它在測試時是隨機的,但我想保留其余的原始方法,以便我的代碼可以繼續工作。棘手的部分是,shuffle它不僅打亂調用它的實例,而且返回打亂的實例,所以我想在測試時返回數據集的未打亂版本。另一方面,讓 mock 繼承并不是那么簡單,tf.data.Dataset因為據我了解,它Dataset似乎是一個帶有抽象方法的抽象類,我想從Dataset初始化程序from_generator創建的任何子類型中抽象出自己。
查看完整描述

3 回答

?
侃侃爾雅

TA貢獻1801條經驗 獲得超16個贊

為什么你沒有 self 參數

聲明一個類時,function您定義的內容與method您的實例中的一樣。這是它的一個實例:


>>> def function():

...     pass

... 

>>> type(function)

<class 'function'>

>>> class A:

...     def b(self):

...         print(self)

>>> type(A.b)

<class 'function'>

>>> a = A()

>>> type(a.b)

<class 'method'>

# So you have the same behavior between the two following calls

>>> A.b(a)

<__main__.A object at 0x7f734511afd0>

>>> a.b()

<__main__.A object at 0x7f734511afd0>

解決方案

我可以提出一些解決方案,但并非都具有吸引力,具體取決于您的使用和需求。


模擬班級

您可以模擬整個類以覆蓋函數定義。如前所述,這考慮到您不使用類的抽象。


import unittest

import unittest.mock as mk


import my_test

import another


class TestMocked(my_test.Test):

    def shuffle(self, buffer_size):

        return self


@mk.patch("my_test.Test", TestMocked)

# Uncomment to mock the other file behavior

# @mk.patch("another.Test", TestMocked)

def test_mock():

    test_class = my_test.Test()

    shuffled_test = test_class.shuffle(2)

    print(my_test.Test.shuffle)

    # This is another file using your class,

    # You will have to mock it too in order to see the mocked behavior

    print(another.Test.shuffle) 

    assert shuffled_test == test_class

將輸出:


>>> from test_mock import test_mock

>>> test_mock()

<function TestMocked.shuffle at 0x7ff1f03f0ae8>

<function Test.shuffle at 0x7ff1f03f09d8>


直接調用函數

我不喜歡這個,因為它會讓你更改測試代碼。您可以將呼叫從 轉換instance.method()為class.method(instance)。這將按預期將參數發送到您的模擬函數。


# my_input.py

import tensorflow as tf



def data_generator():

    for i in itertools.count(1):

        yield (i, [1] * i)



def create_dataset():

    _load_example = lambda x, y: x+y

    buffer_size = 3

    output_types = (tf.int64, tf.int64)

    output_shapes = (tf.TensorShape([]), tf.TensorShape([None]))

    raw_dataset = tf.data.Dataset.from_generator(data_generator, output_types, output_shapes)


    shuffled_dataset = tf.data.Dataset.shuffle(raw_dataset, buffer_size)


    assert raw_dataset == shuffled_dataset

    assert raw_dataset is shuffled_dataset


    dataset = shuffled_dataset.map(_load_example)

    return dataset



# test_mock.py

import unittest.mock as mk

import my_input



def shuffle(self, buffer_size): 

    print("Shuffle! {}, {}".format(self, buffer_size))

    return self



with mk.patch('my_input.tf.data.Dataset.shuffle') as shuffle_mock:

    shuffle_mock.side_effect = shuffle

    dataset = my_input.create_dataset()

運行時,您將獲得以下輸出:


$ python test_mock.py

Shuffle! (<DatasetV1Adapter shapes: ((), (?,)), types: (tf.int64, tf.int64)>, 3)

將方法使用包裝在一個函數中

這幾乎與之前的答案一樣,但是您可以將其包裝如下,而不是從類中調用該方法:


# my_input.py

import tensorflow as tf



def data_generator():

    for i in itertools.count(1):

        yield (i, [1] * i)



def shuffle(instance, buffer_size):

    return instance.shuffle(buffer_size)



def create_dataset():

    _load_example = lambda x, y: x+y

    buffer_size = 3

    output_types = (tf.int64, tf.int64)

    output_shapes = (tf.TensorShape([]), tf.TensorShape([None]))

    raw_dataset = tf.data.Dataset.from_generator(data_generator, output_types, output_shapes)


    shuffled_dataset = tf.data.Dataset.shuffle(raw_dataset, buffer_size)


    assert raw_dataset == shuffled_dataset

    assert raw_dataset is shuffled_dataset


    dataset = shuffled_dataset.map(_load_example)

    return dataset




# test_mock.py

import unittest.mock as mk

import my_input



def shuffle(self, buffer_size): 

    print("Shuffle! {}, {}".format(self, buffer_size))

    return self



with mk.patch('my_input.shuffle') as shuffle_mock:

    shuffle_mock.side_effect = shuffle

    dataset = my_input.create_dataset()


查看完整回答
反對 回復 2022-05-24
?
莫回無

TA貢獻1865條經驗 獲得超7個贊

我想我已經找到了解決問題的合理方法。我沒有嘗試修補 的shuffle方法tf.data.Dataset,而是認為如果我可以訪問它,我可以直接在要測試的實例上更改它。因此,我嘗試修補創建實例的方法tf.data.Dataset.from_generator,以便它調用原始方法,但在返回新創建的實例之前,它用shuffle另一個簡單地返回未更改數據集的方法替換它的方法。代碼如下:


from_generator_old = tf.data.Dataset.from_generator


def from_generator_new(generator, output_types, output_shapes=None, args=None):

    dataset = from_generator_old(generator, output_types, output_shapes, args)

    dataset.shuffle = lambda *args, **kwargs: dataset


    return dataset


from data_input.kitti.kitti_input import tf as tf_mock


with mk.patch.object(tf_mock.data.Dataset, 'from_generator', from_generator_new):

    dataset = input.create_dataset()

這似乎有效,但我不確定這是否是正確的方法。如果有人有更好的主意或能想到我不應該這樣做的原因,歡迎提出建議或其他答案,但到目前為止,我認為這是最好的選擇。如果沒有人提出更好的建議,我想我會將其標記為已接受的答案。


編輯:

我已經為這個問題找到了更好的解決方案。經過一番閱讀,我遇到了關于模擬未綁定方法的解釋。顯然,當mock.patch.object與autospec參數設置為一起使用時True,修補方法的簽名被維護,在引擎蓋下調用該方法的模擬版本。然后,此方法將綁定到調用它的實例(即,將實例作為self參數)??梢栽谝韵骆溄酉抡业浇忉專?/p>


https://het.as.utexas.edu/HET/Software/mock/examples.html#mocking-unbound-methods


在測試這個時,我還發現,當使用tf.test.TestCase類而不是unittest.TestCase測試時,整個計算圖的隨機種子似乎是固定的,所以shuffle每次在這個框架下測試的結果都是一樣的。然而,這似乎根本沒有記錄在案,所以我不確定盲目依賴它是否是個好主意。


查看完整回答
反對 回復 2022-05-24
?
暮色呼如

TA貢獻1853條經驗 獲得超9個贊

你在評論中說

我想檢查dataset在迭代它時是否返回正確的元素”。

客戶create_dataset()不希望元素以任何特定的順序排列,只要所有預期的元素和只有預期的元素都在那里,無論順序是什么,它們都會很好。所以這就是測試應該檢查的內容。

def test_create_dataset():
    dataset = create_dataset()
        assert sorted(dataset) == sorted(expected_elements)

根據迭代數據集時返回的值的類型,斷言可能需要更復雜。例如,如果元素是numpy數組或pandas.Series. 在這種情況下,您將需要使用自定義密鑰。這適用于numpypandas對象:

sorted(dataset, key=list)

或者你可以使用setcollections.Counter...

現在解決評論中表達的一些擔憂:

如果你的意思是shuffle功能

是的,測試想要改變實現.shuffle()并且代碼試圖隱藏它。這使得測試難以編寫(這就是為什么你必須首先來這里提出問題)并且很可能難以理解代碼的未來維護者(可能包括你未來的自己)。我寧愿盡量避免它。

正如我在上面的評論中所說,我認為應該替換它以使測試更加健壯/有意義。

作為create_dataset()我不知道的用戶,我不關心洗牌。這對我來說毫無意義。我調用函數的方式沒有類似的東西,它只是一個實現細節。

讓您的測試擔心這一點會使測試變得脆弱,而不是更健壯。如果您將實現更改為不打亂數據,或者在不調用的情況下打亂數據Dataset.shuffle(),我仍然會得到正確的數據,但測試會失敗。這是為什么?因為它正在檢查我不關心的東西。我也會盡量避免這種情況。

畢竟,這不就是嘲諷的全部目的嗎?使某些模塊的結果可預測,以隔離您實際想要測試的代碼的影響?

是的。嗯,或多或少。但是您要測試的代碼(函數create_dataset())將改組隱藏在其中作為實現細節并與其他行為耦合,從調用者的角度來看,這里沒有什么可以隔離的?,F在測試說不,我想打電話create_dataset()但分開洗牌行為,沒有明顯的方法可以做到這一點,這就是你來這里問問題的原因。

我寧愿通過讓代碼和測試就應該將哪些行為相互解耦達成一致,從而省去這些麻煩。

我寧愿不因為測試而改變我的代碼

也許你應該考慮這樣做。測試可以告訴您您沒有預料到的代碼的有趣用途。您編寫了一個想要改變洗牌行為的測試。其他客戶是否有正當理由想要這樣做?可重復的研究是一回事,也許將種子作為參數畢竟是有意義的。


查看完整回答
反對 回復 2022-05-24
  • 3 回答
  • 0 關注
  • 116 瀏覽
慕課專欄
更多

添加回答

舉報

0/150
提交
取消
微信客服

購課補貼
聯系客服咨詢優惠詳情

幫助反饋 APP下載

慕課網APP
您的移動學習伙伴

公眾號

掃描二維碼
關注慕課網微信公眾號