doctest — 대화형 Python 예제 테스트

doctest — 대화형 Python 예제 테스트

소스 코드: Lib/doctest.py

doctest 모듈은 대화형 Python 세션처럼 보이는 텍스트 조각을 찾아서, 그 세션을 실행해 문서에 적힌 그대로 동작하는지 검증해요. doctest를 쓰는 일반적인 방법은 여러 가지예요.

  • 모든 대화형 예제가 문서화된 대로 여전히 동작하는지 검증해서 모듈의 docstring이 최신 상태인지 확인하기.
  • 테스트 파일이나 테스트 객체의 대화형 예제가 기대대로 동작하는지 검증해서 회귀 테스트(regression testing) 수행하기.
  • 입력-출력 예제로 풍부하게 설명한 패키지용 튜토리얼 문서 작성하기. 예제와 설명 텍스트 중 무엇을 강조하느냐에 따라 "문자적 테스트(literate testing)" 또는 "실행 가능한 문서(executable documentation)"의 성격을 띠어요.

완전하지만 작은 예제 모듈을 볼게요.

"""
This is the "example" module.

The example module supplies one function, factorial().  For example,

>>> factorial(5)
120
"""

def factorial(n):
    """Return the factorial of n, an exact integer >= 0.

    >>> [factorial(n) for n in range(6)]
    [1, 1, 2, 6, 24, 120]
    >>> factorial(30)
    265252859812191058636308480000000
    >>> factorial(-1)
    Traceback (most recent call last):
        ...
    ValueError: n must be >= 0

    Factorials of floats are OK, but the float must be an exact integer:
    >>> factorial(30.1)
    Traceback (most recent call last):
        ...
    ValueError: n must be exact integer
    >>> factorial(30.0)
    265252859812191058636308480000000

    It must also not be ridiculously large:
    >>> factorial(1e100)
    Traceback (most recent call last):
        ...
    OverflowError: n too large
    """

    import math
    if not n >= 0:
        raise ValueError("n must be >= 0")
    if math.floor(n) != n:
        raise ValueError("n must be exact integer")
    if n+1 == n:  # catch a value like 1e300
        raise OverflowError("n too large")
    result = 1
    factor = 2
    while factor <= n:
        result *= factor
        factor += 1
    return result

if __name__ == "__main__":
    import doctest
    doctest.testmod()

example.py를 명령줄에서 직접 실행하면 doctest가 마법을 부려요.

$ python example.py
$

출력이 없어요! 그게 정상이에요. 모든 예제가 동작했다는 뜻이죠. 스크립트에 -v를 넘기면 doctest가 시도하는 내용의 상세 로그를 출력하고 끝에 요약을 내요.

$ python example.py -v
Trying:
    factorial(5)
Expecting:
    120
ok
Trying:
    [factorial(n) for n in range(6)]
Expecting:
    [1, 1, 2, 6, 24, 120]
ok

그리고 계속되다가 결국 이렇게 끝나요.

Trying:
    factorial(1e100)
Expecting:
    Traceback (most recent call last):
        ...
    OverflowError: n too large
ok
2 items passed all tests:
   1 test in __main__
   6 tests in __main__.factorial
7 tests in 2 items.
7 passed.
Test passed.
$

이걸 알면 doctest를 생산적으로 쓰기 시작하는 데 충분해요! 아래 섹션들이 완전한 세부 사항을 제공해요. 표준 Python 테스트 스위트와 라이브러리에는 doctest 예제가 많다는 점을 참고하세요. 특히 Lib/test/test_doctest/test_doctest.py에서 유용한 예제를 찾을 수 있어요.

버전 3.13에 추가됨: 출력이 기본적으로 컬러화되며 환경 변수로 제어할 수 있음.

출처: Python 표준 라이브러리

본문

간단한 사용법: docstring의 예제 검사하기

doctest를 쓰기 시작하는 가장 간단한 방법(하지만 계속 이렇게 하게 될 방법은 아닐 수도 있어요)은 각 모듈 M을 이렇게 끝내는 거예요.

if __name__ == "__main__":
    import doctest
    doctest.testmod()

그러면 doctest가 모듈 M의 docstring을 검사해요. 모듈을 스크립트로 실행하면 docstring의 예제가 실행·검증돼요.

python M.py

이건 예제가 실패하지 않는 한 아무것도 표시하지 않아요. 실패하면 실패한 예제와 실패 원인이 stdout에 출력되고, 마지막 출력 줄은 ***Test Failed*** N failures.가 돼요(N은 실패한 예제 수). 대신 -v 스위치로 실행하면 시도된 모든 예제의 상세 보고가 표준 출력에 출력되고 끝에 여러 요약이 따라와요.

testmod()verbose=True를 전달해 상세 모드를 강제하거나 verbose=False로 금지할 수 있어요. 어느 쪽이든 testmod()sys.argv를 검사하지 않아요(그래서 -v 유무는 효과가 없어요). testmod()를 실행하는 명령줄 단축키도 있어요("명령줄 사용법" 섹션 참고).

간단한 사용법: 텍스트 파일의 예제 검사하기

doctest의 또 다른 간단한 적용은 텍스트 파일의 대화형 예제를 테스트하는 거예요. testfile() 함수로 할 수 있어요.

import doctest
doctest.testfile("example.txt")

이 짧은 스크립트는 example.txt 파일에 담긴 대화형 Python 예제를 실행·검증해요. 파일 내용은 하나의 거대한 docstring처럼 취급돼요. 파일이 Python 프로그램을 담을 필요는 없어요! 예를 들어 example.txt가 이런 내용을 담을 수 있어요.

The ``example`` module
======================

Using ``factorial``
-------------------

This is an example text file in reStructuredText format.  First import
``factorial`` from the ``example`` module:

    >>> from example import factorial

Now use it:

    >>> factorial(6)
    120

doctest.testfile("example.txt")를 실행하면 이 문서의 오류를 찾아내요.

File "./example.txt", line 14, in example.txt
Failed example:
    factorial(6)
Expected:
    120
Got:
    720

testmod()처럼 testfile()도 예제가 실패하지 않는 한 아무것도 표시하지 않아요. 예제가 실패하면 testmod()와 같은 형식으로 실패 예제와 원인이 stdout에 출력돼요. 기본적으로 testfile()은 호출 모듈의 디렉터리에서 파일을 찾아요. 다른 위치에서 찾게 하려면 "기본 API" 섹션의 선택 인자를 사용할 수 있어요. testmod()처럼 testfile()의 장황함도 -v 명령줄 스위치나 선택 키워드 인자 verbose로 설정할 수 있어요.

명령줄 사용법

doctest 모듈은 명령줄에서 스크립트로 호출할 수 있어요.

python -m doctest [-v] [-o OPTION] [-f] file [file ...]
  • -v, --verbose — 시도된 모든 예제의 상세 보고를 표준 출력에 출력하고 끝에 여러 요약을 내요. python -m doctest -v example.pyexample.py를 독립 모듈로 임포트해 testmod()를 실행해요. 파일이 패키지의 일부이고 그 패키지의 다른 서브모듈을 임포트하면 제대로 작동하지 않을 수 있다는 점을 주의하세요. 파일 이름이 .py로 끝나지 않으면 doctest는 대신 testfile()로 실행해야 한다고 추론해요: python -m doctest -v example.txt.
  • -o, --option <option> — 옵션 플래그가 doctest 동작의 여러 측면을 제어해요("옵션 플래그" 섹션 참고). (버전 3.4에 추가됨.)
  • -f, --fail-fast-o FAIL_FAST의 약식이에요. (버전 3.4에 추가됨.)

