这个Python mock patch有什么问题?

7

我在单元测试中遇到了模拟导入模块的问题。我正在尝试使用mock模块来模拟tracker.models模块中的PIL Image类。我知道你应该在使用它们的地方模拟它们,所以我编写了@mock.patch('tracker.models.Image')作为我的单元测试装饰器。我正在尝试检查下载的图像是否被打开为PIL Image。模拟补丁似乎覆盖了整个Image模块。当我运行测试时,会出现以下错误:

File "/home/ubuntu/workspace/tracker/models.py", line 40, in set_photo
    width, height = image.size
ValueError: need more than 0 values to unpack

这是我的单元测试:
测试模型文件:test_models.py
@responses.activate
@mock.patch('tracker.models.Image')
def test_set_photo(self, mock_pil_image):
    # Initialize data
    hammer = Product.objects.get(name="Hammer")
    fake_url = 'http://www.example.com/prod.jpeg'
    fake_destination = 'Hammer.jpeg'

    # Mock successful image download using sample image. (This works fine)
    with open('tracker/tests/test_data/small_pic.jpeg', 'r') as pic:
        sample_pic_content = pic.read()
    responses.add(responses.GET, fake_url, body=sample_pic_content, status=200, content_type='image/jpeg')

    # Run the actual method
    hammer.set_photo(fake_url, fake_destination)

    # Check that it was opened as a PIL Image
    self.assertTrue(mock_pil_image.open.called,
                    "Failed to open the downloaded file as a PIL image.")

这里是被测试的代码片段。
tracker/models.py
class Product(models.Model):
    def set_photo(self, url, filename):
        image_request_result = requests.get(url)
        image_request_result.content
        image = Image.open(StringIO(image_request_result.content))

        # Shrink photo if needed
        width, height = image.size  # Unit test fails here
        max_size = [MAX_IMAGE_SIZE, MAX_IMAGE_SIZE]
        if width > MAX_IMAGE_SIZE or height > MAX_IMAGE_SIZE:
            image.thumbnail(max_size)
        image_io = StringIO()
        image.save(image_io, format='JPEG')
        self.photo.save(filename, ContentFile(image_io.getvalue()))

image.size 必须是一个元组,但你没有为该元组设置任何模拟值。因此 image.size 返回一个模拟对象。 - Martijn Pieters
@MartijnPieters 我该如何防止我的模拟覆盖默认功能?我想有选择地覆盖函数并检查.called而不是为整个模块批量执行。 - Stewart
为什么你需要那个?你不是在测试PIL模块是否正常工作,而只是检查你的代码是否调用了正确的函数并传入了正确的信息。 - Martijn Pieters
2个回答

7
你需要配置Image.open的返回值,使其包含一个size属性:
opened_image = mock_pil_image.open.return_value
opened_image.size = (42, 83)

现在,当您的测试函数调用Image.open时,返回的MagicMock实例将具有一个元组形式的size属性。您可以对需要返回值的任何其他方法或属性执行相同的操作。
然后,opened_image引用对于测试函数的其他方面也非常有用;现在,您可以断言已调用image.thumbnailimage.save
opened_image = mock_pil_image.open.return_value
opened_image.size = (42, 83)

# Run the actual method
hammer.set_photo(fake_url, fake_destination)

# Check that it was opened as a PIL Image
self.assertTrue(mock_pil_image.open.called,
                "Failed to open the downloaded file as a PIL image.")

self.assertTrue(opened_image.thumbnail.called)
self.assertTrue(opened_image.save.called)

这样做可以非常准确地测试您的缩略图大小逻辑是否正确,例如,无需测试PIL是否执行其任务; 毕竟,这里并没有测试PIL。

0
我正在编写一个类似的测试,但我的函数使用Image.open作为上下文管理器(with Image.open(<filepath>) as img:)。感谢Martijn Pieters的答案和this one,我能够让我的测试工作:

mock_pil_image.open.return_value.__enter__.return_value.size = (42, 83)

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接