在 Python 中,如何从另一个未在本地导入的文件中修补函数? [英] In Python, how can I patch a function from another file that's not imported locally?

查看:47
本文介绍了在 Python 中,如何从另一个未在本地导入的文件中修补函数?的处理方法,对大家解决问题具有一定的参考价值,需要的朋友们下面随着小编来一起学习吧!

问题描述

我正在学习 Pythonic 测试开发,并偶然发现了这个看似违反直觉的问题.当我修补与被测代码在同一文件中定义的函数时,patch 可以正常工作.但是当我从不同的文件import一个函数时,让patch正常工作的唯一方法是使import本地化而不是定义它位于文件顶部.

I'm working on learning Pythonic test development, and stumbled across this seemingly counterintuitive issue. When I'm patching a function that is defined in the same file as the code under test, the patch works correctly. But when I import a function from a different file, the only way to get the patch working correctly is to make the import local instead of defining it at the top of the file.

最小复制:

a/b.py:

from x.y import z


def c():
    print("In a.b.c")


class D:
    def do_stuff_with_a_b_c(self):
        print("In do_stuff_with_a_b_c")
        c()

    def do_stuff_with_x_y_z(self):
        from x.y import z
        print("In do_stuff_with_x_y_z")
        z()

x/y.py:

def z():
    print("In x.y.z")

tests/d_tests.py:

tests/d_tests.py:

import inspect
import unittest
from unittest.mock import patch

from x import y
from a.b import D


class DTests(unittest.TestCase):
    def test_do_stuff_with_a_b_c(self):
        print(f"In {inspect.stack()[0][3]}")
        D().do_stuff_with_a_b_c()

    @patch("a.b.c")
    def test_do_stuff_with_patched_a_b_c(self, a_b_c_method):
        print(f"In {inspect.stack()[0][3]}")
        D().do_stuff_with_a_b_c()

    def test_do_stuff_with_x_y_z(self):
        print(f"In {inspect.stack()[0][3]}")
        D().do_stuff_with_x_y_z()

    @patch("x.y.z")
    def test_do_stuff_with_patched_x_y_z(self, x_y_z_method):
        print(f"In {inspect.stack()[0][3]}")
        D().do_stuff_with_x_y_z()

    def test_do_stuff_with_patch_object_x_y_z(self):
        print(f"In {inspect.stack()[0][3]}")
        with patch.object(y, "z"):
            D().do_stuff_with_x_y_z()


if __name__ == '__main__':
    unittest.main()

当我使用上述代码运行测试时,我得到以下输出:

When I run the tests with the code as above, I get the following output:

In test_do_stuff_with_a_b_c
In do_stuff_with_a_b_c
In a.b.c
In test_do_stuff_with_patch_object_x_y_z
In do_stuff_with_x_y_z
In test_do_stuff_with_patched_a_b_c
In do_stuff_with_a_b_c
In test_do_stuff_with_patched_x_y_z
In do_stuff_with_x_y_z
In test_do_stuff_with_x_y_z
In do_stuff_with_x_y_z
In x.y.z

但是,当我在 do_stuff_with_x_y_z 中注释掉 x.y.z 的本地导入时,我得到以下输出:

However, when I comment out the local import of x.y.z in do_stuff_with_x_y_z, I get the following output:

In test_do_stuff_with_a_b_c
In do_stuff_with_a_b_c
In a.b.c
In test_do_stuff_with_patch_object_x_y_z
In do_stuff_with_x_y_z
In x.y.z
In test_do_stuff_with_patched_a_b_c
In do_stuff_with_a_b_c
In test_do_stuff_with_patched_x_y_z
In do_stuff_with_x_y_z
In x.y.z

导致 patch 在一种情况下按预期工作但在另一种情况下不能正常工作的两种形式之间有什么区别?

What is the the difference between the two forms that causes patch to work as expected in one scenario but not the other?

推荐答案

修补时必须以不同方式处理这两种情况 - 顶级导入与本地导入.您必须修补模块中使用的对象,如 中所述文档,或在 Ned Batchelder 的这篇博文中,他描述了更详细的问题.

The two cases - top-level versus local import - have to be handled differently while patching. You have to patch an object as used in the module, as described in the documentation, or in this blog post by Ned Batchelder, who describes the problem in more detail.

在您的第一种情况下,您在运行时导入模块 z 之后它已被修补,因此已修补的模块被导入.在您的第二种情况(顶级导入的更标准情况)中,您在修补模块之前导入模块 ,现在可以引用模块中未修补的 zb,所以为了使补丁生效,你必须给这个参考打补丁:

In your first case, you import the module z at runtime after it has been patched, so the patched module is imported. In your second case (the more standard case of top-level import) you import the module before it is patched, and have now a reference to the unpatched z in your module b, so for the patching to work you have to patch this reference:

@patch('a.b.z')
def test_do_stuff_with_patched_x_y_z(self, mocked_z):
   ... 

总结一下:你总是要检查要使用的对象是如何导入到要测试的模块中的.基本上有两种情况:

To sum this up: you always have to check how the object to use is imported in the module to be tested. There are basically two cases:

  • 对象像 import ximport xy 一样导入(并且像 xyz 一样使用) - 在这种情况下,它可以被修补为 <代码>xyz.如果您在函数内部本地导入模块,情况也是如此,就像在初始情况下一样.
  • 对象像from xy import z(或from .y import z)一样导入——在这种情况下,会创建一个引用该对象的本地引用,并且应针对该参考进行修补(例如 abz 在您的情况下)
  • the object is imported like import x or import x.y (and used like x.y.z) - in this case it can be patched as x.y.z. The same is true if you import the module locally inside a function, as in your initial case.
  • the object is imported like from x.y import z (or from .y import z) - in this case a local reference is created that refers to the object, and the patching shall be done for that reference (e.g. a.b.z in your case)

这篇关于在 Python 中,如何从另一个未在本地导入的文件中修补函数?的文章就介绍到这了,希望我们推荐的答案对大家有所帮助,也希望大家多多支持IT屋!

查看全文
登录 关闭
扫码关注1秒登录
发送“验证码”获取 | 15天全站免登陆