어떻게 동작하나요?

이 섹션은 doctest가 어떻게 동작하는지 자세히 살펴봐요. 어떤 docstring을 보는지, 대화형 예제를 어떻게 찾는지, 어떤 실행 컨텍스트를 쓰는지, 예외를 어떻게 처리하는지, 옵션 플래그로 동작을 어떻게 제어하는지요. doctest 예제를 쓰려면 알아야 할 정보예요.

어떤 docstring을 검사하나요? 모듈 docstring과 모든 함수·클래스·메서드 docstring이 검색돼요. 모듈로 임포트된 객체는 검색되지 않아요. 게다가 테스트가 모듈의 일부이지만 도움말 텍스트의 일부는 아니길 원하는 경우가 있는데, 그러려면 테스트가 docstring에 포함되면 안 돼요. Doctest는 모듈 수준 변수 __test__를 찾아 그걸로 다른 테스트를 찾아요. M.__test__가 있으면 딕셔너리여야 하고, 각 항목이 (문자열) 이름을 함수 객체·클래스 객체·문자열에 매핑해요. M.__test__에서 찾은 함수·클래스 객체의 docstring이 검색되고, 문자열은 마치 docstring인 것처럼 취급돼요. 출력에서 M.__test__의 키 KM.__test__.K라는 이름으로 나타나요.

__test__ = {
    'numbers': """
>>> factorial(6)
720

>>> [factorial(n) for n in range(6)]
[1, 1, 2, 6, 24, 120]
"""
}

example.__test__["numbers"]의 값은 docstring처럼 취급되고 그 안의 모든 테스트가 실행돼요. 값이 함수·클래스 객체·모듈에 매핑될 수도 있고, 그러면 doctest가 그것들을 재귀적으로 검색해 docstring을 찾은 다음 테스트를 스캔해요. 발견된 모든 클래스도 유사하게 재귀적으로 검색되어 포함된 메서드와 중첩 클래스의 docstring을 테스트해요.

참고: doctest는 모듈 수준이나 다른 클래스 안에 정의된 클래스와 함수만 자동으로 발견할 수 있어요. 중첩 클래스와 함수는 외부 함수가 호출될 때만 존재하므로 발견할 수 없어요. 보이게 하려면 밖에 정의하세요.

docstring 예제는 어떻게 인식되나요? 대부분의 경우 대화형 콘솔 세션의 복사-붙여넣기가 잘 동작하지만, doctest는 특정 Python 셸의 정확한 에뮬레이션을 하려는 건 아니에요.

>>> # comments are ignored
>>> x = 12
>>> x
12
>>> if x == 13:
...     print("yes")
... else:
...     print("no")
...     print("NO")
...     print("NO!!!")
...
no
NO
NO!!!
>>>

예상 출력은 마지막 '>>> ' 또는 '... ' 줄(코드를 담고 있는) 바로 뒤에 와야 하고, 예상 출력(있으면)은 다음 '>>> ' 또는 모두-공백 줄까지 이어져요.

세부 사항들:

  • 예상 출력은 모두-공백 줄을 포함할 수 없어요. 그런 줄은 예상 출력의 끝을 알리는 것으로 간주되니까요. 예상 출력에 빈 줄이 포함되면 빈 줄이 기대되는 자리마다 doctest 예제에 <BLANKLINE>을 넣으세요.
  • 모든 하드 탭 문자는 8열 탭 정지점을 사용해 공백으로 확장돼요. 테스트된 코드가 생성한 출력의 탭은 수정되지 않아요. 샘플 출력의 하드 탭은 확장되므로, 코드 출력에 하드 탭이 포함되면 NORMALIZE_WHITESPACE 옵션이나 지시어가 적용될 때만 doctest가 통과할 수 있어요. 대안으로 테스트를 재작성해 출력을 캡처해 테스트의 일부로 예상 값과 비교할 수도 있어요.
  • stdout으로의 출력은 캡처되지만 stderr로의 출력은 캡처되지 않아요(예외 트레이스백은 다른 방법으로 캡처돼요).
  • 백슬래시로 줄을 계속하거나 다른 이유로 백슬래시를 쓰면, 입력한 그대로 백슬래시를 보존하는 원시 docstring(raw docstring)을 사용해야 해요.
>>> def f(x):
...     r'''Backslashes in a raw docstring: m\n'''
...
>>> print(f.__doc__)
Backslashes in a raw docstring: m\n

그렇지 않으면 백슬래시가 문자열의 일부로 해석돼요. 위의 \n은 새 줄 문자로 해석되죠. 대안으로 doctest 버전에서 각 백슬래시를 두 번 쓰면 돼요(원시 문자열은 쓰지 않고).

>>> def f(x):
...     '''Backslashes in a raw docstring: m\\n'''
...
>>> print(f.__doc__)
Backslashes in a raw docstring: m\n

시작 열은 중요하지 않아요.

>>> assert "Easy!"
      >>> import math
          >>> math.floor(1.9)
          1

그리고 예상 출력에서 앞 공백 문자는 예제를 시작한 초기 '>>> ' 줄에 나타난 만큼 제거돼요.

실행 컨텍스트는 무엇인가요? 기본적으로 doctest는 테스트할 docstring을 찾을 때마다 M의 globals의 얕은 복사본(shallow copy)을 사용해요. 그렇게 해서 테스트를 실행해도 모듈의 실제 globals가 바뀌지 않고, M의 한 테스트가 다른 테스트가 우연히 동작하게 하는 부스러기를 남길 수도 없어요. 이는 예제가 M의 최상위에 정의된 어떤 이름이든 자유롭게 쓸 수 있고, 실행 중인 docstring에서 앞서 정의된 이름도 쓸 수 있다는 뜻이에요. 예제는 다른 docstring에 정의된 이름은 볼 수 없어요. globs=your_dicttestmod()testfile()에 전달하면 자신의 딕셔너리를 실행 컨텍스트로 강제할 수 있어요.

예외는 어떻게 되나요? 트레이스백이 예제가 만드는 유일한 출력이라면 문제없어요. 트레이스백을 붙여 넣기만 하면 돼요. 트레이스백은 (예: 정확한 파일 경로와 줄 번호 같은) 빠르게 변할 수 있는 세부 사항을 담기 때문에, 이것이 바로 doctest가 받아들이는 것에 유연하게 동작하려 애쓰는 경우예요.

>>> [1, 2, 3].remove(42)
Traceback (most recent call last):
  File "<stdin>", line 1, in <module>
ValueError: list.remove(x): x not in list

이 doctest는 ValueErrorlist.remove(x): x not in list 세부 사항과 함께 발생하면 성공해요. 예외의 예상 출력은 트레이스백 헤더로 시작해야 하는데, 다음 두 줄 중 하나이며 예제의 첫 줄과 같은 들여쓰기가 돼요.

Traceback (most recent call last):
Traceback (innermost last):

