我如何为 python 中的所有测试全局模拟一个方法

How can I mock a method globally for all tests in python

我的 Django 项目在多个应用程序的各个模块中进行了数百次测试。最近我们添加了一个功能,在创建用户对象(使用 Django Signals)时发送电子邮件(通过 sendgrid)。

我们 运行 遇到的问题是,当 运行 测试时,许多用户是显式创建的或作为固定装置创建的。这导致每个测试周期发送数百封电子邮件,并且由于其中大部分无效,我们收到数百次退回邮件。除了所涉及的费用之外,Sendgrid 实际上还因为奇怪的行为而暂时暂停了我们的帐户。

显然我可以分别模拟每个测试的调用,但是这必须在数百个地方进行,我们必须记住在我们创建的所有未来测试中都这样做。

有没有更简单的方法来全局模拟所有测试的特定代码块(当然,在实际 运行 时保持完整)

下面是实现模拟的两种方法。

方法一:修改生产代码:

您可以创建一个伪包并导入它进行测试,而不是导入原始包。这种基于检查的导入可以在每个文件的开头完成。

例如:

import os
    if 'TEST' in os.environ:
        import pseudoTime as time
    else:
        import time

print time.time

方法二:不修改生产代码:

在你的测试程序中,你可以导入实用程序包(包含问题中描述的电子邮件功能的包)并覆盖实用程序功能。

例如:

考虑以下代码:

import time
def function():
    return time.time()

测试代码可以执行以下操作:

import code
import time

def helloWorld():
    return "Hello World"

print "Before changing ...", code.function()
oldTime = time.time # save
time.time = helloWorld
print "After changing ...", code.function()
time.time = oldTime # revert back

上述测试的输出是:

Before changing ... 1456487043.76
After changing ... Hello World

因此测试代码可以导入实用程序文件,覆盖它提供的功能,然后运行 对生产代码进行测试。

此方法更优越,因为它不会更改生产代码。

我不使用 Django,也许在 Django 中有一些惯用的方法可以很好地完成它。

我解决此类问题的方法是创建自己的 TestCase class,它从 unittest.TestCase 扩展并覆盖 setUpClass()/tearDownClass/setUp()/tearDown() 在我的测试中全局设置我需要的 mock/patch(或至少在其中的一部分)。

现在每次我需要它而不是导入 unittest.TestCase 模块时,我正在导入 myunittest.TestCase

示例:myunittest.py

import unittest

class TestCase(unittest.TestCase):

    @classmethod
    def setUpClass(cls):
        super(TestCase, cls).setUpClass()
        # Init your class Mock/Patch

    @classmethod
    def tearDownClass(cls):
        # Remove Mocks or clean your singletons
        super(TestCase, cls).tearDownClass()

    def setUp(self):
        super(TestCase, self).setUp()
        # Init your obj Mock/Patch

    @classmethod
    def tearDown(self):
        # ... if you need it
        super(TestCase, self).tearDown()

在你的测试中:

from myunittest import TestCase

    class Test(TestCase):
        ... Your test

我在django大项目中用过的两种方式

假设一个:my_mock = patch("myapp.mymodule.MyClass.my_method")

1) 您可以在自定义测试运行程序中添加模拟 class:

from mock import patch
from django.test.runner import DiscoverRunner

class MyTestRunner(DiscoverRunner):
    @my_mock
    def run_tests(self, test_labels, **kwargs):
         return super(MyTestRunner, self).run_tests(test_labels, **kwargs)

2) 您可以在自定义基础测试上添加模拟 class:

from mock import patch
from django.test import TestCase

class MyTestCase(TestCase):

   def setUp(self):
      super(MyTestCase, self).setUp()
      my_mock.start()

   def tearDown(self):
      super(MyTestCase, self).tearDown()
      my_mock.stop()