【问题标题】:Django tests - patch object in all testsDjango 测试 - 在所有测试中修补对象
【发布时间】:2014-11-09 13:07:28
【问题描述】:

我需要为我的测试创建某种MockMixin。它应该包括调用外部源的所有东西的模拟。 例如,每次我在管理面板中保存模型时,我都会调用一些远程 URL。像这样嘲笑和使用它会很好:

class ExampleTestCase(MockedTestCase):
    # tests

所以每次我在管理员中保存模型时,例如在功能测试中,都会应用这个模拟而不是调用远程 URL。

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

【问题讨论】:

    标签: python django unit-testing django-testing python-mock


    【解决方案1】:

    根据mock documentation:

    Patch 可以用作 TestCase 类的装饰器。它的工作原理 装饰类中的每个测试方法。这减少了样板 当您的测试方法共享一个公共补丁集时的代码。

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

    另外,您可以分别在setUp() 和tearDown() 方法中使用start() and stop() 修补程序的方法:

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

    【讨论】:

    • 这也意味着我必须装饰我的每个测试用例,而不是 Mixin 测试类。我必须为每种测试方法添加额外的参数也很不方便。但总比没有好。
    • @galozek 请看相关话题:here 和here。
    • 第二个解决方案有效。我尝试了这种方法,但缺少 start()/stop() 部分。谢谢。
    • 请注意,您最好使用self.addCleanup(self.patcher.stop) 而不是在tearDown 中执行此操作,因为无论是否有异常都会运行清理。请参阅@Meistro 的回答。
    • 不要忘记包含导入:from unittest import TestCase, from import unittest.mock import patch
    【解决方案2】:

    只需添加到alecxe's answer,如果您使用的是teardown(),则添加according to the docs

    您必须通过调用stop 确保“撤消”修补。这可能比您想象的要复杂,因为如果在 setUp 中引发异常,则不会调用 tearDown。

    如果在您的测试中引发异常,您的补丁将不会被撤消。更好的方法是在您的setUp() 中调用addCleanup()。然后你可以完全省略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
    

    【讨论】:

    • tearDown 将始终被调用,即使您的测试引发异常。
    • 来自文档:“如果 setUp() 失败,意味着不调用 tearDown(),那么仍然会调用添加的任何清理函数。”所以调用addCleanup 比希望setUp 中没有异常更好。
    • 不要忘记包含导入:from unittest import TestCase, from import unittest.mock import patch
    • wrt 上面提到的@Meistro...确保在setUp方法中尽快添加清理钩子,否则,如果在添加钩子之前失败,它将无用
    【解决方案3】:

    我最终创建了一个测试运行器来实现我的目的。我需要模拟文件存储,以便在测试时图像实际上不会写入文件系统。在许多测试中都调用了图像对象,因此修补每个类不会是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)
    

    然后我会使用 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}')
    

    【讨论】:

      猜你喜欢
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 2013-08-15
      • 1970-01-01
      • 2019-12-26
      相关资源
      最近更新 更多