트레이스백 헤더 다음에는 선택적인 트레이스백 스택이 오는데, 그 내용은 doctest가 무시해요. 보통 생략하거나 대화형 세션에서 그대로 복사해요. 트레이스백 스택 다음에는 가장 흥미로운 부분, 즉 예외 타입과 세부 사항을 담은 줄이 와요. 보통 트레이스백의 마지막 줄이지만 예외가 여러 줄 세부 사항을 가지면 여러 줄에 걸칠 수 있어요.

>>> raise ValueError('multi\n    line\ndetail')
Traceback (most recent call last):
  File "<stdin>", line 1, in <module>
ValueError: multi
    line
detail

마지막 세 줄(ValueError로 시작하는)이 예외의 타입과 세부 사항과 비교되고 나머지는 무시돼요. 모범 사례는 트레이스백 스택을 생략하는 거예요. 그래서 마지막 예제는 이게 더 나을 거예요.

>>> raise ValueError('multi\n    line\ndetail')
Traceback (most recent call last):
    ...
ValueError: multi
    line
detail

트레이스백은 매우 특별하게 취급된다는 점을 주의하세요. 특히 재작성된 예제에서 ... 사용은 doctest의 ELLIPSIS 옵션과 독립적이에요. 그 예제의 말줄임표는 빼도 되고, 셋(또는 삼백) 개의 쉼표나 숫자, 들여쓴 Monty Python 스킷 대본이어도 무방해요.

한 번 읽어두면 기억할 필요 없는 세부 사항 몇 가지:

  • doctest는 예상 출력이 예외 트레이스백에서 왔는지 일반 출력에서 왔는지 추측할 수 없어요. 그래서 예를 들어 ValueError: 42 is prime을 기대하는 예제는 ValueError가 실제로 발생하든, 예제가 그 트레이스백 텍스트를 그냥 출력하든 통과해요.
  • 트레이스백 스택의 각 줄(있으면)은 예제의 첫 줄보다 더 들여써야 하거나 비영숫자 문자로 시작해야 해요. 트레이스백 헤더 다음에 같은 들여쓰기로 영숫자로 시작하는 첫 줄은 예외 세부 사항의 시작으로 간주돼요.
  • IGNORE_EXCEPTION_DETAIL doctest 옵션이 지정되면 가장 왼쪽 콜론 뒤의 전부와 예외 이름의 모듈 정보가 무시돼요.
  • 대화형 셸은 일부 SyntaxError에 대해 트레이스백 헤더 줄을 생략해요. 하지만 doctest는 트레이스백 헤더 줄로 예외와 비예외를 구분해요. 그래서 드물게 트레이스백 헤더를 생략한 SyntaxError를 테스트해야 한다면 트레이스백 헤더 줄을 수동으로 추가해야 해요.
  • 일부 예외에 대해 Python은 ^ 마커와 물결표로 오류의 위치를 표시해요.
>>> 1 + None
  File "<stdin>", line 1
    1 + None
    ~~^~~~~~
TypeError: unsupported operand type(s) for +: 'int' and 'NoneType'

오류의 위치를 보여 주는 줄들이 예외 타입·세부 사항 앞에 오므로 doctest가 검사하지 않아요. 예를 들어 다음 테스트는 ^ 마커를 잘못된 위치에 두어도 통과해요.

>>> 1 + None
  File "<stdin>", line 1
    1 + None
    ^~~~~~~~
TypeError: unsupported operand type(s) for +: 'int' and 'NoneType'

옵션 플래그

여러 옵션 플래그가 doctest 동작의 여러 측면을 제어해요. 플래그의 상징적 이름은 모듈 상수로 제공되는데, 비트 단위 OR로 결합해 여러 함수에 전달할 수 있어요. 이름은 doctest 지시어에도 쓸 수 있고, -o 옵션으로 doctest 명령줄 인터페이스에도 전달할 수 있어요.

첫 번째 그룹은 테스트 의미론을 정의해요. 실제 출력이 예제의 예상 출력과 일치하는지 doctest가 결정하는 방식을 제어하죠.

  • doctest.DONT_ACCEPT_TRUE_FOR_1 — 기본적으로 예상 출력 블록이 1만 담고 있으면 실제 출력 블록이 1만 또는 True만 담고 있어도 일치로 간주되고, 0False도 마찬가지예요. 이 옵션이 지정되면 어떤 치환도 허용되지 않아요. 기본 동작은 Python이 많은 함수의 반환 타입을 정수에서 불리언으로 바꾼 것에 맞춘 거예요. "작은 정수" 출력을 기대하는 doctest가 이런 경우에도 여전히 동작하도록요. 이 옵션은 아마 사라지겠지만, 몇 년 후일 거예요.
  • doctest.DONT_ACCEPT_BLANKLINE — 기본적으로 예상 출력 블록에 <BLANKLINE> 문자열만 담긴 줄이 있으면 그 줄이 실제 출력의 빈 줄과 일치해요. 진짜 빈 줄은 예상 출력을 구분하므로, 빈 줄이 기대된다는 걸 알리는 유일한 방법이에요.
  • doctest.NORMALIZE_WHITESPACE — 지정되면 모든 공백 시퀀스(빈칸과 줄바꿈)가 동등하게 취급돼요. 예상 출력의 어떤 공백 시퀀스든 실제 출력의 어떤 공백 시퀀스와 일치해요. 기본적으로 공백은 정확히 일치해야 해요. 예상 출력 줄이 매우 길어 소스에서 여러 줄로 감싸고 싶을 때 특히 유용해요.
  • doctest.ELLIPSIS — 지정되면 예상 출력의 말줄임표 마커(...)가 실제 출력의 어떤 부분 문자열이든 일치할 수 있어요. 줄 경계를 넘는 부분 문자열과 빈 부분 문자열을 포함하므로 사용을 단순하게 유지하는 게 좋아요. 복잡한 사용은 .*가 정규식에서 겪는 것과 같은 "oops, it matched too much!" 놀라움을 초래할 수 있어요.
  • doctest.IGNORE_EXCEPTION_DETAIL — 지정되면 예외를 기대하는 doctest는 세부 사항(메시지와 정규 이름)이 일치하지 않아도 기대한 타입의 예외가 발생하기만 하면 통과해요. 예를 들어 ValueError: 42를 기대하는 예제는 실제 예외가 ValueError: 3*14여도 통과하지만 TypeError가 발생하면 실패해요. 또한 예외 클래스 앞에 포함된 정규 이름(full qualified name)도 무시해요. 구현·버전·사용 중인 코드/라이브러리에 따라 달라질 수 있으니까요. (3.2에서 모듈 정보도 무시)
  • doctest.SKIP — 지정되면 예제를 아예 실행하지 않아요. doctest 예제가 문서와 테스트 케이스 양쪽을 겸할 때 문서용으로는 포함하지만 검사해서는 안 되는 예제가 유용해요. 예제의 출력이 무작위이거나 테스트 드라이버에 없는 리소스에 의존하는 경우 같은 거예요. 예제를 임시로 "주석 처리"하는 데도 쓸 수 있어요.
  • doctest.COMPARISON_FLAGS — 위의 모든 비교 플래그를 OR한 비트마스크.

