【问题标题】:unit test python code that has configparser reading from config file具有从配置文件读取 configparser 的单元测试 python 代码
【发布时间】:2018-09-20 07:07:43
【问题描述】:

我是 python 单元测试的新手。我学习并进行了示例单元测试,其中方法接受输入并返回输出。

但是对于下面提到的代码,我有一些问题。

  1. 如何在 unittest 中模拟 init 方法的 configparser?路径 /config/program.cfg 位于生产服务器上,并且在 dev 目录中不存在。 program.cfg 文件存在于代码目录中的其他位置。有没有办法在单元测试中处理它?
  2. 如何在 unittest 中发送或跳过硬编码路径之类的内容,例如/var/log/info_server.log
  3. 如果可能的话,你能告诉我你将如何使用 pytest 模块为下面的代码编写 unittest 吗?这将有助于理解流程,我可以使用其余代码来做到这一点。

    def __init__(self):
        self.setup_logger()
    
        # Read config parameters
        config = configparser.ConfigParser()
        config.read("/config/program.cfg")
        self.host_ip = config.get('default','HostIP')
        self.redis_ip = config.get('default','RedisIP')
        self.redis_port = config.get('default','RedisPort')
        self.info_port = config.get('default','InfoPort')
        self.sqlite_db_file = config.get('default','SQLiteDbFile')
    
        self.connect_redis()
    
    def setup_logger(self):
        #Initialize logger
        self.logger = logging.getLogger(__name__)
        self.logger.setLevel(logging.INFO)
    
        # Create a file handler
        handler = logging.FileHandler('/var/log/info_server.log')
        handler.setLevel(logging.INFO)
    
        # Create a logging format
        formatter = logging.Formatter('%(asctime)s - %(name)s - %(levelname)s - %(message)s')
        handler.setFormatter(formatter)
    
        # Add the handlers to the logger
        self.logger.addHandler(handler)
    
    def connect_redis(self):
        # Start up a Redis instance
        self.logger.info("Start Redis instance")
        self.ad_info = redis.StrictRedis(host=self.redis_ip, port=self.redis_port, db=0)
    
        if self.ad_info is None:
            self.logger.error("Failed to start Redis instance")
        else:
            self.logger.info("Started Redis instance")
    

【问题讨论】:

  • 不要一次问多个问题;一个人知道三个问题的答案的可能性要小于一个。您可以模拟内置的 open 函数,如果 /config/program.cfg 正在打开,该函数将返回带有配置文件内容的 StringIO,或者在打开 /var/log/info_server.log 进行写入时写入 StringIO

标签: python unit-testing pytest


【解决方案1】:

当然,我们可以模拟很多东西,但是简单地重构被测试的类,添加配置和日志,会让生活变得更轻松。

def __init__(self, cfg='/config/program.cfg', log='/var/log/info_server.log'):

/config/program.cfg 不存在的开发服务器中,您可以只是

TheClass(cfg='~/dev.cfg', log='/tmp/dev.log')

self.server_obj.connect_redis() log_mock.logger.info.assert_called_with('Started Redis instance') 不工作

import logging
logging.getLogger

# <function getLogger at 0x7f3d2ed427b8>
logger = logging.getLogger()
type(logger)
# <class 'logging.RootLogger'>
logger.info
# <bound method Logger.info of <RootLogger root (WARNING)>>

补丁应该是这样的:

with mock.patch.object(logging.RootLogger, 'info') as mock_info:

    server_obj = Server(cfg='sth', log='sth')
    mock_info.assert_called_with('xxx')

【讨论】:

  • 谢谢。这真的让生活变得轻松多了。但是我现在面临在 start_redis 方法中调用 self.logger.info("Started Redis instance") 次数的问题。我尝试使用 patch('scripts.server.Server.logging') as log_mock: self.server_obj.connect_redis() log_mock.logger.info.assert_called_with('Started Redis instance') 但它查看的是 logging.info 而不是 self connect_redis 中的 .logging.info 我该如何实现?
  • 假设你正在测试的脚本或模块是server.py,你import logging,使用patch('scripts.sever.logging.getLogger.info') as mock_info,那么你可以self.server.obj.logger.info.assert_called_with('xxx')@Amit
  • 感谢刚的快速回复。是的。脚本名称是 server.py 并且它具有我尝试使用上述建议的导入日志记录,但出现以下错误。 AttributeError: 没有属性 'info' 所以我添加了“getLogger.level..”并尝试使用 patch('scripts.server.logging.getLogger.level.info') 作为 mock_info: self .server_obj.logger.info.assert_call_with('Started Redis instance') 但得到 ModuleNotFoundError: No module named 'scripts.server.logging'; 'scripts.server' 不是一个包我错过了什么吗?
  • 只是告诉你,我使用的是 python 3.6.5
  • @Amit,如果您的 IDE 中有自动完成功能,那么在这种情况下更容易弄清楚要模拟什么,我更新了答案,它是伪代码,让您了解如何操作
猜你喜欢
  • 2021-08-01
  • 2013-10-11
  • 1970-01-01
  • 1970-01-01
  • 2021-01-13
  • 2015-02-28
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
相关资源
最近更新 更多