Django测试 - 在所有测试中修补对象

tun*_*rob 25 python django unit-testing django-testing python-mock

我需要MockMixin为我的测试创建一些.它应该包括调用外部源的所有内容的模拟.例如,每次我在管理面板中保存模型时,我都会调用一些远程URL.如果嘲笑并使用那样的话会很好:

class ExampleTestCase(MockedTestCase):
    # tests
Run Code Online (Sandbox Code Playgroud)

因此,每次我在管理员中保存模型时,例如在功能测试中,应用此模拟而不是调用远程URL.

这有可能吗?我能够为1个特定测试做到这一点,这不是问题.但是有一些全局模拟更有用,因为我经常使用它.

ale*_*cxe 41

根据mock文件:

Patch可以用作TestCase类装饰器.它通过装饰类中的每个测试方法来工作.当您的测试方法共享一个公共的补丁集时,这会减少样板代码.

这基本上意味着您可以创建一个基础测试类,其上@patch应用了装饰器,可以模拟外部调用,同时执行内部的每个测试方法.

此外,您可以分别使用start()stop() patcher的方法setUp()tearDown()方法:

class BaseTestCase(TestCase):
    def setUp(self):
        self.patcher = patch('mymodule.foo')
        self.mock_foo = self.patcher.start()

    def tearDown(self):
        self.patcher.stop()
Run Code Online (Sandbox Code Playgroud)

  • 这也意味着我必须装饰我的每个测试用例,而不是```Mixin```测试类.我必须为每种测试方法添加额外的参数,这也很不方便.但它总比没有好. (2认同)
  • 注意,最好使用`self.addCleanup(self.patcher.stop)`而不是在`tearDown`中这样做,因为不管是否有异常,清理都会运行。请参阅@Meistro的答案。 (2认同)

Mei*_*tro 22

只是为了增加alecxe的答案,如果你正在使用teardown()那么根据文档

您必须通过调用确保修补"撤消" stop.这可能比您想象的更为繁琐,因为如果在setUp那时引发异常tearDown则不会被调用.

如果您的测试中出现异常,则您的修补程序将无法撤消.更好的方法是打电话给addCleanup()setUp().然后你可以tearDown()完全省略这个方法.

class BaseTestCase(TestCase):
    def setUp(self):
        self.patcher = patch('mymodule.foo')
        self.mock_foo = self.patcher.start()
        self.addCleanup(self.patcher.stop) # add this line
Run Code Online (Sandbox Code Playgroud)

  • 从文档:"如果setUp()失败,意味着没有调用tearDown(),那么仍然会调用添加的任何清理函数." 所以最好调用`addCleanup`而不是希望`setUp`中不会引发异常. (6认同)
  • 不要忘了包括导入:from unittest import TestCase,from import unittest.mock import patch。 (2认同)

gia*_*tas 6

我最终创建了一个测试运行程序来实现我的目的。我需要模拟文件存储,以便在测试时图像不会实际写入文件系统。在许多测试中都会调用图像对象,因此不会修补每个类DRY。另外,我注意到模拟文件本身会将其保留在系统上,以防测试失败。但这个方法却没有。

runner.py我在项目根目录中创建了一个文件

# runner.py
from unittest.mock import patch

from django.test.runner import DiscoverRunner

from myapp.factories import ImageFactory


class UnitTestRunner(DiscoverRunner):

    @patch('django.core.files.storage.FileSystemStorage.save')
    def run_tests(self, test_labels, mock_save, extra_tests=None, **kwargs):
        mock_save.return_value = ImageFactory.get_image()
        return super().run_tests(test_labels, extra_tests=None, **kwargs)
Run Code Online (Sandbox Code Playgroud)

然后我会使用运行我的测试python manage.py tests --testrunner=runner.UnitTestRunner


为了清楚起见,该ImageFactory.get_image方法是自定义方法

from django.core.files.base import ContentFile
from factory.django import DjangoModelFactory
from io import BytesIO
from PIL import Image as PilImage
from random import randint

class ImageFactory(DjangoModelFactory):

    @classmethod
    def get_image(cls, name='trial', extension='png', size=None):
        if size is None:
            width = randint(20, 1000)
            height = randint(20, 1000)
            size = (width, height)

        color = (256, 0, 0)

        file_obj = BytesIO()
        image = PilImage.new("RGBA", size=size, color=color)
        image.save(file_obj, extension)
        file_obj.seek(0)
        return ContentFile(file_obj.read(), f'{name}.{extension}')
Run Code Online (Sandbox Code Playgroud)