我有以下结构:
sources/
- parser/
- sources_parser.py # SourcesParser class is here
- tests/
- test_sources_parsers.py # SourcesParserTest is here
sources_parser.py
:
from sources.parser.sitemap_parser import SitemapParser
class SourcesParser(object):
__sitemap_parser: SitemapParser
def __init__(self) -> None:
super().__init__()
self.__sitemap_parser = SitemapParser()
def parse(self):
# ...
urls = self.__parse(source)
self.logger.info(f'Got {len(urls)} URLs')
def __parse(self, source: NewsSource) -> List[str]:
results = self.__sitemap_parser.parse_sitemap(source.url)
self.logger.info(f'Results: {results}, is list: {isinstance(results, list)}')
return results
测试:
class SourcesParserTest(TestCase):
@patch('sources.parser.sources_parser.SitemapParser')
def test_normal_parse(self, mock_sitemap_parser):
mock_sitemap_parser.parse_sitemap.return_value = [
'https://localhost/news/1',
'https://localhost/news/2',
'https://localhost/news/3',
]
parser = SourcesParser()
parser.parse()
日志如下:
Results: <MagicMock name='SitemapParser().parse_sitemap()' id='5105954240'>, is list: False
Got 0 URLs
如果我正确理解了mock,parse_sitemap
调用应该返回给定的url列表,但它返回的是一个被转换为空列表的Mock
对象。
Python版本为3.9.
怎么了?
如果模拟成员函数,则必须在被模拟的实例对象上模拟函数,而不是在被模拟的类对象上模拟函数。这可能不是完全直观的,因为成员函数属于类,但您可以将其视为绑定与未绑定方法-您必须模拟绑定方法。
对于Mock
,模拟实例是通过在类对象上使用return_value
来完成的,类似于模拟函数调用的结果,所以在您的情况下您需要:
@patch('sources.parser.sources_parser.SitemapParser')
def test_normal_parse(self, mock_sitemap_parser):
mocked_parser = mock_sitemap_parser.return_value # mocked instance
mock_parser.parse_sitemap.return_value = [
'https://localhost/news/1',
'https://localhost/news/2',
'https://localhost/news/3',
]
...
(分割模拟仅用于说明)