두 번째 그룹은 테스트 실패가 어떻게 보고되는지 제어해요.

  • doctest.REPORT_UDIFF — 지정되면 여러 줄 예상·실제 출력이 관련된 실패를 통합 diff(unified diff)로 표시해요.
  • doctest.REPORT_CDIFF — 지정되면 컨텍스트 diff로 표시돼요.
  • doctest.REPORT_NDIFF — 지정되면 차이를 difflib.Differ로 계산하는데, 유명한 ndiff.py 유틸리티와 같은 알고리즘을 써요. 줄 안과 줄 사이 차이를 모두 표시하는 유일한 방법이에요.
  • doctest.REPORT_ONLY_FIRST_FAILURE — 지정되면 각 doctest의 첫 실패 예제만 표시하고 나머지 예제의 출력은 억제해요. 나머지 예제는 여전히 실행되고 총 실패 수에 포함되지만 출력만 억제돼요.
  • doctest.FAIL_FAST — 지정되면 첫 실패 예제 후 종료하고 나머지 예제를 실행하지 않아요. 보고된 실패 수는 많아야 1이에요. 첫 실패 이후의 예제가 디버깅 출력조차 만들지 않으므로 디버깅 중에 유용할 수 있어요.
  • doctest.REPORTING_FLAGS — 위의 모든 보고 플래그를 OR한 비트마스크.

새 옵션 플래그 이름을 등록하는 방법도 있어요. 서브클래싱으로 doctest 내부를 확장하려는 게 아니라면 유용하지 않지만요.

  • doctest.register_optionflag(name) — 주어진 이름의 새 옵션 플래그를 만들고 새 플래그의 정수 값을 반환해요. OutputCheckerDocTestRunner를 서브클래싱할 때 서브클래스가 지원하는 새 옵션을 만들고 쓸 수 있어요. 항상 다음 관용구로 호출해야 해요.
MY_FLAG = register_optionflag('MY_FLAG')

지시어 (Directives) — doctest 지시어는 개별 예제의 옵션 플래그를 수정하는 데 쓸 수 있어요. 지시어는 예제의 소스 코드를 따르는 특수 Python 주석이에요.

directive:             "#" "doctest:" directive_options
directive_options:     directive_option ("," directive_option)*
directive_option:      on_or_off directive_option_name
on_or_off:             "+" | "-"
directive_option_name: "DONT_ACCEPT_BLANKLINE" | "NORMALIZE_WHITESPACE" | ...

+-와 지시어 옵션 이름 사이에는 공백이 허용되지 않아요. 지시어 옵션 이름은 위에서 설명한 옵션 플래그 이름 중 아무거나 될 수 있어요. 예제의 doctest 지시어는 그 단일 예제에 대해 doctest 동작을 수정해요. +로 이름이 붙은 동작을 활성화하고 -로 비활성화해요.

예를 들어 이 테스트는 통과해요.

>>> print(list(range(20)))  # doctest: +NORMALIZE_WHITESPACE
[0,   1,  2,  3,  4,  5,  6,  7,  8,  9,
10,  11, 12, 13, 14, 15, 16, 17, 18, 19]

지시어가 없으면 실제 출력에 한 자리 목록 요소 앞의 빈칸 두 개가 없고 출력이 한 줄이므로 실패해요. 이 테스트도 통과하며 지시어가 필요해요.

>>> print(list(range(20)))  # doctest: +ELLIPSIS
[0, 1, ..., 18, 19]

하나의 물리적 줄에 여러 지시어를 쉼표로 구분해 쓸 수 있어요.

>>> print(list(range(20)))  # doctest: +ELLIPSIS, +NORMALIZE_WHITESPACE
[0,    1, ...,   18,    19]

단일 예제에 여러 지시어 주석을 쓰면 결합돼요.

>>> print(list(range(20)))  # doctest: +ELLIPSIS
...                         # doctest: +NORMALIZE_WHITESPACE
[0,    1, ...,   18,    19]

앞의 예제가 보여 주듯 지시어만 담은 ... 줄을 예제에 추가할 수 있어요. 예제가 길어 지시어가 같은 줄에 편안히 오기 어려울 때 유용해요. 모든 옵션은 기본적으로 비활성이고 지시어는 그것이 나타나는 예제에만 적용되므로, 지시어의 +로 옵션을 활성화하는 게 보통 유일하게 의미 있는 선택이에요. 하지만 옵션 플래그를 doctest를 실행하는 함수에 전달해 다른 기본값을 설정할 수도 있는데, 그 경우 지시어의 -로 옵션을 비활성화하는 게 유용할 수 있어요.

경고 (Warnings) — doctest는 예상 출력에서 정확한 일치를 요구하는 데 진지해요. 단일 문자가 일치하지 않아도 테스트는 실패해요. Python이 출력에 대해 정확히 무엇을 보장하고 무엇을 보장하지 않는지 배우면서 몇 번 놀라게 될 거예요. 예를 들어 집합을 출력할 때 Python은 요소가 어떤 특정 순서로 출력된다고 보장하지 않아서, 이런 테스트는 취약해요.

>>> foo()
{"spam", "eggs"}

한 가지 해결책은 이렇게 하는 거예요.

>>> foo() == {"spam", "eggs"}
True

또는 이렇게요.

>>> d = sorted(foo())
>>> d
['eggs', 'spam']

객체 주소를 포함한 것을 출력하는 것도 나쁜 생각이에요.

>>> id(1.0)  # certain to fail some of the time
7948648
>>> class C: pass
>>> C()  # the default repr() for instances embeds an address
<C object at 0x00AC18F0>

마지막 예제에는 ELLIPSIS 지시어가 좋은 접근이에요.

>>> C()  # doctest: +ELLIPSIS
<C object at 0x...>

부동소수점 숫자도 플랫폼에 따라 작은 출력 변동이 있기 쉬워요. Python이 일부 부동소수점 계산을 플랫폼 C 라이브러리에 맡기고, C 라이브러리는 여기서 품질이 크게 다르거든요.

>>> 1000**0.1  # risky
1.9952623149688797
>>> round(1000**0.1, 9) # safer
1.995262315
>>> print(f'{1000**0.1:.4f}') # much safer
1.9953

I/2.**J 형태의 숫자는 모든 플랫폼에서 안전한데, 저는 종종 그런 형태의 숫자를 만들도록 doctest 예제를 꾸며요. 간단한 분수는 사람이 이해하기도 더 쉬워서 더 나은 문서를 만들어요.

기본 API

testmod()testfile() 함수는 대부분의 기본 용도에 충분한 doctest 간단 인터페이스를 제공해요.

