brunch

PythonUnittest 활용 테스트 코드 작성가이드

by 아이나비시스템즈
맵스플랫폼 사업팀_브런치 (24).png



Ep.24


테스트의 중요성 및 기존 모듈에 대한 테스트 필요성


소프트웨어 개발 과정을 처음 접하는 개발자라면, Python으로 기능을 완성한 뒤에 “이제 무엇을 해야 할까?” 하고 고민하는 경우가 많습니다. 하지만 코드 구현이 끝났다고 해서 개발이 완전히 마무리된 것은 아닙니다. 프로그램이 의도한 대로 정확히 작동하는지 확인하고, 잠재적인 오류를 찾아내는 테스트 단계가 여전히 남아 있기 때문입니다.


테스트 코드란 이러한 검증을 위해 작성하는 작은 프로그램으로, 쉽게 말해 본 프로그램의 각 기능이 기대한 대로 동작하는지를 자동으로 확인하는 역할을 합니다.


Python에서는 이러한 테스트 과정을 지원하기 위해 표준 라이브러리로 제공되는 unittest 모듈이 널리 활용되고 있으며, 이를 통해 체계적이고 효율적인 테스트 코드 작성과 실행을 할 수 있습니다.


테스트 코드를 작성하면 다음과 같은 주요 장점을 얻을 수 있습니다.


<테스트 코드 작성 시 장점>

유지 보수 편의성 향상

- 코드베이스가 커지고 기능이 복잡해질수록, 작은 수정만으로도 의도치 않은 문제가 발생할 가능성이 커집니다. 이때 테스트 코드는 수정 전후에 핵심 기능이 여전히 정상 작동하는지 자동으로 검증해 줍니다. 특히 수동으로 모든 기능을 확인하느라 걸리는 시간과 노력을 크게 줄여 줍니다. 결과적으로 “이 부분을 고쳤더니 다른 기능이 깨지지는 않았을까?”라는 불안감을 덜고, 안정적으로 코드를 유지·관리할 수 있습니다.

레거시 코드 안정성 확보

- 운영 중인 코드를 흔히 ‘레거시 코드’라고 부릅니다. 레거시 코드는 문서화가 부족하고 변경 이력도 남아 있지 않으며, 주석이 거의 없어서 전체 구조나 개발 의도를 파악하기 어렵습니다. 따라서 이러한 코드를 수정하거나 개선하는 일 자체가 큰 리스크로 여겨지곤 합니다.

- 그러나 테스트 코드가 미리 작성되어 있으면, 레거시 코드를 수정할 때 “원래 의도한 동작이 유지되는지”를 즉시 검증할 수 있습니다. 즉, 레거시 코드에 대대적인 변경을 가하기 전에 기존 핵심 기능이 그대로 작동하는지를 자동으로 확인할 수 있으므로, 코드 품질을 보존하면서 단계적으로 개선할 수 있습니다.

기능 수정/추가 시 기존 동작의 일관성 유지 : 테스트 코드는 기능을 추가하거나 수정할 때 과거에 잘 돌아가던 기능이 망가지는 것을 막아줍니다. 새로운 변경 후에도 기존 기능이 동일하게 동작하는지를 테스트가 검증해 주므로, 문제가 발생하면 즉시 알 수 있습니다.



unittest 기본 사용법과 구조


Python에는 unittest라는 테스트 도구가 기본으로 포함되어 있습니다. 이 도구를 사용하면 우리가 작성한 코드가 제대로 동작하는지 자동으로 확인할 수 있습니다. unittest의 기본 구조는 다음과 같습니다.


<unittest 기본 구조>

unittest.TestCase를 상속받은 클래스를 작성

그 클래스에 이름이 “test”로 시작하는 테스트 메소드를 정의

각 메소드 안에서 기대하는 결과에 대해 assert 메소드를 이용하여 검증


아래 예시를 통해 좀 더 상세히 테스트 과정을 진행해 보도록 하겠습니다.


1. 예제와 unittest 테스트 코드

이제 실제로 간단한 Class를 가정하고, 이에 대한 테스트 코드를 작성하는 과정을 살펴보겠습니다. 예시로, 간단한 계산을 하는 모듈이 있다고 가정합니다.

아래는 Calculator 모듈에 대한 테스트 코드입니다.

이제 test_Calculator.py를 실행하면 각 테스트 메소드가 수행되고, 모든 테스트가 통과한다면 아래와 같은 출력이 나옵니다:

