【问题标题】:Why does mock patch only work when running specific test and not whole test suite?为什么模拟补丁仅在运行特定测试而不是整个测试套件时才有效?
【发布时间】:2021-03-25 06:28:40
【问题描述】:

我专门使用 Django 和 Pytest 来运行测试套件,并尝试测试当用户访问网站时特定表单是否显示预期数据(集成测试)。

这个特定的视图使用了一个存储过程,我正在嘲笑它,因为测试永远无法访问它。

我的测试代码如下所示:

#test_integrations.py

from my_app.tests.data_setup import setup_data, setup_sb7_data
from unittest.mock import patch

...

# Setup to use a non-headless browser so we can see whats happening for debugging
@pytest.mark.usefixtures("standard_browser")
class SeniorPageTestCase(StaticLiveServerTestCase):
    """
    These tests surround the senior form
    """

    @classmethod
    def setUpClass(cls):
        cls.host = socket.gethostbyname(socket.gethostname())
        super(SeniorPageTestCase, cls).setUpClass()

    def setUp(self):
        # setup the dummy data - this works fine
        basic_setup(self)
        # setup the 'results'
        self.sb7_mock_data = setup_sb7_data(self)

    @patch("my_app.utils.get_employee_sb7_data")
    def test_senior_form_displays(self, mock_sb7_get):
        # login the dummy user we created
        login_user(self, "futureuser")
        # setup the results
        mock_sb7_get.return_value = self.sb7_mock_data
        # hit the page for the form
        self.browser.get(self.live_server_url + "/my_app/senior")
        form_id = "SeniorForm"
        # assert that the form displays on the page
        self.assertTrue(self.browser.find_element_by_id(form_id))
# utils.py

from django.conf import settings
from django.db import connections


def get_employee_sb7_data(db_name, user_number, window):
    """
    Executes the stored procedure for getting employee data

    Args:
        user_number: Takes the user_number
        db (db connection): Takes a string of the DB to connect to

    Returns:

    """
    cursor = connections[db_name].cursor()
    cursor.execute(
        'exec sp_sb7 %s, "%s"' % (user_number, window.senior_close)
    )
    columns = [col[0] for col in cursor.description]
    results = [dict(zip(columns, row)) for row in cursor.fetchall()]
    return results
# views.py

from myapp.utils import (
    get_employee_sb7_data,
)

...

###### Senior ######
@login_required
@group_required("user_senior")
def senior(request):

    # Additional Logic / Getting Other Models here

    # Execute stored procedure to get data for user
    user_number = request.user.user_no
    results = get_employee_sb7_data("production_db", user_number, window)
    if not results:
        return render(request, "users/senior_not_required.html")

    # Additional view stuff

    return render(
        request,
        "users/senior.html",
        {
            "data": data,
            "form": form,
            "results": results,
        },
    )

如果我自己运行这个测试:

pytest my_app/tests/test_integrations.py::SeniorPageTestCase

测试通过没有问题。浏览器出现了 - 表单显示了我们所期望的虚拟数据,并且一切正常。

但是,如果我运行:

pytest my_app

所有其他测试都运行并通过 - 但此类中的所有测试都失败了,因为它没有修补函数。

它尝试调用实际的存储过程(由于它还没有在生产服务器上而失败)并且它失败了。

为什么当我专门调用该 TestCase 时它会正确修补 - 但当我只是在应用程序或项目级别上运行 pytest 时无法正确修补?

我很茫然,不知道如何很好地调试它。任何帮助表示赞赏

【问题讨论】:

  • 你确定是这个原因吗? @pytest.mark.usefixtures("standard_browser") 是一个类属性。所以它被所有测试重用。这会导致同样的症状吗?这同样适用于没有进行任何必要清理的 tearDown() 的 setUp()。
  • 测试在单独运行时有效——只是在应用程序或项目级别运行时无效。该夹具只是打开一个浏览器而没有做任何其他事情 - 测试完成时关闭 - 所以我认为这不会产生任何影响。它在自行测试时起作用。此外,setUp() 设置数据 - Django 在测试之间自动截断 - 所以没有什么需要清理的。日志中的错误特别表明它没有运行该夹具,而是尝试运行存储过程 - 因此在运行整个套件时没有应用补丁。
  • 当您运行完整套件时,此方法是否从其他地方导入? Reason
  • 我不知道,除非 pytest 在运行完整套件时做一些不同的事情,而不是特定类别的测试(我不相信它会这样做);我已将实用程序函数和调用该函数的视图添加到问题中,以便更容易理解正在发生的事情。
  • 嗯,它告诉我的是有 2 个不同的符号:get_employee_sb7_datamy_app.utils.get_employee_sb7_data。他们来自同一个地方,但名字不同。所以不同之处在于views.pytest_integrations.py 之前被导入,并且不知何故仍在范围内,但是当单独运行时,它首先导入测试用例(它确实如此)。测试理论的方法是在测试用例中添加 views.py 作为导入,然后两者都应该表现出相同(不工作)的行为。

标签: django unit-testing mocking pytest liveservertestcase


【解决方案1】:

所以发生的情况是,您的视图是在修补之前导入的。

让我们先看看工作案例:

  1. pytest 导入 test_integrations 文件
  2. 执行测试并运行补丁装饰器的内部函数
  3. 还没有导入 utils,所以补丁导入并替换了函数
  4. 执行测试体,将url传递给测试客户端
  5. 测试客户端导入解析器,然后导入视图,视图导入实用程序。
  6. 由于 utils 已经打过补丁,所以一切正常

如果另一个测试用例首先运行,它也导入相同的视图,那么该导入胜出,补丁不能替代导入。

您的解决方案是引用相同的符号。所以在test_integrations.py:

@patch("myapp.views.get_employee_sb7_data")

【讨论】:

  • 我按照您的建议在文件顶部添加了导入 (test_integrations.py),然后将该测试类的函数上的装饰器更改为 @patch("get_employee_sb7_data") 但收到错误:@987654324 @
  • 我的错。更新。该符号作为绝对导入存在,但作为“名称”而不是目标存在。
  • 感谢您的精彩解释以及对需要发生的事情的更正。我现在对正在发生的事情有了更深入的了解(尽管 Mocks 仍然让我有些困惑 - 文档看起来像泥巴一样清晰!哈!)。我很感激!
猜你喜欢
  • 1970-01-01
  • 2015-06-29
  • 2017-03-28
  • 2020-05-14
  • 2017-11-15
  • 1970-01-01
  • 2018-06-25
  • 1970-01-01
  • 2011-04-14
相关资源
最近更新 更多