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()

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每次在這個框架下測試的結果都是一樣的。然而,這似乎根本沒有記錄在案,所以我不確定盲目依賴它是否是個好主意。

TA貢獻1853條經驗 獲得超9個贊
你在評論中說
我想檢查
dataset
在迭代它時是否返回正確的元素”。
客戶create_dataset()
不希望元素以任何特定的順序排列,只要所有預期的元素和只有預期的元素都在那里,無論順序是什么,它們都會很好。所以這就是測試應該檢查的內容。
def test_create_dataset(): dataset = create_dataset() assert sorted(dataset) == sorted(expected_elements)
根據迭代數據集時返回的值的類型,斷言可能需要更復雜。例如,如果元素是numpy
數組或pandas.Series
. 在這種情況下,您將需要使用自定義密鑰。這適用于numpy
和pandas
對象:
sorted(dataset, key=list)
或者你可以使用set
或collections.Counter
...
現在解決評論中表達的一些擔憂:
如果你的意思是
shuffle
功能
是的,測試想要改變實現.shuffle()
并且代碼試圖隱藏它。這使得測試難以編寫(這就是為什么你必須首先來這里提出問題)并且很可能難以理解代碼的未來維護者(可能包括你未來的自己)。我寧愿盡量避免它。
正如我在上面的評論中所說,我認為應該替換它以使測試更加健壯/有意義。
作為create_dataset()
我不知道的用戶,我不關心洗牌。這對我來說毫無意義。我調用函數的方式沒有類似的東西,它只是一個實現細節。
讓您的測試擔心這一點會使測試變得脆弱,而不是更健壯。如果您將實現更改為不打亂數據,或者在不調用的情況下打亂數據Dataset.shuffle()
,我仍然會得到正確的數據,但測試會失敗。這是為什么?因為它正在檢查我不關心的東西。我也會盡量避免這種情況。
畢竟,這不就是嘲諷的全部目的嗎?使某些模塊的結果可預測,以隔離您實際想要測試的代碼的影響?
是的。嗯,或多或少。但是您要測試的代碼(函數create_dataset()
)將改組隱藏在其中作為實現細節并與其他行為耦合,從調用者的角度來看,這里沒有什么可以隔離的?,F在測試說不,我想打電話create_dataset()
但分開洗牌行為,沒有明顯的方法可以做到這一點,這就是你來這里問問題的原因。
我寧愿通過讓代碼和測試就應該將哪些行為相互解耦達成一致,從而省去這些麻煩。
我寧愿不因為測試而改變我的代碼
也許你應該考慮這樣做。測試可以告訴您您沒有預料到的代碼的有趣用途。您編寫了一個想要改變洗牌行為的測試。其他客戶是否有正當理由想要這樣做?可重復的研究是一回事,也許將種子作為參數畢竟是有意義的。
添加回答
舉報