Published on

파이썬으로 윤년 계산 프로그램 만들기 실수 없이 구현하는 규칙과 테스트

Authors

윤년(leap year) 계산은 겉보기엔 단순한 if문 문제처럼 보이지만, 막상 구현하면 의외로 자주 틀립니다. 이유는 규칙이 한 줄로 끝나지 않기 때문입니다. 4로 나누어떨어지면 윤년만 적용하면 1900년 같은 반례에서 바로 깨지고, 100으로 나누어떨어지면 평년만 추가하면 2000년 같은 반례에서 또 깨집니다.

이 글에서는 윤년 규칙을 정확히 이해하고, 파이썬으로 재사용 가능한 함수 + CLI 프로그램으로 만들며, 테스트 케이스로 검증하는 흐름까지 다룹니다. 반복문/조건문 감각이 부족하다면 함께 보면 좋은 글로 파이썬 구구단 출력 프로그램 for문과 while문으로 끝내는 반복문 감각도 연결해두겠습니다.


윤년 규칙 한 번에 정리하기

그레고리력 기준 윤년 규칙은 다음 3단계로 요약됩니다.

  1. 연도가 4로 나누어떨어지면 윤년 후보
  2. 단, 100으로 나누어떨어지면 평년(윤년 아님)
  3. 단, 400으로 나누어떨어지면 다시 윤년

즉, 수학적으로는 아래 조건과 같습니다.

  • year % 400 == 0 이면 윤년
  • 아니고 year % 100 == 0 이면 평년
  • 아니고 year % 4 == 0 이면 윤년
  • 그 외는 평년

여기서 중요한 포인트는 우선순위입니다. 400 규칙이 100 규칙보다 더 강합니다.


가장 안전한 구현: 읽기 쉬운 if-elif 체인

가장 실수 없는 구현은 조건을 규칙 순서대로 나열하는 것입니다.

def is_leap_year(year: int) -> bool:
    """Return True if year is a leap year in the Gregorian calendar."""
    if year % 400 == 0:
        return True
    if year % 100 == 0:
        return False
    return year % 4 == 0

왜 이런 형태가 좋은가


흔히 하는 실수와 반례

윤년은 반례로 검증하는 게 핵심입니다.

실수 1: 4로만 나누기

# 잘못된 예
return year % 4 == 0
  • 1900년은 4로 나누어떨어지지만 실제로는 평년입니다(100으로 나누어떨어지고 400으로는 안 나누어떨어짐).

실수 2: 괄호 우선순위가 애매한 한 줄 조건

# 읽기 어려워서 실수 유발 가능
return year % 4 == 0 and year % 100 != 0 or year % 400 == 0

파이썬에서 andor보다 우선이긴 하지만, 팀 코드에서 이런 표현은 리뷰할 때도 헷갈립니다. 아래처럼 괄호를 명시하거나, 아예 if-elif 체인으로 쓰는 걸 권장합니다.

return (year % 4 == 0 and year % 100 != 0) or (year % 400 == 0)

입력을 받는 윤년 계산 CLI 프로그램 만들기

이제 함수만 있는 게 아니라, 실제로 터미널에서 실행 가능한 작은 프로그램으로 만들어봅시다.

요구사항을 현실적으로 잡아보면:

  • 사용자가 연도를 입력한다
  • 숫자가 아니면 다시 입력받거나 에러를 출력한다
  • 음수/0 같은 값은 정책을 정한다(일반적으로는 1 이상의 정수만 허용)

while로 입력 검증하기

def is_leap_year(year: int) -> bool:
    if year % 400 == 0:
        return True
    if year % 100 == 0:
        return False
    return year % 4 == 0


def read_year() -> int:
    while True:
        raw = input("연도를 입력하세요 (예: 2024): ").strip()
        try:
            year = int(raw)
        except ValueError:
            print("숫자로 된 연도를 입력해야 합니다.")
            continue

        if year < 1:
            print("1 이상의 정수를 입력하세요.")
            continue

        return year


def main() -> None:
    year = read_year()
    if is_leap_year(year):
        print(f"{year}년은 윤년입니다.")
    else:
        print(f"{year}년은 평년입니다.")


if __name__ == "__main__":
    main()

트러블슈팅

  • ValueError: invalid literal for int()
    • 사용자가 2024년처럼 문자를 섞어 입력했을 때 발생합니다. 위 코드처럼 try/except로 처리하세요.
  • 공백 입력
    • .strip()을 써도 빈 문자열이면 int('')에서 예외가 납니다. 결국 except로 잡히니 UX 측면에서 메시지를 명확히 하면 됩니다.

테스트 케이스로 검증하기 (가장 중요한 단계)

윤년 로직은 반드시 테스트로 잠그는 게 좋습니다. 특히 100/400 예외는 사람 눈으로 놓치기 쉽습니다.

최소 필수 테스트 세트

  • 1996 → 윤년 (4로 나눔)
  • 1999 → 평년
  • 1900 → 평년 (100으로 나눔, 400은 아님)
  • 2000 → 윤년 (400으로 나눔)
  • 2024 → 윤년
  • 2100 → 평년

간단한 assert 기반 테스트

별도 프레임워크 없이도 빠르게 검증할 수 있습니다.

def test_is_leap_year():
    assert is_leap_year(1996) is True
    assert is_leap_year(1999) is False
    assert is_leap_year(1900) is False
    assert is_leap_year(2000) is True
    assert is_leap_year(2024) is True
    assert is_leap_year(2100) is False


if __name__ == "__main__":
    test_is_leap_year()
    print("OK")

프로젝트가 커지면 pytest 같은 도구로 옮기는 게 좋지만, 윤년 같은 유틸 함수는 위 방식만으로도 충분히 안정성을 확보할 수 있습니다.


Best Practice 정리

1) 규칙을 코드 구조로 드러내기

  • 한 줄 트릭보다 if -> if -> return 구조가 유지보수에 유리합니다.

2) 입력 검증은 함수로 분리하기

  • read_year()처럼 입력/검증 로직을 분리하면 테스트와 재사용이 쉬워집니다.

3) 반례 중심 테스트를 먼저 작성하기

  • 1900, 2000은 윤년 문제의 대표적인 함정입니다.
  • 테스트가 있으면 리팩터링 중 실수해도 바로 잡힙니다.

결론 핵심 요약

  • 윤년 규칙은 4로 나눔 → 100년 예외 → 400년 재예외 순서가 핵심입니다.
  • 파이썬 구현은 읽기 쉬운 if-elif(또는 if-if-return) 구조가 가장 안전합니다.
  • 반드시 1900(평년), 2000(윤년) 같은 반례로 테스트해 정확도를 보장하세요.

이 정도까지 갖추면 단순한 연습문제를 넘어, 실제 서비스 코드에서도 실수 없이 재사용 가능한 윤년 계산 유틸리티가 됩니다.