doctest.testfile(filename, module_relative=True, name=None, package=None, globs=None, verbose=None, report=True, optionflags=0, extraglobs=None, raise_on_error=False, parser=DocTestParser(), encoding=None) — 이름이 filename인 파일의 예제를 테스트하고 (failure_count, test_count)를 반환해요. filename을 제외한 모든 인자는 선택이며 키워드 형식으로 지정해야 해요.

  • module_relativeTrue(기본)이면 filename은 OS 독립적인 모듈-상대 경로를 지정해요. 기본적으로 호출 모듈 디렉터리를 기준으로 하지만, package 인자가 지정되면 그 패키지를 기준으로 해요. OS 독립성을 위해 / 문자로 경로 세그먼트를 구분해야 하고 절대 경로(즉 /로 시작)면 안 돼요. module_relativeFalsefilename은 OS 특정 경로를 지정하는데 절대·상대일 수 있고, 상대 경로는 현재 작업 디렉터리 기준으로 해결돼요.
  • name — 테스트의 이름. 기본 또는 None이면 os.path.basename(filename)이 사용돼요.
  • package — Python 패키지 또는 그 이름. 모듈-상대 파일 이름의 기본 디렉터리로 쓸 디렉터리예요. module_relativeFalsepackage를 지정하는 건 오류예요.
  • globs — 예제 실행 시 globals로 쓸 dict. doctest용으로 이 dict의 새 얕은 사본이 만들어져 예제가 깨끗한 출발로 시작해요. 기본 또는 None이면 새 빈 dict가 사용돼요.
  • extraglobs — 예제 실행에 쓰는 globals에 병합되는 dict. dict.update()처럼 동작하고, globsextraglobs가 공통 키를 가지면 extraglobs의 값이 결합된 dict에 나타나요. doctest의 매개변수화를 허용하는 고급 기능이에요. 기본 클래스의 doctest를 클래스의 일반 이름으로 쓰고, 일반 이름을 테스트할 서브클래스에 매핑하는 extraglobs dict를 전달해 얼마든지 서브클래스를 테스트하도록 재사용할 수 있어요.
  • verbose — 참이면 많이 출력하고 거짓이면 실패만 출력해요. 기본 또는 None이면 '-v'sys.argv에 있을 때만 참이에요.
  • report — 참이면 끝에 요약을 출력하고, 그렇지 않으면 아무것도 출력하지 않아요.
  • optionflags — (기본 0) 옵션 플래그의 비트 OR.
  • raise_on_error — 기본 false. 참이면 첫 실패나 예제의 예기치 않은 예외에서 예외가 발생해요. 기본 동작은 예제를 계속 실행하는 거예요.
  • parser — 파일에서 테스트를 추출할 DocTestParser(또는 서브클래스). 기본은 일반 파서(DocTestParser())예요.
  • encoding — 파일을 유니코드로 변환하는 데 쓸 인코딩.

doctest.testmod(m=None, name=None, globs=None, verbose=None, report=True, optionflags=0, extraglobs=None, raise_on_error=False, exclude_empty=False)m(또는 m이 제공되지 않거나 None이면 모듈 __main__)에서 도달할 수 있는 함수·클래스의 docstring에 있는 예제를 테스트해요. m.__doc__에서 시작해요. m.__test__ dict가 있으면 그로부터 도달할 수 있는 예제도 테스트해요. (failure_count, test_count)를 반환해요. name은 모듈 이름(기본 m.__name__)이고, exclude_empty가 참이면 doctest가 발견되지 않은 객체는 고려에서 제외돼요(기본은 하위 호환 해킹). extraglobs, verbose, report, optionflags, raise_on_error, globstestfile()과 같지만 globs는 기본적으로 m.__dict__예요.

doctest.run_docstring_examples(f, globs, verbose=False, name='NoName', compileflags=None, optionflags=0) — 객체 f와 연관된 예제를 테스트해요. f는 문자열·모듈·함수·클래스 객체일 수 있어요. globs의 얕은 사본이 실행 컨텍스트로 쓰여요. name은 실패 메시지에 쓰이고 기본 "NoName". verbose가 참이면 실패가 없어도 출력이 생성돼요(기본은 실패 시에만). compileflags는 예제 실행 시 Python 컴파일러가 쓸 플래그 집합이고 기본은 globs에서 찾은 미래 기능(future features) 집합에 해당하는 플래그로 추론돼요. optionflagstestfile()과 같게 동작해요.

Unittest API

doctest된 모듈 모음이 커지면 모든 doctest를 체계적으로 실행하는 방법을 원할 거예요. doctest는 doctest를 담은 모듈과 텍스트 파일에서 unittest 테스트 스위트를 만드는 데 쓸 수 있는 두 함수를 제공해요. unittest 테스트 발견(discovery)과 통합하려면 테스트 모듈에 load_tests 함수를 포함하세요.

import unittest
import doctest
import my_module_with_doctests

def load_tests(loader, tests, ignore):
    tests.addTests(doctest.DocTestSuite(my_module_with_doctests))
    return tests

*doctest.DocFileSuite(paths, module_relative=True, package=None, setUp=None, tearDown=None, globs=None, optionflags=0, parser=DocTestParser(), encoding=None) — 하나 이상의 텍스트 파일에서 doctest 테스트를 unittest.TestSuite로 변환해요. 반환된 unittest.TestSuite는 unittest 프레임워크가 실행하고 각 파일의 대화형 예제를 실행해요. 파일의 어떤 예제가 실패하면 합성된 유닛 테스트가 실패하고, 테스트를 담은 파일 이름과 (때로는 근사한) 줄 번호를 보여 주는 failureException이 발생해요. 모든 예제가 건너뛰어지면 합성된 유닛 테스트도 건너뛰어졌다고 표시돼요.

검사할 텍스트 파일에 하나 이상의 경로(문자열)를 전달해요. module_relative·packagetestfile()과 같이 동작해요. setUp/tearDown은 테스트 스위트용 설정·해제 함수로, 각 파일의 테스트 실행 전후에 호출돼요. 각 함수에는 DocTest 객체가 전달되고 테스트 globals에 globs 속성으로 접근할 수 있어요. globs는 테스트의 초기 전역 변수를 담은 딕셔너리예요(각 테스트마다 새 사본). optionflags는 개별 옵션 플래그를 OR해 만든 기본 doctest 옵션. parserencodingtestfile()과 같아요. DocFileSuite()로 텍스트 파일에서 로드된 doctest에 제공되는 globals에는 전역 __file__이 추가돼요.

doctest.DocTestSuite(module=None, globs=None, extraglobs=None, test_finder=None, setUp=None, tearDown=None, optionflags=0, checker=None) — 모듈의 doctest 테스트를 unittest.TestSuite로 변환해요. 각 docstring은 별도의 유닛 테스트로 실행돼요. doctest가 실패하면 합성된 유닛 테스트가 실패하고 파일 이름과 줄 번호를 보여 주는 failureException이 발생해요.

  • module — 테스트할 모듈. 모듈 객체나 (점으로 구분될 수 있는) 모듈 이름. 지정하지 않으면 이 함수를 호출하는 모듈을 사용해요.
  • globs — 테스트의 초기 전역 변수 딕셔너리. 기본은 모듈의 __dict__.
  • extraglobsglobs에 병합되는 추가 전역 변수 집합.
  • test_finder — 모듈에서 doctest를 추출하는 데 쓰는 DocTestFinder 객체(또는 대체물).
  • setUp, tearDown, optionflagsDocFileSuite()와 같지만 각 docstring마다 호출돼요.

이 함수는 testmod()와 같은 검색 기법을 사용해요. (3.5에서 모듈에 docstring이 없으면 ValueError 대신 빈 unittest.TestSuite를 반환하도록 변경.) 내부적으로 DocTestSuite()doctest.DocTestCase 인스턴스로 unittest.TestSuite를 만들고, DocTestCaseunittest.TestCase의 서브클래스예요. DocFileSuite()doctest.DocFileCase 인스턴스로 unittest.TestSuite를 만들고, DocFileCaseDocTestCase의 서브클래스예요.

