Python unittest在引发异常时撤销补丁(patch)。

huangapple go评论72阅读模式
英文:

Python unittest undo patch when exception is raised

问题

这是一个有着完全虚构类的复杂问题,但思路是一样的。

在这里,我需要打开一个空白文件,但如果这个方法返回FileExistsError,我会删除那个文件然后再次调用open。如果我想测试openBlankFile方法在文件存在的情况下,我需要模拟掉'open'方法调用,并将副作用设置为FileNotFound异常。但是,当你这样做时,第二次调用'open'方法时,显然也被模拟了。你如何处理这种类型的问题?

文件 mockPatch.py

from unittest import TestCase
from unittest.mock import patch
from os import remove

class WriteModule(object):
    def openBlankFile(self):
        try:
            with open("Foo.txt", "x"):
                print("created")
        except FileExistsError:
            remove("Foo.txt")
            open("Foo.txt", "x")
            print("removed and created")

class testWriteModule(TestCase):
    @patch("mockPatch.open", side_effect=FileExistsError)
    @patch("mockPatch.remove")
    def testWriteModuleOpen(self, mock_remove, mock_open):
        WriteModule().openBlankFile()

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

# EOF

运行:

python -m unittest mockPatch

运行这个代码似乎没有模拟FileExistsError。

错误输出

这段代码有什么问题?

英文:

This is a difficult one with a complete fake class, but the idea is the same.

Here I need to open a blank file, but if this method returns FileExistsError, I delete that file and call open again. If I want to test the openBlankFile method for situations where the file exists, I need to patch out the 'open' method call and set side-effect to FileNotFound exception. But, when you do this, the second time the 'open' method is called, this one is obviously also patched. How do you go about this type of issue?

File mockPatch.py

from unittest import TestCase
from unittest.mock import patch
from os import remove

class WriteModule(object):
    def openBlankFile(self):
        try:
            with open("Foo.txt", "x"):
            print("created")
        except FileExistsError:
            remove("Foo.txt")
            open("Foo.txt", "x")
            print("removed and created")

class testWriteModule(TestCase):
    @patch("mockPatch.open", side_effect=FileExistsError)
    @patch("mockPatch.remove")
    def testWriteModuleOpen(self, mock_remove, mock_open):
        WriteModule().openBlankFile()

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

# EOF

Run:

python -m unittest mockPatch

Running this does not seem to mock either FileExistsError.

Error output

What is up with this piece of code?

答案1

得分: 2

当你模拟open函数时,你可以将side_effect设置为一个列表,其中包含要引发的初始异常和第二次调用时要返回的“真实”文件对象。例如,

class testWriteModule(TestCase):
    @patch("mockPatch.open", side_effect=[FileExistsError, io.StringIO()])
    @patch("mockPatch.remove")
    def testWriteModuleOpen(self, mock_remove, mock_open):
        WriteModule().openBlankFile()

关于Mock文档在这一点上有一些不太清楚的地方:

如果side_effect是一个可迭代对象,那么对模拟对象的每次调用都将返回可迭代对象中的下一个值。

但是,如果可迭代对象的元素实际上是异常类型或异常类型的实例,那么会引发异常,而不是返回元素本身。我认为前面那句话的意图

或者side_effect可以是异常类或实例。在这种情况下,当调用模拟对象时将引发异常。

这意味着无论异常是独立的还是作为可迭代对象的一部分,都会通过引发异常来“调用”,但至少我需要测试一下来确认这一点。

英文:

When you mock open, you can set side_effect to a list that contains both the initial exception to raise and the "real" file object for the second call to return. For example,

class testWriteModule(TestCase):
    @patch("mockPatch.open", side_effect=[FileExistsError, io.StringIO()])
    @patch("mockPatch.remove")
    def testWriteModuleOpen(self, mock_remove, mock_open):
        WriteModule().openBlankFile()

The documentation for Mock is fuzzy on this point:

> If side_effect is an iterable then each call to the mock will return the next value from the iterable.

But if an element of the iterable is in fact an exception type or an instance of an exception type, an exception is raised rather than the element being returned. I think the intent of the previous sentence

> Alternatively side_effect can be an exception class or instance. In this case the exception will be raised when the mock is called.

is to imply that an exception is "called" by being raised, whether it is standalone or part of an iterable, but I at least needed to test this to confirm.

答案2

得分: 0

你是正确的,由于修补装饰器的使用方式,WriteModule类中对open方法的第二次调用也被修补了。为了避免多次修补open方法,我们可以修改测试用例,使用一个修补来处理FileExistsError并移除副作用。

下面是如何实现的:

from unittest import TestCase
from unittest.mock import patch
from os import remove

class WriteModule(object):
    def openBlankFile(self):
        try:
            with open("Foo.txt", "x"):
                pass
        except FileExistsError:
            remove("Foo.txt")
            with open("Foo.txt", "x"):
                pass

class testWriteModule(TestCase):
    @patch("mockPatch.open", side_effect=[FileExistsError, None])
    @patch("mockPatch.remove")
    def testWriteModuleOpen(self, mock_remove, mock_open):
        WriteModule().openBlankFile()
        mock_remove.assert_called_once_with("Foo.txt")
        mock_open.assert_called_with("Foo.txt", "x")

