Doctest를 이용한 파이썬스러운 코드 문서화 및 테스트
Daniel Hayes
Full-Stack Engineer · Leapcell

소개
빠르게 변화하는 소프트웨어 개발 세계에서 코드 품질을 보장하고 최신 문서를 유지하는 것은 매우 중요합니다. 종종 이러한 두 가지 중요한 측면은 별개이며 때로는 상충되는 고려 사항으로 취급됩니다. 개발자는 코드와 분리된 광범위한 테스트 스위트를 작성하고, 지속적인 동기화가 필요한 문서를 작성할 수 있습니다. 이러한 전통적인 접근 방식은 코드베이스가 발전함에 따라 문서가 오래되고 테스트가 덜 효과적이게 만드는 결과를 초래할 수 있습니다.
여러분의 문서가 곧 테스트가 되는 세상을 상상해 보세요. 사용자에게 보여주는 예제가 자동으로 정확성을 검증하고, 코드 문서를 작성하는 행위가 본질적으로 신뢰성을 강화하는 세상입니다. 이것은 몽상이 아니라 Python의 강력한 doctest
모듈 덕분에 현실이 되었습니다. 테스트 케이스를 docstring에 직접 원활하게 통합함으로써, doctest
는 자체 문서화되고 자체 테스트하는 코드를 만드는 독특하고 매우 효과적인 방법을 제공합니다. 이 글에서는 doctest
를 사용하여 Python 개발 워크플로우를 향상시키는 원리, 구현 및 실제 이점에 대해 자세히 살펴보겠습니다.
문서화와 테스트의 시너지
doctest
의 구체적인 내용을 살펴보기 전에, 그 철학의 근간을 이루는 몇 가지 핵심 개념을 명확히 하겠습니다.
-
Docstrings: Python에서 docstring은 모듈, 함수, 클래스 또는 메서드 정의의 첫 번째 문장으로 나타나는 문자열 리터럴입니다. 코드의 기능, 인수 및 반환 값에 대한 설명을 제공하는 인라인 문서 역할을 합니다.
__doc__
속성을 통해 액세스할 수 있으며 Sphinx와 같은 도구에서 API 문서를 생성하는 데 널리 사용됩니다. -
Test-Driven Development (TDD): 실제 코드를 작성하기 전에 테스트를 작성하는 소프트웨어 개발 프로세스입니다. 이 접근 방식은 코드가 지정된 요구 사항을 충족하도록 보장하고 강력하고 모듈화된 구성 요소를 설계하는 데 도움이 됩니다.
-
Unit Testing: 소프트웨어 응용 프로그램의 개별 단위 또는 구성 요소를 격리하여 테스트하는 관행입니다. 목표는 소프트웨어의 각 단위가 설계된 대로 작동하는지 확인하는 것입니다.
doctest
는 함수 또는 모듈 docstring 내에 예제 사용법과 예상 출력을 직접 포함할 수 있도록 함으로써 docstring과 단위 테스트 간의 격차를 해소합니다. 이러한 예제는 실행 가능한 테스트 역할을 합니다.
Doctest 작동 방식
doctest
모듈은 docstring에서 대화형 Python 세션처럼 보이는 텍스트를 검색하여 작동합니다. 구체적으로 >>>
(Python 대화형 프롬프트)로 시작하는 줄을 찾고 해당 대화형 명령에 대한 예상 출력을 다음 줄로 취급합니다.
doctest
가 실행될 때 >>>
프롬프트 뒤에 오는 코드를 실행하고 실제 출력을 docstring에 제공된 예상 출력과 비교합니다. 일치하면 테스트가 통과하고, 그렇지 않으면 실패합니다.
간단한 예제로 이를 설명해 보겠습니다.
def add(a, b): """ 두 숫자를 더하고 합계를 반환합니다. >>> add(2, 3) 5 >>> add(10, -5) 5 >>> add(0, 0) 0 """ return a + b
이 add
함수에서 docstring에는 세 가지 별도의 테스트 케이스가 포함되어 있습니다. 각 케이스는 add
를 호출하는 방법과 해당 반환 값이 어떻게 되어야 하는지를 보여줍니다.
이러한 테스트를 실행하려면 스크립트 끝에 간단한 블록을 추가할 수 있습니다.
import doctest def add(a, b): """ 두 숫자를 더하고 합계를 반환합니다. >>> add(2, 3) 5 >>> add(10, -5) 5 >>> add(0, 0) 0 """ return a + b if __name__ == "__main__": doctest.testmod()
이 스크립트를 실행하면: python your_module.py
, doctest.testmod()
는 현재 모듈의 모든 doctest를 자동으로 찾아 실행합니다. 모든 테스트가 통과하면 출력은 다음과 유사하게 나타납니다.
$ python your_module.py
(기본적으로 성공은 출력이 없음을 나타냅니다)
테스트가 실패하면 doctest
는 불일치를 보고합니다.
하나를 의도적으로 망가뜨려 보겠습니다.
def add(a, b): """ 두 숫자를 더하고 합계를 반환합니다. >>> add(2, 3) 6 """ return a + b if __name__ == "__main__": doctest.testmod()
이것을 실행하면 다음과 같은 결과가 나타납니다.
$ python your_module.py
**********************************************************************
File "your_module.py", line 5, in __main__.add
Failed example:
add(2, 3)
Expected:
6
Got:
5
**********************************************************************
1 items had failures:
1 of 1 in __main__.add
***Test Failed*** 1 failures.
고급 기능 및 지시문
doctest
는 기본 프롬프트-출력 일치 이상의 기능을 제공합니다. 예외, 정렬되지 않은 출력 또는 부동 소수점 비교와 같은 상황을 처리하기 위해 특수 지시문을 사용할 수 있습니다.
-
ELLIPSIS
: 출력의 일부가 달라질 것으로 예상되는 경우(예: 메모리 주소 또는 객체 ID) 유용합니다. 어떤 부분 문자열과도 일치시키려면 예상 출력에...
을 사용합니다.def get_object_info(obj): """ 객체의 유형 및 ID 문자열 표현을 반환합니다. >>> get_object_info(1) "<class 'int'> at 0x..." """ return f"{type(obj)} at {hex(id(obj))}"
이것을 활성화하려면
doctest.testmod()
에optionflags=doctest.ELLIPSIS
를 전달해야 합니다. -
예상 예외: 테스트 케이스에서 예외가 발생할 것으로 예상되는 경우 직접 지정할 수 있습니다.
def divide(a, b): """ 두 숫자를 나눕니다. >>> divide(10, 2) 5.0 >>> divide(10, 0) Traceback (most recent call last): ... ZeroDivisionError: division by zero """ return a / b
-
NORMALIZE_WHITESPACE
: 예상 출력과 실제 출력의 공백(공백, 탭, 새 줄) 차이를 무시합니다.def format_list(items): """ 항목 목록을 문자열로 형식 지정합니다. >>> format_list(['apple', 'banana', 'cherry']) # doctest: +NORMALIZE_WHITESPACE "Items: - apple - banana - cherry" """ return "Items:\n" + "\n".join(f"- {item}" for item in items)
이러한 플래그는 testmod()
에 대해 전역적으로 또는 doctest: +FLAG
를 사용하여 로컬로 활성화할 수 있습니다.
적용 시나리오
doctest
는 여러 시나리오에서 빛을 발합니다.
- 문서 내 예제: 주된 사용 사례입니다. docstring에 표시된 모든 예제 사용법이 항상 정확하고 최신 상태임을 보장합니다. 이는 문서에 대한 신뢰를 구축합니다.
- 간단한 함수 테스트: 작고 잘 정의된 함수에 대해
doctest
는 별도의unittest
또는pytest
스위트를 설정하지 않고도 기대되는 동작을 캡처하는 빠르고 효과적인 방법이 될 수 있습니다. - 교육 자료: 튜토리얼을 작성하거나 Python을 가르칠 때
doctest
는 학생들에게 제공되는 코드 조각이 실행 가능하고 광고된 결과를 생성하는지 확인합니다. - 빠른 회귀 검사: 버그가 수정되면 관련 함수의 docstring에 새로운
doctest
를 추가하여 버그가 다시 발생하는 것을 방지할 수 있으며, 이는 간단하지만 효과적인 회귀 테스트 역할을 합니다.
모범 사례 및 한계
강력하지만 doctest
는 포괄적인 단위 테스트 프레임워크인 unittest
또는 pytest
를 대체하지는 않습니다.
doctest
를 사용해야 할 때:
- 간단하고 설명적인 예제.
- 함수, 메서드 및 클래스의 공개 API 테스트.
- 테스트 케이스가 실행 가능한 예제에 자연스럽게 맞을 때.
다른 프레임워크를 사용해야 할 때의 한계:
- 복잡한 설정:
doctest
는 데이터베이스, 외부 서비스 또는 복잡한 객체 그래프를 모킹하는 것과 같이 광범위한 설정이 필요한 테스트에 이상적이지 않습니다. - 픽스처 관리: 다른 프레임워크는 테스트 환경을 초기화하기 위한 강력한 픽스처 관리 기능을 제공합니다.
- 매개변수화된 테스트: 다른 입력으로 동일한 테스트 논리를 실행하는 것은
pytest
의 매개변수화를 통해 더 쉽습니다. - 성능 테스트:
doctest
는 성능 또는 부하 테스트를 위해 설계되지 않았습니다. - 오류 보고:
doctest
는 실패를 보고하지만, 대규모 프로젝트의 경우pytest
또는unittest
의 상세한 보고 기능을 선호하는 경우가 많습니다.
일반적인 접근 방식은 명확하고 공개적인 예제를 위해 doctest
를 사용하고, 내부적이고 복잡하거나 엣지 케이스 테스트를 위해 pytest
또는 unittest
를 사용하는 것입니다.
결론
doctest
는 문서 문자열에 실행 가능한 예제를 직접 포함함으로써 문서와 테스트를 경량임에도 효과적으로 혼합할 수 있는 방법을 제공하여 "배터리 포함"이라는 Python 철학을 구현합니다. 이를 통해 본질적으로 자체 검증되고 이해하기 쉬운 코드를 만들 수 있습니다. 이 고유한 접근 방식은 회귀 오류를 조기에 포착하여 코드 품질을 향상시킬 뿐만 아니라 문서가 항상 코드의 실제 동작을 반영하도록 하여 유지 보수성을 크게 향상시킵니다. doctest
를 채택하여 문서가 코드의 정확성을 수호하도록 하세요.