我发现自己无法在类中模拟打开文件,然后调用 pytz 来设置时区,因为 pytz 还需要执行文件打开操作,并且最终也收到模拟。问题的一个例子:
import unittest
from unittest.mock import patch, mock_open
from datetime import datetime
import pytz
class Foo:
def __init__(self, filename):
with open(filename, "r") as input:
timestring = input.read()
time = datetime.strptime(timestring, '%Y-%m-%d %H:%M:%S')
zone = pytz.timezone('GMT')
self.converted_time = zone.localize(time).strftime('%a %b %d %H:%M:%S %Z %Y')
def show_time(self):
return self.converted_time
class TestFoo(unittest.TestCase):
def test_foo(self):
with patch('builtins.open', mock_open(read_data="2023-12-20 00:00:00")) as mock_file:
foo = Foo(mock_file)
output = foo.converted_time
self.assertEqual(output, 'Wed Dec 20 00:00:00 GMT 2023')
if __name__ == '__main__':
unittest.main()
当达到
zone = pytz.timezone('GMT')
时测试失败,因为 pytz 还尝试从最初打开文件的模拟中读取数据。有没有办法将模拟限制为仅针对 Foo 类,而不是从其他模块调用的任何函数?或者,仅模拟builtins.open 的第一次使用?
最好重新设计你的类,不需要模拟来测试它。
Foo.__init__
应该采用类似文件的对象作为参数,而不是尝试打开文件本身。
import io
class Foo:
def __init__(self, fobj):
timestring = fobj.read()
time = datetime.strptime(timestring, '%Y-%m-%d %H:%M:%S')
zone = pytz.timezone('GMT')
return zone.localize(time).strftime('%a %b %d %H:%M:%S %Z %Y')
class TestFoo(unittest.TestCase):
def test_foo(self):
output = Foo(io.StringIO("2023-12-20 00:00:00"))
self.assertEqual(output, 'Wed Dec 20 00:00:00 GMT 2023')
经过一些实验,我发现用
patch('__main__.open')
代替 patch('builtins.open')
可以避免替换 pytz 模块中对 open 的调用,从而允许单元测试正确执行而无需任何其他更改。话虽如此,chepner 的答案似乎是更好的做法,因为它将打开文件的关注点与转换日期字符串的关注点分开了。