그림3.png

만약 어떤 테스트에서 실패가 발생하면 실패한 테스트의 이름과 AssertionError 메시지가 출력됩니다. 이를 통해 어떤 입력에서 예상과 다른 결과가 나왔는지 파악할 수 있습니다.


2. setUp과 tearDown 활용하기

각 단위 테스트에서 DB 연결, 클래스 초기화 등 동일한 준비 작업이 반복될 때 테스트 코드가 길어지고 유지보수가 어려워집니다. 이럴 때 setUp과 tearDown 메소드를 사용하면 공통 코드를 분리해 중복을 줄이고, 테스트 구조를 명확하게 만들 수 있습니다.


setUp(self)

- 각 테스트 메소드가 실행되기 바로 직전에 자동으로 호출됩니다.

- 환경 초기화(객체 생성, 테스트용 더미 데이터 준비 등)를 작성합니다.

tearDown(self)

- 각 테스트 메소드가 종료된 뒤 호출됩니다.

- DB 연결 해제 등 정리 작업을 위한 코드를 작성합니다.


예를 들어, 테스트마다 같은 인스턴스를 매번 생성하는 코드를 setUp으로 옮기면 테스트 본문을 더 간결하고 집중력 있게 만들 수 있습니다.


3. 테스트 건너뛰기

테스트 과정에서 아직 수정 중이거나, 실행 시간이 오래 걸리는 테스트 케이스가 있다면 skip 데코레이터를 사용하여 해당 테스트를 임시로 제외할 수 있습니다. 이는 테스트 실행 속도를 높이거나, 미완성 기능에 대한 테스트 실패로 전체 테스트가 방해받는 것을 방지할 때 유용합니다


4. 반복 테스트

subTest를 사용하면 루프 내에서 각 반복 케이스를 개별적인 테스트로 다룰 수 있어, 반복 중 실패해도 나머지 케이스를 계속 실행할 수 있습니다. 이는 테스트 케이스가 많거나 유사한 입력을 반복적으로 검증할 때 유용하며, 어떤 입력에서 실패했는지를 정확히 파악할 수 있게 도와줍니다.


5. 예외 상황 및 raise 검증

실제 개발에서는 잘못된 입력을 받는 경우 의도적으로 예외(Exception)를 발생시켜야 하는 경우가 있습니다. 이때 assertRaises를 이용하여 예외 상황에 대한 검증할 수 있습니다.


6. 테스트 커버리지

테스트 커버리지란 전체 코드 중에서 테스트가 실행된 코드의 비율을 의미합니다. 높은 커버리지는 더 많은 코드가 테스트 되었다는 것을 의미하며, 잠재적인 버그를 찾을 가능성이 높아집니다.


7. 기본 내장 모듈로 커버리지 확인하기

Python에는 trace 모듈이 내장되어 있어서 별도 설치 없이 간단한 커버리지를 확인할 수 있습니다.

python -m trace --count --coverdir={coverage 결과가 저장될 폴더} test_calculator.py

실행이 완료되었다면 폴더에 Workspace로 시작하는 파일을 확인하면, unittest에서 사용된 함수와 그 횟수를 알 수 있습니다.

중요한 것은 단순히 높은 커버리지 수치가 아니라, 핵심 로직과 에러가 발생하기 쉬운 부분이 잘 테스트 되는 것입니다.



마치며

unittest는 Python 개발에서 코드 품질을 보장하는 핵심 도구입니다. 이 가이드에서 살펴본 사용법을 활용하면, 안정적이고 유지보수가 용이한 코드를 작성할 수 있습니다. 테스트 코드 작성의 핵심은 꾸준함입니다.


처음에는 번거롭게 느껴질 수 있지만, 장기적으로 보면 개발 시간을 크게 단축하고 코드의 신뢰성을 높여주는 투자가 됩니다. 작은 기능부터 시작해서 점진적으로 테스트 커버리지를 늘려나가는 것을 권장합니다.


by 아이나비시스템즈 지도기술개발팀 이영균


iMPS배너_최종_하얀색.png

국내 가격 경쟁력 1위! 아이나비가 만든 차별화된 지도 솔루션, iMPS 바로가기


#python #unittest #테스트코드 #소프트웨어개발 #테스트커버리지 #개발자 #지도 #아이나비시스템즈

keyword
작가의 이전글Web Worker, 브라우저에서 멀티스레드로 성능향상