두 방식 모두 DocTestCase 인스턴스를 실행해요. 이는 미묘한 이유로 중요해요. doctest 함수를 직접 실행할 때는 옵션 플래그를 전달해 쓰는 doctest 옵션을 직접 제어할 수 있어요. 하지만 unittest 프레임워크를 쓴다면 unittest가 궁극적으로 테스트를 언제·어떻게 실행할지 제어해요. 프레임워크 작성자는 보통 doctest 보고 옵션을 제어하고 싶어 하는데, unittest를 통해 doctest 테스트 러너에 옵션을 전달할 방법은 없어요.

doctest.set_unittest_reportflags(flags) — 사용할 doctest 보고 플래그를 설정해요. flags 인자는 옵션 플래그의 비트 OR이고 "보고 플래그"만 사용할 수 있어요. 모듈-전역 설정이며 이후 module unittest가 실행하는 모든 doctest에 영향을 줘요. DocTestCaserunTest() 메서드는 DocTestCase 인스턴스가 구성될 때 테스트 케이스에 지정된 옵션 플래그를 봐요. 보고 플래그가 지정되지 않았다면 doctest의 unittest 보고 플래그가 옵션 플래그에 비트 OR되고, 그렇게 증강된 옵션 플래그가 doctest를 실행하는 DocTestRunner 인스턴스에 전달돼요. 이 함수를 호출하기 전에 유효했던 unittest 보고 플래그 값을 반환해요.

고급 API

기본 API는 doctest를 쉽게 쓰게 하려는 단순 래퍼예요. 꽤 유연해서 대부분 사용자의 요구를 충족하지만, 테스트에 대한 더 세밀한 제어가 필요하거나 doctest의 능력을 확장하고 싶다면 고급 API를 사용해야 해요. 고급 API는 doctest 케이스에서 추출한 대화형 예제를 저장하는 두 컨테이너 클래스를 중심으로 해요.

  • Example — 단일 Python 문과 그 예상 출력의 쌍.
  • DocTest — 보통 단일 docstring이나 텍스트 파일에서 추출한 Example의 모음.

추가 처리 클래스는 doctest 예제를 찾고(found)·파싱하고(parse)·실행하고(run)·검사(check)하도록 정의돼요.

  • DocTestFinder — 주어진 모듈에서 모든 docstring을 찾고, DocTestParser로 대화형 예제를 담은 각 docstring에서 DocTest를 만든다.
  • DocTestParser — 문자열(객체의 docstring 같은)에서 DocTest 객체를 만든다.
  • DocTestRunnerDocTest의 예제를 실행하고, OutputChecker로 출력을 검증한다.
  • OutputChecker — doctest 예제의 실제 출력과 예상 출력을 비교해 일치 여부를 결정한다.

이 처리 클래스들의 관계는 다음 도표로 요약돼요.

                            list of:
+------+                   +---------+
|module| --DocTestFinder-> | DocTest | --DocTestRunner-> results
+------+    |        ^     +---------+     |       ^    (printed)
            |        |     | Example |     |       |
            v        |     |   ...   |     v       |
           DocTestParser   | Example |   OutputChecker
                           +---------+

DocTest 객체 — class doctest.DocTest(examples, globs, name, filename, lineno, docstring) — 단일 네임스페이스에서 실행해야 하는 doctest 예제의 모음. 생성자 인자는 같은 이름의 속성을 초기화하는 데 쓰이고 직접 수정하면 안 돼요.

  • examples — 이 테스트가 실행할 개별 대화형 Python 예제를 인코딩하는 Example 객체의 리스트.
  • globs — 예제를 실행할 네임스페이스(즉 globals). 이름을 값에 매핑하는 딕셔너리. 예제가 만든 네임스페이스 변경(새 변수 바인딩 같은)은 테스트 실행 후 globs에 반영돼요.
  • name — DocTest 식별 문자열. 보통 테스트를 추출한 객체·파일의 이름.
  • filename — 이 DocTest를 추출한 파일 이름, 또는 파일 이름을 모르거나 파일에서 추출되지 않았으면 None.
  • linenofilename 내에서 DocTest가 시작하는 줄 번호(파일 시작 기준 0 기반), 또는 사용 불가하면 None.
  • docstring — 테스트를 추출한 문자열, 또는 사용 불가하거나 문자열에서 추출되지 않았으면 None.

Example 객체 — class doctest.Example(source, want, exc_msg=None, lineno=0, indent=0, options=None) — Python 문과 그 예상 출력으로 이루어진 단일 대화형 예제. 생성자 인자는 같은 이름의 속성을 초기화하고 직접 수정하면 안 돼요.

  • source — 예제의 소스 코드 문자열. 단일 Python 문으로 구성되고 항상 새 줄로 끝나요(생성자가 필요 시 추가).
  • want — 예제의 소스 코드 실행의 예상 출력(stdout에서, 또는 예외의 경우 트레이스백). 예상 출력이 없으면 빈 문자열이에요.
  • exc_msg — 예제가 예외를 생성할 것으로 예상되면 그 예외 메시지, 아니면 None. 이 메시지는 traceback.format_exception_only()의 반환 값과 비교돼요.
  • lineno — 이 예제를 담은 문자열 내에서 예제가 시작하는 줄 번호.
  • indent — 포함 문자열에서 예제의 들여쓰기, 즉 예제의 첫 프롬프트 앞에 오는 공백 문자 수.
  • options — 옵션 플래그를 True·False에 매핑하는 딕셔너리. 이 예제의 기본 옵션을 재정의하는 데 쓰여요. 딕셔너리에 없는 옵션 플래그는 기본값(DocTestRunneroptionflags)으로 남아요.

DocTestFinder 객체 — class doctest.DocTestFinder(verbose=False, parser=DocTestParser(), recurse=True, exclude_empty=True) — 주어진 객체에 관련된 DocTest를 그 docstring과 포함된 객체의 docstring에서 추출하는 처리 클래스. DocTest는 모듈·클래스·함수·메서드·staticmethod·classmethod·property에서 추출할 수 있어요. verbose는 파인더가 검색하는 객체를 표시하는 데 쓸 수 있고(기본 False). parser는 docstring에서 doctest를 추출할 DocTestParser 객체(또는 대체물). recurse가 false면 DocTestFinder.find()는 주어진 객체만 검사하고 포함 객체는 검사하지 않아요. exclude_empty가 false면 빈 docstring을 가진 객체도 테스트에 포함해요.

find(obj[, name][, module][, globs][, extraglobs])obj의 docstring이나 포함된 객체의 docstring으로 정의된 DocTest 리스트를 반환해요. name은 객체의 이름을 지정하고 반환된 DocTest의 이름을 만드는 데 쓰여요(기본 obj.__name__). module은 주어진 객체를 담은 모듈인데, 지정하지 않거나 None이면 파인더가 올바른 모듈을 자동 결정하려 해요. 모듈은 기본 네임스페이스(globs가 지정되지 않으면), 다른 모듈에서 임포트된 객체에서 DocTest를 추출하지 못하게 막기, 객체를 담은 파일 이름 찾기, 객체의 줄 번호 찾기에 사용돼요. moduleFalse면 모듈을 찾으려 하지 않아요. 각 DocTest의 globals는 globsextraglobs를 결합해 만들어져요(extraglobs의 바인딩이 globs를 재정의). 지정되지 않으면 globs는 모듈의 __dict__(모듈이 지정된 경우) 또는 {}로, extraglobs{}로 기본 설정돼요.

