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参数)。可以在以下链接下找到解释:
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())将改组隐藏在其中作为实现细节并与其他行为耦合,从调用者的角度来看,这里没有什么可以隔离的。现在测试说不,我想打电话create_dataset()但分开洗牌行为,没有明显的方法可以做到这一点,这就是你来这里问问题的原因。
我宁愿通过让代码和测试就应该将哪些行为相互解耦达成一致,从而省去这些麻烦。
我宁愿不因为测试而改变我的代码
也许你应该考虑这样做。测试可以告诉您您没有预料到的代码的有趣用途。您编写了一个想要改变洗牌行为的测试。其他客户是否有正当理由想要这样做?可重复的研究是一回事,也许将种子作为参数毕竟是有意义的。
添加回答
举报
