2013-12-21 1 views
10

특정 조건에서 logger.warn('...')을 통해 경고를 발생시키는 파이썬 코드 조각에 대한 단위 테스트를 작성하려고합니다. 이 경고가 기록되었다고 어떻게 주장합니까? 적어도 assertLogged은 Python 3.4 이상에서 사용할 수 없다는 것을 알았습니다. 불행히도 2.7에 있습니다.Python 2.7 단위 테스트 : 어거 로거 경고가 발생했습니다.

답변

2

유닛 테스트 설정에서 레코드를 버퍼링하는 로깅 처리기를 추가하고 해체 중에 제거하십시오. Python 테스트 인프라의 일부인 기초로 a couple of utility classes, TestHandler and Matcher을 사용할 수 있습니다. (이 링크는 파이썬의 기본 브랜치와 연결되어 있지만 클래스는 다른 파이썬 버전에서도 사용할 수 있어야합니다). 이러한 클래스를 사용하는 방법에 대한 자세한 내용은 this post을 참조하십시오.

2

Python 3.4 해당 기능을 unittest에 정확하게 추가했습니다. TestCase.assertLogs을 참조하십시오. API는 정말 사용하기 쉬운 :

with self.assertLogs('foo', level='INFO') as cm: 
    logging.getLogger('foo').info('first message') 
    logging.getLogger('foo.bar').error('second message') 
self.assertEqual(cm.output, ['INFO:foo:first message', 
          'ERROR:foo.bar:second message']) 

지금,이 질문은 python2.7 태그이지만 python + unittest + logging 비슷한 제목 때 검색을 표시됩니다. 그리고 그것은 백 포트에 Python2.7에 기능을 아주 쉽게, 그래서 여기있다 :

#test_my_module 
from logger_test import LogTestCase 

class TestMyModule(LogTestCase): 

    def test_some_feature(self): 
     with self.assertLogs('foo', level='INFO') as cm: 
      logging.getLogger('foo').info('first message') 
      logging.getLogger('foo.bar').error('second message') 
     self.assertEqual(cm.output, ['INFO:foo:first message', 
         'ERROR:foo.bar:second message']) 
:

는 단위 테스트 모듈에서 지금
# logger_test.py 
# this file contains the base class containing the newly added method 
# assertLogs 
import collections 
import logging 
_LoggingWatcher = collections.namedtuple("_LoggingWatcher", 
             ["records", "output"]) 

class _BaseTestCaseContext(object): 

    def __init__(self, test_case): 
     self.test_case = test_case 

    def _raiseFailure(self, standardMsg): 
     msg = self.test_case._formatMessage(self.msg, standardMsg) 
     raise self.test_case.failureException(msg) 


class _CapturingHandler(logging.Handler): 
    """ 
    A logging handler capturing all (raw and formatted) logging output. 
    """ 

    def __init__(self): 
     logging.Handler.__init__(self) 
     self.watcher = _LoggingWatcher([], []) 

    def flush(self): 
     pass 

    def emit(self, record): 
     self.watcher.records.append(record) 
     msg = self.format(record) 
     self.watcher.output.append(msg) 


class _AssertLogsContext(_BaseTestCaseContext): 
    """A context manager used to implement TestCase.assertLogs().""" 

    LOGGING_FORMAT = "%(levelname)s:%(name)s:%(message)s" 

    def __init__(self, test_case, logger_name, level): 
     _BaseTestCaseContext.__init__(self, test_case) 
     self.logger_name = logger_name 
     if level: 
      self.level = logging._levelNames.get(level, level) 
     else: 
      self.level = logging.INFO 
     self.msg = None 

    def __enter__(self): 
     if isinstance(self.logger_name, logging.Logger): 
      logger = self.logger = self.logger_name 
     else: 
      logger = self.logger = logging.getLogger(self.logger_name) 
     formatter = logging.Formatter(self.LOGGING_FORMAT) 
     handler = _CapturingHandler() 
     handler.setFormatter(formatter) 
     self.watcher = handler.watcher 
     self.old_handlers = logger.handlers[:] 
     self.old_level = logger.level 
     self.old_propagate = logger.propagate 
     logger.handlers = [handler] 
     logger.setLevel(self.level) 
     logger.propagate = False 
     return handler.watcher 

    def __exit__(self, exc_type, exc_value, tb): 
     self.logger.handlers = self.old_handlers 
     self.logger.propagate = self.old_propagate 
     self.logger.setLevel(self.old_level) 
     if exc_type is not None: 
      # let unexpected exceptions pass through 
      return False 
     if len(self.watcher.records) == 0: 
      self._raiseFailure(
       "no logs of level {} or higher triggered on {}" 
       .format(logging.getLevelName(self.level), self.logger.name)) 


class LogTestCase(unittest.TestCase): 

    def assertLogs(self, logger=None, level=None): 
     """Fail unless a log message of level *level* or higher is emitted 
     on *logger_name* or its children. If omitted, *level* defaults to 
     INFO and *logger* defaults to the root logger. 

     This method must be used as a context manager, and will yield 
     a recording object with two attributes: `output` and `records`. 
     At the end of the context manager, the `output` attribute will 
     be a list of the matching formatted log messages and the 
     `records` attribute will be a list of the corresponding LogRecord 
     objects. 

     Example:: 

      with self.assertLogs('foo', level='INFO') as cm: 
       logging.getLogger('foo').info('first message') 
       logging.getLogger('foo.bar').error('second message') 
      self.assertEqual(cm.output, ['INFO:foo:first message', 
             'ERROR:foo.bar:second message']) 
     """ 
     return _AssertLogsContext(self, logger, level) 

해당 클래스를 사용할 수 있습니다