DocTestParser 객체 — class doctest.DocTestParser — 문자열에서 대화형 예제를 추출해 DocTest 객체를 만드는 처리 클래스.

  • get_doctest(string, globs, name, filename, lineno) — 주어진 문자열에서 모든 doctest 예제를 추출해 DocTest 객체로 모아요. globs·name·filename·lineno는 새 DocTest 객체의 속성이에요.
  • get_examples(string, name='<string>') — 주어진 문자열에서 모든 doctest 예제를 추출해 Example 객체 리스트로 반환해요. 줄 번호는 0 기반. name은 이 문자열을 식별하는 데만 쓰여요.
  • parse(string, name='<string>') — 주어진 문자열을 예제와 그 사이의 텍스트로 나누고, 교대하는 Example과 문자열 리스트로 반환해요. Example의 줄 번호는 0 기반.

TestResults 객체 — class doctest.TestResults(failed, attempted)failed(실패한 테스트 수), attempted(시도한 테스트 수), skipped(건너뛴 테스트 수) 속성. skipped버전 3.13에 추가됨.

DocTestRunner 객체 — class doctest.DocTestRunner(checker=None, verbose=None, optionflags=0) — DocTest의 대화형 예제를 실행·검증하는 처리 클래스. 예상 출력과 실제 출력의 비교는 OutputChecker가 하고, 여러 옵션 플래그로 사용자 정의할 수 있어요. 테스트 러너의 표시 출력은 두 가지로 제어할 수 있어요. 첫째, 출력 함수를 run()에 전달할 수 있는데 이 함수가 표시할 문자열로 호출돼요(기본 sys.stdout.write). 둘째, DocTestRunner를 서브클래싱하고 report_start(), report_success(), report_unexpected_exception(), report_failure() 메서드를 재정의해 표시 출력을 사용자 정의할 수 있어요.

  • checker — 예상 출력과 실제 출력을 비교할 OutputChecker 객체(또는 대체물).
  • verboseTrue면 각 예제에 대한 정보가 실행되며 출력되고, False면 실패만 출력돼요. 지정되지 않거나 None이면 -v 스위치가 쓰일 때만 상세 출력을 써요.
  • optionflags — 테스트 러너가 예상 출력과 실제 출력을 비교하는 방식과 실패 표시 방식을 제어해요.

테스트 러너는 통계를 누적해요. 시도·실패·건너뜀 예제의 집계 수는 tries, failures, skips 속성으로도 쓸 수 있어요. run()summarize() 메서드는 TestResults 인스턴스를 반환해요.

메서드:

  • report_start(out, test, example) — 테스트 러너가 주어진 예제를 처리하려 한다고 보고. 직접 호출하면 안 돼요.
  • report_success(out, test, example, got) — 주어진 예제가 성공적으로 실행됐다고 보고.
  • report_failure(out, test, example, got) — 주어진 예제가 실패했다고 보고.
  • report_unexpected_exception(out, test, example, exc_info) — 주어진 예제가 예기치 않은 예외를 일으켰다고 보고. exc_info는 예기치 않은 예외에 대한 정보를 담은 튜플(sys.exc_info()가 반환).
  • run(test, compileflags=None, out=None, clear_globs=True)test(DocTest 객체)의 예제를 실행하고 writer 함수 out으로 결과를 표시. TestResults 인스턴스를 반환. 예제는 test.globs 네임스페이스에서 실행돼요. clear_globs가 참(기본)이면 테스트 후 이 네임스페이스를 지워 가비지 컬렉션을 돕고, clear_globs=False로 하면 테스트 완료 후 네임스페이스를 검사할 수 있어요.
  • summarize(verbose=None) — 이 DocTestRunner가 실행한 모든 테스트 케이스의 요약을 출력하고 TestResults 인스턴스를 반환.

DocTestParser의 속성: tries(시도한 예제 수), failures(실패한 예제 수), skips(건너뛴 예제 수, 3.13).

OutputChecker 객체 — class doctest.OutputChecker — doctest 예제의 실제 출력이 예상 출력과 일치하는지 검사하는 클래스. 두 메서드를 정의해요.

  • check_output(want, got, optionflags) — 예제의 실제 출력(got)이 예상 출력(want)과 일치하면 True를 반환. 문자열이 동일하면 항상 일치로 간주되고, 테스트 러너가 쓰는 옵션 플래그에 따라 여러 비-정확 일치 유형도 가능해요.
  • output_difference(example, got, optionflags) — 주어진 예제(example)의 예상 출력과 실제 출력(got)의 차이를 설명하는 문자열을 반환.

디버깅

Doctest는 doctest 예제를 디버깅하는 여러 메커니즘을 제공해요. 여러 함수가 doctest를 실행 가능한 Python 프로그램으로 변환하는데, Python 디버거 pdb 아래에서 실행할 수 있어요. DebugRunner 클래스는 DocTestRunner의 서브클래스로, 첫 실패 예제에서 그 예제에 대한 정보를 담은 예외를 발생시켜요. 이 정보로 예제에 대한 사후(post-mortem) 디버깅을 수행할 수 있어요. DocTestSuite()가 만든 unittest 케이스는 unittest.TestCase가 정의한 debug() 메서드를 지원해요.

doctest 예제에 pdb.set_trace() 호출을 추가할 수도 있어요. 그 줄이 실행될 때 Python 디버거로 떨어지고 변수의 현재 값을 검사할 수 있어요. 예를 들어 a.py가 이 모듈 docstring만 담고 있다고 해볼게요.

"""
>>> def f(x):
...     g(x*2)
>>> def g(x):
...     print(x+3)
...     import pdb; pdb.set_trace()
>>> f(3)
9
"""

그러면 대화형 Python 세션이 이렇게 보일 수 있어요.

>>> import a, doctest
>>> doctest.testmod(a)
--Return--
> <doctest a[1]>(3)g()->None
-> import pdb; pdb.set_trace()
(Pdb) list
  1     def g(x):
  2         print(x+3)
  3  ->     import pdb; pdb.set_trace()
[EOF]
(Pdb) p x
6
(Pdb) step
--Return--
> <doctest a[0]>(2)f()->None
-> g(x*2)
(Pdb) list
  1     def f(x):
  2  ->     g(x*2)
[EOF]
(Pdb) p x
3
(Pdb) step
--Return--
> <doctest a[2]>(1)?()->None
-> f(3)
(Pdb) cont
(0, 3)
>>>

