2013-10-08 3 views
2

테스트 러너를 쓰고 있습니다. 나는 예외를 포착하고 저장할 수있는 객체를 가지고 있으며,이 객체는 나중에 테스트 실패 보고서의 일부로 문자열로 포맷됩니다. 예외를 형식화하는 프로 시저를 단위 테스트하려고합니다.파이썬에서 흔적 추적하기

내 테스트 설정에서 실제로는이 내 개체를 잡을 예외를 throw하고 싶지 않습니다. 이는 주로 추적 표시를 예측할 수 없기 때문입니다. (파일의 길이가 변하면 트레이스 백에있는 줄 번호가 바뀔 것입니다.)

가짜 추적을 예외에 첨부하면 포맷 방법에 대한 주장을 할 수 있습니다. 이것은 가능한가? 파이썬 3.3을 사용하고 있습니다.

간단한 예 :

class ExceptionCatcher(object): 
    def __init__(self, function_to_try): 
     self.f = function_to_try 
     self.exception = None 
    def try_run(self): 
     try: 
      self.f() 
     except Exception as e: 
      self.exception = e 

def format_exception_catcher(catcher): 
    pass 
    # No implementation yet - I'm doing TDD. 
    # This'll probably use the 'traceback' module to stringify catcher.exception 


class TestFormattingExceptions(unittest.TestCase): 
    def test_formatting(self): 
     catcher = ExceptionCatcher(None) 
     catcher.exception = ValueError("Oh no") 

     # do something to catcher.exception so that it has a traceback? 

     output_str = format_exception_catcher(catcher) 
     self.assertEquals(output_str, 
"""Traceback (most recent call last): 
    File "nonexistent_file.py", line 100, in nonexistent_function 
    raise ValueError("Oh no") 
ValueError: Oh no 
""") 

답변

2

독서 the source of traceback.py은 올바른 방향으로 나를 가리켰다. 여기 내 해킹 해법이있다. 추적 코드가 일반적으로 참조하는 프레임과 코드 객체를 가짜로 만드는 것이다. 실제 예외 with_traceback()에 인수 유형을 체크하기 때문에 필요하다 FakeException을 제공 @User에

import traceback 

class FakeCode(object): 
    def __init__(self, co_filename, co_name): 
     self.co_filename = co_filename 
     self.co_name = co_name 


class FakeFrame(object): 
    def __init__(self, f_code, f_globals): 
     self.f_code = f_code 
     self.f_globals = f_globals 


class FakeTraceback(object): 
    def __init__(self, frames, line_nums): 
     if len(frames) != len(line_nums): 
      raise ValueError("Ya messed up!") 
     self._frames = frames 
     self._line_nums = line_nums 
     self.tb_frame = frames[0] 
     self.tb_lineno = line_nums[0] 

    @property 
    def tb_next(self): 
     if len(self._frames) > 1: 
      return FakeTraceback(self._frames[1:], self._line_nums[1:]) 


class FakeException(Exception): 
    def __init__(self, *args, **kwargs): 
     self._tb = None 
     super().__init__(*args, **kwargs) 

    @property 
    def __traceback__(self): 
     return self._tb 

    @__traceback__.setter 
    def __traceback__(self, value): 
     self._tb = value 

    def with_traceback(self, value): 
     self._tb = value 
     return self 


code1 = FakeCode("made_up_filename.py", "non_existent_function") 
code2 = FakeCode("another_non_existent_file.py", "another_non_existent_method") 
frame1 = FakeFrame(code1, {}) 
frame2 = FakeFrame(code2, {}) 
tb = FakeTraceback([frame1, frame2], [1,3]) 
exc = FakeException("yo").with_traceback(tb) 

print(''.join(traceback.format_exception(FakeException, exc, tb))) 
# Traceback (most recent call last): 
# File "made_up_filename.py", line 1, in non_existent_function 
# File "another_non_existent_file.py", line 3, in another_non_existent_method 
# FakeException: yo 

감사합니다.

이 버전은 몇 가지 제한 가지고 : format_exception이 진짜를 찾기 위해 꺼집니다 때문에, 그것은 실제 역 추적 마찬가지로, 각 스택 프레임에 대한 코드의 라인을 인쇄하지 않습니다

  • 을 파일에서 온 코드입니다 (우리의 경우에는 존재하지 않습니다). 이 작업을 확인하려면 , 당신은 (traceback 소스 코드를 잡아 linecache를 사용하기 때문에) @User's answer belowlinecache의 캐시에 가짜 데이터를 삽입해야합니다.

  • 또한 실제로 exc을 올리고 가짜 추적 생존을 기대할 수 없다.

  • 더 일반적으로, 당신은 traceback 아닌 다른 방법으로는 (예 : inspect 모듈의 많이), 이러한 가짜 아마 작동하지 않습니다 않는 에서 역 추적을 통과 클라이언트 코드가있는 경우. 클라이언트 코드가 기대하는 여분의 속성을 추가해야합니다 ( ). 난 그냥 traceback를 호출하는 코드에 대한 테스트 더블로 사용하고 있습니다 - -

