如何用 unittest 修补方法 io.RawIOBase.read?
How to patch method io.RawIOBase.read with unittest?
我最近了解了 unittest.monkey.patch
及其变体,我想用它来对文件读取函数的原子性进行单元测试。不过打补丁好像没什么效果。
这是我的设置。细看下的方法大致是这样(略):
#local_storage.py
def read(uri):
with open(path, "rb") as file_handle:
result = file_handle.read()
return result
以及执行单元测试的模块(也有删节):
#test/test_local_storage.py
import unittest.mock
import local_storage
def _read_while_writing(io_handle, size=-1):
""" The patch function, to replace io.RawIOBase.read. """
_write_something_to(TestLocalStorage._unsafe_target_file) #Appends "12".
result = io_handle.read(size) #Should call the actual read.
_write_something_to(TestLocalStorage._unsafe_target_file) #Appends "34".
class TestLocalStorage(unittest.TestCase):
_unsafe_target_file = "test.txt"
def test_read_atomicity(self):
with open(self._unsafe_target_file, "wb") as unsafe_file_handle:
unsafe_file_handle.write(b"Test")
with unittest.mock.patch("io.RawIOBase.read", _read_while_writing): # <--- This doesn't work!
result = local_storage.read(TestLocalStorage._unsafe_target_file) #The actual test.
self.assertIn(result, [b"Test", b"Test1234"], "Read is not atomic.")
这样,补丁应该确保每次你尝试读取它时,文件在实际读取之前和之后被修改,就好像它同时发生一样,从而测试我们读取的原子性。
单元测试目前成功,但我已经用打印语句验证补丁函数实际上并没有被调用,所以文件永远不会获得额外的写入(它只是说 "Test")。我还故意将代码修改为非原子的。
所以我的问题是:如何在 local_storage 模块中修补 IO 句柄的 read
函数? 我在别处读到人们倾向于将 open() 函数替换为 return 类似 StringIO
的东西,但我不知道如何解决这个问题。
我需要支持 Python 3.4 及更高版本。
我终于自己找到了解决方案。
问题是 mock
无法模拟用 C 编写的对象的任何方法。其中之一是我遇到的 RawIOBase
。
所以确实解决方案是将 open
模拟为 return RawIOBase
的包装器。我无法 mock
为我制作包装器,所以我自己实现了它。
有一个预定义文件被视为 "unsafe"。每次对包装器进行任何调用时,包装器都会写入此 "unsafe" 文件。这允许测试文件写入的原子性,因为它在写入时将其他内容写入不安全文件。我的实现通过写入临时 ("safe") 文件然后将该文件移动到目标文件上来防止这种情况。
包装器对 read
函数有一个特殊情况,因为要正确测试原子性,它需要在 读取期间 写入文件。所以它首先读取文件的一半,然后停止并写入一些东西,然后继续读取。这个解决方案现在是半硬编码的(在中途有多远),但我会找到一种方法来改进它。
我最近了解了 unittest.monkey.patch
及其变体,我想用它来对文件读取函数的原子性进行单元测试。不过打补丁好像没什么效果。
这是我的设置。细看下的方法大致是这样(略):
#local_storage.py
def read(uri):
with open(path, "rb") as file_handle:
result = file_handle.read()
return result
以及执行单元测试的模块(也有删节):
#test/test_local_storage.py
import unittest.mock
import local_storage
def _read_while_writing(io_handle, size=-1):
""" The patch function, to replace io.RawIOBase.read. """
_write_something_to(TestLocalStorage._unsafe_target_file) #Appends "12".
result = io_handle.read(size) #Should call the actual read.
_write_something_to(TestLocalStorage._unsafe_target_file) #Appends "34".
class TestLocalStorage(unittest.TestCase):
_unsafe_target_file = "test.txt"
def test_read_atomicity(self):
with open(self._unsafe_target_file, "wb") as unsafe_file_handle:
unsafe_file_handle.write(b"Test")
with unittest.mock.patch("io.RawIOBase.read", _read_while_writing): # <--- This doesn't work!
result = local_storage.read(TestLocalStorage._unsafe_target_file) #The actual test.
self.assertIn(result, [b"Test", b"Test1234"], "Read is not atomic.")
这样,补丁应该确保每次你尝试读取它时,文件在实际读取之前和之后被修改,就好像它同时发生一样,从而测试我们读取的原子性。
单元测试目前成功,但我已经用打印语句验证补丁函数实际上并没有被调用,所以文件永远不会获得额外的写入(它只是说 "Test")。我还故意将代码修改为非原子的。
所以我的问题是:如何在 local_storage 模块中修补 IO 句柄的 read
函数? 我在别处读到人们倾向于将 open() 函数替换为 return 类似 StringIO
的东西,但我不知道如何解决这个问题。
我需要支持 Python 3.4 及更高版本。
我终于自己找到了解决方案。
问题是 mock
无法模拟用 C 编写的对象的任何方法。其中之一是我遇到的 RawIOBase
。
所以确实解决方案是将 open
模拟为 return RawIOBase
的包装器。我无法 mock
为我制作包装器,所以我自己实现了它。
有一个预定义文件被视为 "unsafe"。每次对包装器进行任何调用时,包装器都会写入此 "unsafe" 文件。这允许测试文件写入的原子性,因为它在写入时将其他内容写入不安全文件。我的实现通过写入临时 ("safe") 文件然后将该文件移动到目标文件上来防止这种情况。
包装器对 read
函数有一个特殊情况,因为要正确测试原子性,它需要在 读取期间 写入文件。所以它首先读取文件的一半,然后停止并写入一些东西,然后继续读取。这个解决方案现在是半硬编码的(在中途有多远),但我会找到一种方法来改进它。