Clock_out_Time_Calculator/tests/test_time_calculator.py
KINDNICK bedbb1e9ec
Some checks failed
CI / test (push) Has been cancelled
Initial release v2.2.0
핵심 기능:
- 단축근무·표준·반일 등 다양한 근무 패턴 (5개 프리셋 + 사용자 정의)
- Windows 이벤트 뷰어 자동 출퇴근 감지
- 30분 단위 연장근무 적립/사용 시스템
- 1.0/0.5/0.25일 연차·반차·반반차
- 자동 점심·저녁·외출·자동 백업·화면 잠금 자동 외출
- 한국 공휴일 자동 등록 (음력 포함, holidays 패키지)
- matplotlib 차트 기반 주간/월간/패턴 통계
- 미니 위젯 + 시스템 트레이 통합
- 한국어/English i18n
- 자가 업데이트 (updater.exe + Gitea Releases)

아키텍처:
- core/ (db, time_calculator, notifier, i18n, version, settings_keys)
- ui/ (main_window + 9 dialogs + 3 controllers)
- utils/ (backup, lock_detector, debug_log, updater_client, time_format)
- tests/ (66 pytest 단위) + 통합/i18n GUI 검증

CI/CD:
- .gitea/workflows/ci.yml: push 시 pytest + 통합 테스트
- .gitea/workflows/release.yml: v* 태그 push 시 두 .exe 자동 빌드 + Releases 첨부

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
2026-04-30 12:54:40 +09:00

101 lines
3.1 KiB
Python

"""
TimeCalculator 단위 테스트.
"""
import os
import sys
from datetime import datetime, timedelta
import pytest
sys.path.insert(0, os.path.dirname(os.path.dirname(os.path.abspath(__file__))))
from core.time_calculator import TimeCalculator
@pytest.fixture
def calc_8h():
return TimeCalculator(work_hours=8, lunch_duration_minutes=60)
@pytest.fixture
def calc_short():
"""단축근무 7h30m + 점심 30m"""
return TimeCalculator(work_minutes=450, lunch_duration_minutes=30)
@pytest.fixture
def clock_in_9am():
return datetime(2026, 4, 29, 9, 0, 0)
class TestClockOutTime:
def test_standard_8h_with_lunch(self, calc_8h, clock_in_9am):
co = calc_8h.calculate_clock_out_time(clock_in_9am, include_lunch=True)
assert co == datetime(2026, 4, 29, 18, 0, 0)
def test_short_7h30m_with_lunch(self, calc_short, clock_in_9am):
co = calc_short.calculate_clock_out_time(clock_in_9am, include_lunch=True)
assert co == datetime(2026, 4, 29, 17, 0, 0)
def test_no_lunch(self, calc_8h, clock_in_9am):
co = calc_8h.calculate_clock_out_time(clock_in_9am, include_lunch=False)
assert co == datetime(2026, 4, 29, 17, 0, 0)
def test_with_dinner(self, calc_8h, clock_in_9am):
co = calc_8h.calculate_clock_out_time(clock_in_9am, include_lunch=True, include_dinner=True)
assert co == datetime(2026, 4, 29, 19, 0, 0)
def test_with_break_minutes(self, calc_8h, clock_in_9am):
co_no = calc_8h.calculate_clock_out_time(clock_in_9am, include_lunch=True)
co = calc_8h.calculate_clock_out_time(clock_in_9am, include_lunch=True, break_minutes=30)
assert (co - co_no) == timedelta(minutes=30)
@pytest.mark.parametrize("actual_min,expected_earned", [
(29, 0), # 30분 미만 절삭
(30, 30),
(35, 30),
(60, 60),
(89, 60),
(90, 90),
(120, 120),
])
def test_overtime_30min_truncation(calc_8h, clock_in_9am, actual_min, expected_earned):
base_co = clock_in_9am + timedelta(hours=8)
actual_co = base_co + timedelta(minutes=actual_min)
_, earned = calc_8h.calculate_overtime(clock_in_9am, actual_co, include_lunch=False)
assert earned == expected_earned
class TestCompatibility:
def test_work_hours_property_returns_float(self):
c = TimeCalculator(work_minutes=450)
assert c.work_hours == 7.5
def test_work_hours_constructor_accepts_float(self):
c = TimeCalculator(work_hours=7.5, lunch_duration_minutes=30)
assert c.work_minutes == 450
def test_work_minutes_takes_precedence(self):
# 둘 다 주면 work_minutes 우선
c = TimeCalculator(work_hours=8, work_minutes=450)
assert c.work_minutes == 450
def test_default_8_hours(self):
c = TimeCalculator()
assert c.work_minutes == 480
class TestDayType:
def test_weekend(self):
calc = TimeCalculator()
sat = datetime(2026, 5, 2)
assert calc.is_weekend(sat)
assert calc.get_day_type(sat) == 'weekend'
def test_weekday(self):
calc = TimeCalculator()
mon = datetime(2026, 5, 4)
assert not calc.is_weekend(mon)
assert calc.get_day_type(mon) == 'normal'