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

我正在使用一个类,该类具有一个方法,该方法shuffle返回调用它的实例的洗牌版本。这是:


shuffled_object = unshuffled_object.shuffle(buffer_size)


我想模拟这个方法,这样当它被调用时,它会简单地返回自身,而不需要任何改组。以下将是这种情况的简化:


# my_test.py

class Test():


    def shuffle(self, buffer_size):

        return self

# test_mock

import unittest

import unittest.mock as mk


import my_test


def mock_test(self, buffer_size):

    return self


class 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.py

def 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.py

def shuffle(self, buffer_size):

    return self


with 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创建的任何子类型中抽象出自己。


慕容森
浏览 136回答 3
3回答

侃侃尔雅

为什么你没有 self 参数声明一个类时,function您定义的内容与method您的实例中的一样。这是它的一个实例:>>> def function():...&nbsp; &nbsp; &nbsp;pass...&nbsp;>>> type(function)<class 'function'>>>> class A:...&nbsp; &nbsp; &nbsp;def b(self):...&nbsp; &nbsp; &nbsp; &nbsp; &nbsp;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 unittestimport unittest.mock as mkimport my_testimport anotherclass TestMocked(my_test.Test):&nbsp; &nbsp; def shuffle(self, buffer_size):&nbsp; &nbsp; &nbsp; &nbsp; return self@mk.patch("my_test.Test", TestMocked)# Uncomment to mock the other file behavior# @mk.patch("another.Test", TestMocked)def test_mock():&nbsp; &nbsp; test_class = my_test.Test()&nbsp; &nbsp; shuffled_test = test_class.shuffle(2)&nbsp; &nbsp; print(my_test.Test.shuffle)&nbsp; &nbsp; # This is another file using your class,&nbsp; &nbsp; # You will have to mock it too in order to see the mocked behavior&nbsp; &nbsp; print(another.Test.shuffle)&nbsp;&nbsp; &nbsp; 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.pyimport tensorflow as tfdef data_generator():&nbsp; &nbsp; for i in itertools.count(1):&nbsp; &nbsp; &nbsp; &nbsp; yield (i, [1] * i)def create_dataset():&nbsp; &nbsp; _load_example = lambda x, y: x+y&nbsp; &nbsp; buffer_size = 3&nbsp; &nbsp; output_types = (tf.int64, tf.int64)&nbsp; &nbsp; output_shapes = (tf.TensorShape([]), tf.TensorShape([None]))&nbsp; &nbsp; raw_dataset = tf.data.Dataset.from_generator(data_generator, output_types, output_shapes)&nbsp; &nbsp; shuffled_dataset = tf.data.Dataset.shuffle(raw_dataset, buffer_size)&nbsp; &nbsp; assert raw_dataset == shuffled_dataset&nbsp; &nbsp; assert raw_dataset is shuffled_dataset&nbsp; &nbsp; dataset = shuffled_dataset.map(_load_example)&nbsp; &nbsp; return dataset# test_mock.pyimport unittest.mock as mkimport my_inputdef shuffle(self, buffer_size):&nbsp;&nbsp; &nbsp; print("Shuffle! {}, {}".format(self, buffer_size))&nbsp; &nbsp; return selfwith mk.patch('my_input.tf.data.Dataset.shuffle') as shuffle_mock:&nbsp; &nbsp; shuffle_mock.side_effect = shuffle&nbsp; &nbsp; dataset = my_input.create_dataset()运行时,您将获得以下输出:$ python test_mock.pyShuffle! (<DatasetV1Adapter shapes: ((), (?,)), types: (tf.int64, tf.int64)>, 3)将方法使用包装在一个函数中这几乎与之前的答案一样,但是您可以将其包装如下,而不是从类中调用该方法:# my_input.pyimport tensorflow as tfdef data_generator():&nbsp; &nbsp; for i in itertools.count(1):&nbsp; &nbsp; &nbsp; &nbsp; yield (i, [1] * i)def shuffle(instance, buffer_size):&nbsp; &nbsp; return instance.shuffle(buffer_size)def create_dataset():&nbsp; &nbsp; _load_example = lambda x, y: x+y&nbsp; &nbsp; buffer_size = 3&nbsp; &nbsp; output_types = (tf.int64, tf.int64)&nbsp; &nbsp; output_shapes = (tf.TensorShape([]), tf.TensorShape([None]))&nbsp; &nbsp; raw_dataset = tf.data.Dataset.from_generator(data_generator, output_types, output_shapes)&nbsp; &nbsp; shuffled_dataset = tf.data.Dataset.shuffle(raw_dataset, buffer_size)&nbsp; &nbsp; assert raw_dataset == shuffled_dataset&nbsp; &nbsp; assert raw_dataset is shuffled_dataset&nbsp; &nbsp; dataset = shuffled_dataset.map(_load_example)&nbsp; &nbsp; return dataset# test_mock.pyimport unittest.mock as mkimport my_inputdef shuffle(self, buffer_size):&nbsp;&nbsp; &nbsp; print("Shuffle! {}, {}".format(self, buffer_size))&nbsp; &nbsp; return selfwith mk.patch('my_input.shuffle') as shuffle_mock:&nbsp; &nbsp; shuffle_mock.side_effect = shuffle&nbsp; &nbsp; dataset = my_input.create_dataset()

