← SEARCHABLE ARCHIVE
Python · 2026-08-08

Python 기초부터 마스터까지 — 8화

unittest와 디버깅으로 프로그램 동작 검증하기

INTERACTIVE READING MODE →

본 블로그 작성 글들은, 강의를 목표로 하고 제작되었습니다. 또한 작성된 글의 정보도 틀렸을 수 있으니, 주의해주세요.

프로그램이 잘 동작한다는 말은 실행해봤다는 경험보다 같은 조건에서 다시 확인할 수 있는 증거에 가까워야 한다. 테스트와 디버깅은 실패를 재현하고 수정 뒤에도 계약이 유지되는지 확인하는 도구다.

첫 unittest

import unittest


def calculate_damage(attack: int, defense: int) -> int:
    if attack < 0 or defense < 0:
        raise ValueError("능력치는 음수일 수 없습니다.")
    return max(1, attack - defense)


class DamageTests(unittest.TestCase):
    def test_defense_cannot_reduce_damage_below_one(self) -> None:
        self.assertEqual(1, calculate_damage(5, 100))

    def test_negative_stat_is_rejected(self) -> None:
        with self.assertRaises(ValueError):
            calculate_damage(-1, 10)


if __name__ == "__main__":
    unittest.main()

테스트 이름은 조건과 기대 결과를 설명해야 한다. 내부 변수나 호출 순서를 그대로 검사하기보다 사용자가 관찰할 수 있는 규칙을 확인하면 구현을 정리해도 테스트가 유지된다.

경계값을 표로 확인하기

class RankTests(unittest.TestCase):
    def test_rank_boundaries(self) -> None:
        cases = [
            (69, "B"),
            (70, "A"),
            (89, "A"),
            (90, "S"),
        ]

        for score, expected in cases:
            with self.subTest(score=score):
                self.assertEqual(expected, get_rank(score))

비슷한 성공 사례를 무작정 늘리기보다 조건이 바뀌는 경계와 잘못된 입력을 선택한다. 테스트는 순서, 현재 시각, 이전 실행에서 남은 파일에 의존하지 않아야 한다.

재현 중심 디버깅

버그가 생기면 다음 순서로 범위를 줄일 수 있다.

  1. 입력, 실행 명령, 환경, 예상 결과, 실제 결과를 기록한다.
  2. 실패를 자동으로 재현하는 테스트를 만든다.
  3. 입력과 코드 경계를 줄여 최소 재현을 만든다.
  4. 가설 하나를 세우고 관찰 지점 하나를 추가한다.
  5. 수정 전에는 테스트가 실패하고 수정 후에는 통과하는지 확인한다.

코드 흐름을 멈춰 현재 상태를 보고 싶다면 breakpoint()를 사용할 수 있다.

def find_player(players: list[dict], player_id: int) -> dict | None:
    breakpoint()
    return next((player for player in players if player["id"] == player_id), None)

운영 중인 문제는 실행을 멈출 수 없으므로 logging으로 필요한 문맥을 남긴다. 비밀번호, 토큰, 전체 사용자 입력 같은 민감정보는 로그에 기록하지 않는다.

반복 가능한 품질 검사

python -m compileall -q src tests
python -m unittest discover -s tests -v

프로젝트가 커지면 formatter, linter, type checker도 같은 검사 명령에 추가할 수 있다. 도구의 개수보다 로컬과 자동화 환경이 같은 명령을 실행하고 실패를 무시하지 않는 것이 중요하다.

테스트는 버그가 없음을 증명하지 못한다. 대신 중요한 정상 흐름, 경계값, 실패 경로를 반복해서 확인하고 변경이 만든 회귀를 빠르게 발견하게 해준다.