doctest를 Python 코드로 변환하고 합성된 코드를 디버거에서 실행할 수도 있는 함수들:

  • doctest.script_from_examples(s) — 예제가 있는 텍스트를 스크립트로 변환. s는 doctest 예제를 담은 문자열. 문자열이 Python 스크립트로 변환되는데, s의 doctest 예제는 일반 코드로, 나머지는 전부 Python 주석으로 바뀌어요. 생성된 스크립트를 문자열로 반환해요. 다른 함수가 내부적으로 사용하지만, 대화형 Python 세션을 Python 스크립트로 변환하고 싶을 때도 유용해요.
  • doctest.testsource(module, name) — 객체의 doctest를 스크립트로 변환. module은 대상 객체를 담은 모듈(객체 또는 점으로 구분된 이름). name은 관심 있는 doctest를 가진 (모듈 안의) 객체 이름. 결과는 객체의 docstring을 위의 script_from_examples()처럼 Python 스크립트로 변환한 문자열.
  • doctest.debug(module, name, pm=False) — 객체의 doctest를 디버깅. module·name 인자는 testsource()와 같아요. 이름 지어진 객체의 docstring에 대한 합성 Python 스크립트를 임시 파일에 쓰고, 그 파일을 Python 디버거 pdb의 제어 아래 실행해요. module.__dict__의 얕은 사본을 로컬·전역 실행 컨텍스트 양쪽에 사용해요. pm은 사후 디버깅을 쓸지 제어해요. 참이면 스크립트 파일을 직접 실행하고, 잡히지 않은 예외로 종료될 때만 디버거가 개입해 pdb.post_mortem()으로 사후 디버깅을 호출해요. pm이 지정되지 않거나 거짓이면 스크립트를 시작부터 디버거 아래에서 실행해요.
  • doctest.debug_src(src, pm=False, globs=None) — 문자열의 doctest를 디버깅. src 인자로 doctest 예제를 담은 문자열을 직접 지정하는 것 외엔 debug()와 같아요. globs는 로컬·전역 실행 컨텍스트 양쪽으로 쓸 딕셔너리예요.

DebugRunner 클래스와 그것이 발생시킬 수 있는 특수 예외는 주로 테스트 프레임워크 작성자에게 흥미롭고, 여기서는 간단히만 다룰게요. 자세한 내용은 소스 코드, 특히 DebugRunner의 docstring(그것도 doctest예요!)을 보세요.

class doctest.DebugRunner(checker=None, verbose=None, optionflags=0) — 실패를 만나는 즉시 예외를 발생시키는 DocTestRunner의 서브클래스. 예기치 않은 예외가 발생하면 테스트·예제·원래 예외를 담은 UnexpectedException 예외가 발생해요. 출력이 일치하지 않으면 테스트·예제·실제 출력을 담은 DocTestFailure 예외가 발생해요.

exception doctest.DocTestFailure(test, example, got) — doctest 예제의 실제 출력이 예상 출력과 일치하지 않았음을 알리기 위해 DocTestRunner가 발생시키는 예외. 속성: test(예제가 실패할 때 실행 중이던 DocTest 객체), example(실패한 Example), got(예제의 실제 출력).

exception doctest.UnexpectedException(test, example, exc_info) — doctest 예제가 예기치 않은 예외를 일으켰음을 알리기 위해 DocTestRunner가 발생시키는 예외. 속성: test(DocTest 객체), example(실패한 Example), exc_info(예기치 않은 예외 정보를 담은 튜플, sys.exc_info()가 반환).

견해 (Soapbox)

소개에서 언급했듯 doctest는 세 가지 주요 용도로 성장했어요. docstring의 예제 검사, 회귀 테스트, 실행 가능한 문서 / 문자적 테스트. 이 용도는 요구 사항이 다르고 구분하는 게 중요해요. 특히 docstring을 난해한 테스트 케이스로 채우는 건 나쁜 문서를 만든다는 점을 기억하세요.

docstring을 쓸 때는 docstring 예제를 신중히 고르세요. 여기엔 배워야 할 예술이 있어요 — 처음엔 자연스럽지 않을 수 있어요. 예제는 문서에 진정한 가치를 더해야 해요. 좋은 예제 하나는 많은 말보다 가치가 있을 수 있죠. 신중히 하면 예제는 사용자에게 매우 소중해지고, 세월이 흐르고 사물이 변하면서 그것을 모으는 데 든 시간을 몇 배로 돌려받아요. "무해한" 변경 후 내 doctest 예제 중 하나가 동작을 멈추는 걸 얼마나 자주 보는지 아직도 놀라워요.

Doctest는 회귀 테스트에도 훌륭한 도구예요, 설명 텍스트를 아끼지 않는다면 말이죠. 산문과 예제를 섞으면 실제로 테스트되는 것이 무엇이고 왜인지 추적하기가 훨씬 쉬워져요. 테스트가 실패하면 좋은 산문이 문제가 무엇이고 어떻게 고쳐야 하는지 파악하기 훨씬 쉽게 해 줘요. 코드 기반 테스트에 방대한 주석을 쓸 수는 있지만, 그렇게 하는 프로그래머는 거의 없어요. 많은 사람이 대신 doctest 방식을 쓰면 훨씬 명확한 테스트가 된다는 걸 발견했어요. 아마 doctest가 코드를 쓰는 것보다 산문을 쓰는 걸 조금 더 쉽게 만들기 때문일 거예요. doctest 기반 테스트를 쓸 때의 자연스러운 태도는 소프트웨어의 정확한 점을 설명하고 예제로 그것을 예시하고 싶어 하는 거예요. 이는 자연스럽게 가장 단순한 기능으로 시작해 논리적으로 복잡성과 경계 케이스로 나아가는 테스트 파일로 이어져요. 그 결과는 무작위로 보이는 기능 조각을 테스트하는 고립된 함수의 모음 대신 일관된 서사(narrative)가 돼요. 어떤 면에선 테스트와 설명의 구분이 흐려지는 거죠.

회귀 테스트는 전용 객체나 파일에 한정하는 게 가장 좋아요. 테스트를 구성하는 여러 옵션이 있어요.

  • 테스트 케이스를 대화형 예제로 담은 텍스트 파일을 쓰고, testfile()이나 DocFileSuite()로 파일을 테스트하세요. 이것이 권장되는데, 처음부터 doctest를 쓰도록 설계된 새 프로젝트에서 하기 가장 쉬워요.
  • 이름 지어진 주제에 대한 테스트 케이스를 담은 단일 docstring으로 구성된 _regrtest_topic이라는 함수를 정의하세요. 이 함수는 모듈과 같은 파일에 포함하거나 별도의 테스트 파일로 분리할 수 있어요.
  • 회귀 테스트 주제를 테스트 케이스를 담은 docstring에 매핑하는 __test__ 딕셔너리를 정의하세요.

테스트를 모듈에 배치했으면 모듈 자체가 테스트 러너가 될 수 있어요. 테스트가 실패하면 테스트 러너가 문제를 디버깅하는 동안 실패한 doctest만 다시 실행하도록 정리할 수 있어요. 그런 테스트 러너의 최소 예시를 볼게요.

if __name__ == '__main__':
    import doctest
    flags = doctest.REPORT_NDIFF|doctest.FAIL_FAST
    if len(sys.argv) > 1:
        name = sys.argv[1]
        if name in globals():
            obj = globals()[name]
        else:
            obj = __test__[name]
        doctest.run_docstring_examples(obj, globals(), name=name,
                                       optionflags=flags)
    else:
        fail, total = doctest.testmod(optionflags=flags)
        print(f"{fail} failures out of {total} tests")

각주: 예상 출력과 예외를 모두 담은 예제는 지원되지 않아요. 어디서 하나가 끝나고 다른 하나가 시작하는지 추측하는 건 오류가 나기 너무 쉽고 테스트도 혼란스럽게 만들거든요.