이러한 제한은 내 목적을 위해 미세하지만 당신은 더 복잡 역 추적 조작을 수행하려는 경우, 그것은 looks like 당신은 아래로 이동해야 할 수도 있습니다 C 레벨로.

+0

나는 C/Level에 동의하지만, 필요하다면 파일 라인을 도울 수있다. 'linecache.updatecache (파일 이름, fake_module_with_a ___ 로더 ___ 속성)'이 올바른 방향입니다. – User

+0

@User Awesome! 나는 그것을 들여다 볼 것이다. –

+0

그 점을 들여다 보았습니다. 새로운 솔루션을 갖고 있다면 공유하십시오! :) – User

-1

당신은 할 수 있어야 단순히 당신이 당신의 테스트 실행에서 원하는 위치에 원하는 가짜 어떤 예외 raise. 파이썬 예외 문서는 클래스를 생성하고이를 예외로 발생시키는 것을 제안합니다. 문서 8.5 절입니다. 사용자가 만든 클래스를 가지고 한 번

http://docs.python.org/2/tutorial/errors.html

은 매우 간단합니다.

+0

-1. 이건 내 질문을 해결하지 못합니다. 나는 추적 코드가 라인 번호 등을 예측할 수 없기 때문에 직접 테스트 코드에서 예외를 발생시키고 싶지는 않다. 그것은 예외의 클래스가 아니라 가짜로 만들고 싶은 _ 추적입니다. –

+0

추적 표시를 작성하지 않고 강제로 예외로 호출하는 것이 좋지 않은 경우 추적 표시를 "위장"할 수있는 방법이 표시되지 않습니다. 'catcher.exception'을 사용하여 질문 할 때 추적 점을 잡으려고한다면 예외를 출력해야합니다. 가짜 추적을 작성해도 실제 모듈을 통해 구현을 테스트 할 수 없다면 어떻게 될지 알 수 있습니다. – Benjooster

+0

Python에서는 런타임 스택을 검사 할 수 있습니다. ['sys._getframe()'] (http://docs.python.org/3.3/library/sys.html#sys._getframe) 및 ['inspect.stack()'] (http : // docs. python.org/3.3/library/inspect.html#the-interpreter-stack). 트레이스 백은 원할 경우 수동으로 트래버스 할 수있는 최상급 객체입니다 (예외 추적 .__ traceback__ 또는 ['sys.exc_info()']를 통해 실시간 추적을 얻을 수 있습니다 (http://docs.python.org/ 3.3/library/sys.html)). 이를 염두에두고 실제 예외를 던지지 않고 순수한 파이썬에서 자신 만의 추적을 만들 수있을 것이라고 기대하는 것은 너무 극단적이지 않습니다. –

2

EDIT2 :

linecache의 코드

.. 내가 댓글을 것입니다. 그냥 TestRun으로 전에 의미

def updatecache(filename, module_globals=None): # module_globals is a dict 
     # ... 
    if module_globals and '__loader__' in module_globals: 
     name = module_globals.get('__name__') 
     loader = module_globals['__loader__'] 
      # module_globals = dict(__name__ = 'somename', __loader__ = loader) 
     get_source = getattr(loader, 'get_source', None) 
      # loader must have a 'get_source' function that returns the source 

     if name and get_source: 
      try: 
       data = get_source(name) 
      except (ImportError, IOError): 
       pass 
      else: 
       if data is None: 
        # No luck, the PEP302 loader cannot find the source 
        # for this module. 
        return [] 
       cache[filename] = (
        len(data), None, 
        [line+'\n' for line in data.splitlines()], fullname 
       ) 
       return cache[filename][2] 

는 수행

class Loader: 
    def get_source(self): 
     return 'source of the module' 
import linecache 
linecache.updatecache(filename, dict(__name__ = 'modulename without <> around', 
            __loader__ = Loader())) 

'source of the module'이 테스트 모듈의 소스입니다.

EDIT1 : 지금까지

내 솔루션 :

class MyExeption(Exception): 
    _traceback = None 
    @property 
    def __traceback__(self): 
     return self._traceback 
    @__traceback__.setter 
    def __traceback__(self, value): 
     self._traceback = value 
    def with_traceback(self, tb_or_none): 
     self.__traceback__ = tb_or_none 
     return self 

지금 당신이 예외의 사용자 지정 역 추적을 설정할 수 있습니다 당신이 예외를 리 레이즈 경우 일반적으로 무엇을

e = MyExeption().with_traceback(1) 

:

raise e.with_traceback(fake_tb) 

모든 예외 인쇄물은 다음 기능을 수행합니다.

import traceback 
traceback.print_exception(_type, _error, _traceback) 

어쨌든 도움이 되길 바랍니다.

+1

'with_traceback()'에 어떤 종류의 객체를 전달합니까? –