莫回无

我想我已经找到了解决问题的合理方法。我没有尝试修补 的shuffle方法tf.data.Dataset,而是认为如果我可以访问它,我可以直接在要测试的实例上更改它。因此,我尝试修补创建实例的方法tf.data.Dataset.from_generator,以便它调用原始方法,但在返回新创建的实例之前,它用shuffle另一个简单地返回未更改数据集的方法替换它的方法。代码如下:from_generator_old = tf.data.Dataset.from_generatordef from_generator_new(generator, output_types, output_shapes=None, args=None):&nbsp; &nbsp; dataset = from_generator_old(generator, output_types, output_shapes, args)&nbsp; &nbsp; dataset.shuffle = lambda *args, **kwargs: dataset&nbsp; &nbsp; return datasetfrom data_input.kitti.kitti_input import tf as tf_mockwith mk.patch.object(tf_mock.data.Dataset, 'from_generator', from_generator_new):&nbsp; &nbsp; 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每次在这个框架下测试的结果都是一样的。然而,这似乎根本没有记录在案,所以我不确定盲目依赖它是否是个好主意。

暮色呼如

你在评论中说我想检查dataset在迭代它时是否返回正确的元素”。客户create_dataset()不希望元素以任何特定的顺序排列,只要所有预期的元素和只有预期的元素都在那里,无论顺序是什么,它们都会很好。所以这就是测试应该检查的内容。def&nbsp;test_create_dataset(): &nbsp;&nbsp;&nbsp;&nbsp;dataset&nbsp;=&nbsp;create_dataset() &nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;assert&nbsp;sorted(dataset)&nbsp;==&nbsp;sorted(expected_elements)根据迭代数据集时返回的值的类型,断言可能需要更复杂。例如,如果元素是numpy数组或pandas.Series.&nbsp;在这种情况下,您将需要使用自定义密钥。这适用于numpy和pandas对象:sorted(dataset,&nbsp;key=list)或者你可以使用set或collections.Counter...现在解决评论中表达的一些担忧:如果你的意思是shuffle功能是的,测试想要改变实现.shuffle()并且代码试图隐藏它。这使得测试难以编写(这就是为什么你必须首先来这里提出问题)并且很可能难以理解代码的未来维护者(可能包括你未来的自己)。我宁愿尽量避免它。正如我在上面的评论中所说,我认为应该替换它以使测试更加健壮/有意义。作为create_dataset()我不知道的用户,我不关心洗牌。这对我来说毫无意义。我调用函数的方式没有类似的东西,它只是一个实现细节。让您的测试担心这一点会使测试变得脆弱,而不是更健壮。如果您将实现更改为不打乱数据,或者在不调用的情况下打乱数据Dataset.shuffle(),我仍然会得到正确的数据,但测试会失败。这是为什么?因为它正在检查我不关心的东西。我也会尽量避免这种情况。毕竟,这不就是嘲讽的全部目的吗?使某些模块的结果可预测,以隔离您实际想要测试的代码的影响?是的。嗯,或多或少。但是您要测试的代码(函数create_dataset())将改组隐藏在其中作为实现细节并与其他行为耦合,从调用者的角度来看,这里没有什么可以隔离的。现在测试说不,我想打电话create_dataset()但分开洗牌行为,没有明显的方法可以做到这一点,这就是你来这里问问题的原因。我宁愿通过让代码和测试就应该将哪些行为相互解耦达成一致,从而省去这些麻烦。我宁愿不因为测试而改变我的代码也许你应该考虑这样做。测试可以告诉您您没有预料到的代码的有趣用途。您编写了一个想要改变洗牌行为的测试。其他客户是否有正当理由想要这样做?可重复的研究是一回事,也许将种子作为参数毕竟是有意义的。
打开App,查看更多内容
随时随地看视频慕课网APP

相关分类

Python