if __name__ == "__main__":
    unittest.main()
英文:

You are correct that the second call to the open method in the WriteModule class is also patched due to the way the patch decorators are used. To avoid patching the open method multiple times, we can modify the test case to use a single patch for both FileExistsError and remove side-effects.

Here's how you can achieve that:

from unittest import TestCase
from unittest.mock import patch
from os import remove

class WriteModule(object):
    def openBlankFile(self):
        try:
            with open("Foo.txt", "x"):
                pass
        except FileExistsError:
            remove("Foo.txt")
            with open("Foo.txt", "x"):
                pass

class testWriteModule(TestCase):
    @patch("mockPatch.open", side_effect=[FileExistsError, None])
    @patch("mockPatch.remove")
    def testWriteModuleOpen(self, mock_remove, mock_open):
        WriteModule().openBlankFile()
        mock_remove.assert_called_once_with("Foo.txt")
        mock_open.assert_called_with("Foo.txt", "x")

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

答案3

得分: -1

在提供的代码中,问题出在 openBlankFile 方法如何处理异常以及使用 open 函数的方式上。此外,测试用例中对 open 函数的修补没有正确执行。让我们逐步解决这些问题:

  1. 修复 openBlankFile 方法:
    openBlankFile 方法不应该使用内置的 open 函数进行文件处理,因为这会干扰测试用例中的修补过程。相反,最好使用 os 模块的文件处理功能,这将允许我们独立地对这些函数进行修补。

这是修改后的 openBlankFile 版本:

import os

class WriteModule(object):
    def openBlankFile(self):
        try:
            with open("Foo.txt", "x"):
                pass
        except FileExistsError:
            os.remove("Foo.txt")
            with open("Foo.txt", "x"):
                pass
  1. 修改测试用例:
    openBlankFile 方法需要修改以使用 os 模块的函数,如上所述。
    我们需要在测试用例中修补 os.open 和 os.remove 函数,而不是内置的 open 函数。

这是修改后的测试用例:

from unittest import TestCase
from unittest.mock import patch
from os import remove

class WriteModule(object):
    def openBlankFile(self):
        try:
            with open("Foo.txt", "x"):
                pass
        except FileExistsError:
            remove("Foo.txt")
            with open("Foo.txt", "x"):
                pass

class testWriteModule(TestCase):
    @patch("mockPatch.os.remove")
    @patch("mockPatch.os.open", side_effect=FileExistsError)
    def testWriteModuleOpen(self, mock_open, mock_remove):
        WriteModule().openBlankFile()

通过在方法和测试用例中都使用 os.open 和 os.remove,我们可以成功修补 os.open 函数以引发 FileExistsError 异常,测试用例将正确处理它。

经过这些修改,测试用例应该按预期工作,您应该能够验证 openBlankFile 方法在文件存在时的行为。

英文:

In the provided code, the issue lies in the way the openBlankFile method is handling the exceptions and using the open function. Additionally, the patching of the open function is not being done correctly in the test case. Let's address these issues step by step:

  1. Fixing openBlankFile method:
    The openBlankFile method should not use the built-in open function for file handling since it interferes with the patching process in the test case. Instead, it's better to use the os module's functions for file handling, which will allow us to patch those functions independently.

Here's the modified version of openBlankFile:

import os

class WriteModule(object):
    def openBlankFile(self):
        try:
            with open("Foo.txt", "x"):
                pass
        except FileExistsError:
            os.remove("Foo.txt")
            with open("Foo.txt", "x"):
                pass

  1. Modifying the test case:

The openBlankFile method needs to be modified to use os module functions, as discussed above.
We need to patch the os.open and os.remove functions in the test case instead of the built-in open function.
Here's the modified test case:

from unittest import TestCase
from unittest.mock import patch
from os import remove

class WriteModule(object):
    def openBlankFile(self):
        try:
            with open("Foo.txt", "x"):
                pass
        except FileExistsError:
            remove("Foo.txt")
            with open("Foo.txt", "x"):
                pass

class testWriteModule(TestCase):
    @patch("mockPatch.os.remove")
    @patch("mockPatch.os.open", side_effect=FileExistsError)
    def testWriteModuleOpen(self, mock_open, mock_remove):
        WriteModule().openBlankFile()

By using os.open and os.remove in both the method and the test case, we can successfully patch the os.open function to raise a FileExistsError exception, and the test case will handle it correctly.

With these modifications, the test case should work as expected, and you should be able to verify the behavior of the openBlankFile method when the file exists.

huangapple
  • 本文由 发表于 2023年7月27日 20:53:41
  • 转载请务必保留本文链接:https://go.coder-hub.com/76779940.html
匿名

发表评论

匿名网友

:?: :razz: :sad: :evil: :!: :smile: :oops: :grin: :eek: :shock: :???: :cool: :lol: :mad: :twisted: :roll: :wink: :idea: :arrow: :neutral: :cry: :mrgreen:

确定