# GUARDiA ITSM + Messenger — 단위·통합 테스트 계획서 **문서 버전**: 1.0 **작성일**: 2026-05-25 **대상 시스템**: GUARDiA ITSM v1.0, GUARDiA Messenger v1.0 --- ## 목차 1. [개요](#1-개요) 2. [테스트 범위 및 전략](#2-테스트-범위-및-전략) 3. [테스트 환경](#3-테스트-환경) 4. [단위 테스트 계획](#4-단위-테스트-계획) 5. [통합 테스트 계획](#5-통합-테스트-계획) 6. [비기능 테스트 계획](#6-비기능-테스트-계획) 7. [테스트 케이스 목록](#7-테스트-케이스-목록) 8. [결함 관리](#8-결함-관리) 9. [완료 기준](#9-완료-기준) --- ## 1. 개요 ### 1.1 목적 본 문서는 GUARDiA ITSM 및 GUARDiA Messenger 시스템의 품질 보증을 위한 단위 테스트 및 통합 테스트 계획을 정의한다. 개별 컴포넌트의 정확성 검증(단위)과 컴포넌트 간 상호작용 검증(통합)을 통해 출시 전 결함을 조기에 발견하고 수정한다. ### 1.2 테스트 대상 | 구분 | 대상 모듈 | |------|-----------| | ITSM 코어 | auth, dashboard, tasks, approvals, audit, cmdb | | ITSM 운영 | ssl_manager, pm, incidents, oncall, batch | | ITSM 지원 | kb, shell_scripts, ssh, attachments, notifications | | ITSM 고객 | rating, institutions, timetable, work | | Messenger | bot dispatcher, GUARDiA_ITSM bot, command handlers | | 공통 | scheduler, seed, database, JWT auth | ### 1.3 테스트 유형 정의 - **단위 테스트(Unit Test)**: 함수/클래스 단위의 독립 검증. 외부 의존성은 mock 처리. - **통합 테스트(Integration Test)**: DB·Redis·SSH·Messenger 등 실제 의존성과의 연동 검증. - **E2E 시나리오 테스트**: 사용자 시나리오 전 구간 검증 (API 호출 → DB 저장 → 알림 발송). --- ## 2. 테스트 범위 및 전략 ### 2.1 단위 테스트 범위 ``` 범위 내 (In Scope) ├── 모든 라우터 엔드포인트 (130개 API) ├── core/scheduler.py — 스케줄 함수 ├── core/seed.py — 시드 데이터 생성 ├── utils/crypto.py — AES-256-GCM 암/복호화 ├── utils/ssh_runner.py — SSH 명령 검증 └── schemas/*.py — Pydantic 직렬화/역직렬화 범위 외 (Out of Scope) ├── 3rd party 라이브러리 내부 (FastAPI, SQLAlchemy) └── OS 수준 시스템 콜 ``` ### 2.2 통합 테스트 범위 ``` 범위 내 ├── ITSM API ↔ SQLite DB 연동 ├── ITSM API ↔ GUARDiA Messenger 알림 ├── SSH 실행 → 원격 서버 응답 처리 ├── SSL 점검 스크립트 → JSON 파싱 ├── 스케줄러 → DB 변경 → 알림 체인 └── PM 스케줄 → WorkTimetable 자동 생성 범위 외 ├── 외부 LLM API (완전 금지) ├── 실제 고객 운영 서버 대상 SSH └── 이메일/SMS 외부 발송 ``` ### 2.3 테스트 전략 - **TDD 기반**: 핵심 비즈니스 로직은 테스트 먼저 작성 - **AAA 패턴**: Arrange → Act → Assert - **픽스처 격리**: 각 테스트는 독립 SQLite in-memory DB 사용 - **커버리지 목표**: 단위 80% 이상, 통합 70% 이상 --- ## 3. 테스트 환경 ### 3.1 단위 테스트 환경 ``` OS: 동일 (Linux/Windows 무관) Python: 3.11+ 프레임워크: pytest 8.x + pytest-asyncio Mock: unittest.mock, respx (httpx mock) DB: SQLite in-memory (aiosqlite) 의존성: requirements-dev.txt 참조 ``` **requirements-dev.txt**: ``` pytest>=8.0 pytest-asyncio>=0.23 pytest-cov>=5.0 httpx>=0.27 # TestClient respx>=0.21 # httpx mock faker>=25.0 # 테스트 데이터 freezegun>=1.4 # 시간 고정 ``` ### 3.2 통합 테스트 환경 ``` OS: Ubuntu 22.04 LTS (권장) 또는 Windows Server 2022 Python: 3.11+ DB: SQLite 파일 기반 (테스트 전용) Messenger: 테스트 채널 (별도 구성) SSH 대상: localhost SSH 서버 (테스트 전용) ``` ### 3.3 디렉토리 구조 ``` C:\GUARDiA\itsm\ └── tests\ ├── conftest.py # 공통 픽스처 ├── unit\ │ ├── test_auth.py │ ├── test_tasks.py │ ├── test_ssl_manager.py │ ├── test_pm.py │ ├── test_incidents.py │ ├── test_oncall.py │ ├── test_batch.py │ ├── test_scheduler.py │ └── test_crypto.py └── integration\ ├── test_sr_workflow.py ├── test_approval_chain.py ├── test_pm_workflow.py ├── test_incident_workflow.py └── test_notification_chain.py ``` --- ## 4. 단위 테스트 계획 ### 4.1 conftest.py 공통 픽스처 ```python # tests/conftest.py import pytest import pytest_asyncio from httpx import AsyncClient, ASGITransport from sqlalchemy.ext.asyncio import create_async_engine, AsyncSession from sqlalchemy.orm import sessionmaker from database import Base, get_db from main import app TEST_DB = "sqlite+aiosqlite:///:memory:" @pytest_asyncio.fixture async def db_session(): engine = create_async_engine(TEST_DB) async with engine.begin() as conn: await conn.run_sync(Base.metadata.create_all) SessionLocal = sessionmaker(engine, class_=AsyncSession, expire_on_commit=False) async with SessionLocal() as session: yield session async with engine.begin() as conn: await conn.run_sync(Base.metadata.drop_all) @pytest_asyncio.fixture async def client(db_session): app.dependency_overrides[get_db] = lambda: db_session async with AsyncClient( transport=ASGITransport(app=app), base_url="http://test" ) as ac: yield ac app.dependency_overrides.clear() @pytest_asyncio.fixture async def auth_headers(client): r = await client.post("/auth/login", json={"username":"admin","password":"admin1234!"}) token = r.json()["access_token"] return {"Authorization": f"Bearer {token}"} ``` ### 4.2 인증 모듈 (auth) 단위 테스트 **파일**: `tests/unit/test_auth.py` | 테스트 ID | 테스트명 | 설명 | 기대 결과 | |-----------|----------|------|-----------| | UT-AUTH-001 | 정상 로그인 | 올바른 계정으로 로그인 | 200 + access_token 반환 | | UT-AUTH-002 | 잘못된 비밀번호 | 틀린 비밀번호 시도 | 401 Unauthorized | | UT-AUTH-003 | 존재하지 않는 계정 | 없는 사용자명 | 401 Unauthorized | | UT-AUTH-004 | 비밀번호 변경 | 현재 PW 확인 후 변경 | 200 + message | | UT-AUTH-005 | JWT 토큰 만료 | 만료된 토큰으로 API 호출 | 401 Unauthorized | | UT-AUTH-006 | RBAC — CUSTOMER 권한 제한 | CUSTOMER가 admin API 호출 | 403 Forbidden | | UT-AUTH-007 | RBAC — ENGINEER 권한 | ENGINEER가 SR 처리 | 200 OK | | UT-AUTH-008 | 빈 토큰 | Authorization 헤더 없음 | 401 Unauthorized | ```python # 예시 테스트 코드 @pytest.mark.asyncio async def test_login_success(client, db_session): # Arrange: seed admin user from core.seed import seed_all await seed_all(db_session) # Act r = await client.post("/auth/login", json={"username":"admin","password":"admin1234!"}) # Assert assert r.status_code == 200 data = r.json() assert "access_token" in data assert data["token_type"] == "bearer" @pytest.mark.asyncio async def test_login_wrong_password(client, db_session): from core.seed import seed_all await seed_all(db_session) r = await client.post("/auth/login", json={"username":"admin","password":"wrong!"}) assert r.status_code == 401 ``` ### 4.3 SR 처리 (tasks) 단위 테스트 | 테스트 ID | 테스트명 | 기대 결과 | |-----------|----------|-----------| | UT-TASK-001 | SR 생성 (INCIDENT 유형) | 201 + sr_id 반환 | | UT-TASK-002 | SR 생성 (CHANGE 유형) | 201 + sr_id 반환 | | UT-TASK-003 | SR 목록 페이징 | 200 + items/total 구조 | | UT-TASK-004 | SR 상태 변경 OPEN→IN_PROGRESS | 200 + 상태 업데이트 | | UT-TASK-005 | SR 상태 — 잘못된 전환 | 400 Bad Request | | UT-TASK-006 | SR 첨부파일 업로드 | 201 + file_id | | UT-TASK-007 | SR 첨부파일 경로 순회 방지 | 400 Bad Request | | UT-TASK-008 | 고객(CUSTOMER) SR 자기 것만 조회 | 200 + 본인 SR만 | | UT-TASK-009 | SR 검색 (제목 키워드) | 200 + 필터 결과 | | UT-TASK-010 | SR 만족도 평가 | 200 + rating 저장 | ### 4.4 SSL 관리 (ssl_manager) 단위 테스트 | 테스트 ID | 테스트명 | 기대 결과 | |-----------|----------|-----------| | UT-SSL-001 | SSL 도메인 등록 | 201 + ssl_id | | UT-SSL-002 | 중복 도메인 등록 | 409 Conflict | | UT-SSL-003 | SSL 즉시 점검 — 정상 응답 파싱 | 200 + days_left 계산 | | UT-SSL-004 | SSL 즉시 점검 — SSH 실패 시 | 200 + error_msg 기록 | | UT-SSL-005 | 만료 임박 알림 등급 (EXPIRED ≤0) | level=EXPIRED | | UT-SSL-006 | 만료 임박 알림 등급 (URGENT ≤7) | level=URGENT | | UT-SSL-007 | 만료 임박 알림 등급 (WARN ≤30) | level=WARN | | UT-SSL-008 | 정상 인증서 (>30일) | level=OK | | UT-SSL-009 | SSL 수동 갱신 완료 기록 | 200 + renewed_at 업데이트 | | UT-SSL-010 | SSL 도메인 삭제 | 204 No Content | ### 4.5 PM 정기점검 (pm) 단위 테스트 | 테스트 ID | 테스트명 | 기대 결과 | |-----------|----------|-----------| | UT-PM-001 | PM 템플릿 생성 | 201 + template_id | | UT-PM-002 | PM 템플릿 목록 조회 | 200 + 카테고리별 목록 | | UT-PM-003 | PM 스케줄 생성 (MONTHLY) | 201 + next_scheduled 계산 | | UT-PM-004 | PM 스케줄 생성 (WEEKLY) | 201 + 7일 후 next_scheduled | | UT-PM-005 | PM 스케줄 생성 (QUARTERLY) | 201 + 90일 후 next_scheduled | | UT-PM-006 | PM 작업 생성 (WorkTimetable 연동) | 201 + timetable_id | | UT-PM-007 | PM 결과 저장 — PASS | 200 + result=PASS | | UT-PM-008 | PM 결과 저장 — FAIL (비고 필수) | 400 if note 없음 | | UT-PM-009 | PM 완료 처리 | 200 + completed_at 기록 | | UT-PM-010 | PM Excel 보고서 다운로드 | 200 + Content-Type: xlsx | | UT-PM-011 | next_scheduled 계산 — MONTHLY 말일 | 말일 처리 정확성 | | UT-PM-012 | PM 템플릿 소프트 삭제 | 200 + is_active=False | ### 4.6 장애 관리 (incidents) 단위 테스트 | 테스트 ID | 테스트명 | 기대 결과 | |-----------|----------|-----------| | UT-INC-001 | 장애 생성 (P1) | 201 + INC-YYYYMMDD-XXXXXX 형식 | | UT-INC-002 | 장애 생성 (P4) | 201 + 알림 없음 | | UT-INC-003 | P1/P2 생성 → Messenger 알림 | 알림 함수 호출 확인 (mock) | | UT-INC-004 | 상태 전환 OPEN→INVESTIGATING | 200 + 타임라인 기록 | | UT-INC-005 | 잘못된 상태 전환 CLOSED→OPEN | 400 Bad Request | | UT-INC-006 | 장애 타임라인 조회 | 200 + events 목록 | | UT-INC-007 | 장애 종결 (RCA 필수) | 400 if rca 없음 | | UT-INC-008 | 장애 목록 — P1 필터 | 200 + P1 only | | UT-INC-009 | 장애 통계 (기간별) | 200 + count by priority | | UT-INC-010 | 장애 SR 연동 | 200 + related_sr_ids 업데이트 | ### 4.7 온콜/당직 (oncall) 단위 테스트 | 테스트 ID | 테스트명 | 기대 결과 | |-----------|----------|-----------| | UT-OC-001 | 당직 단건 등록 | 201 + duty_id | | UT-OC-002 | 중복 당직 등록 (같은 날+교대) | 409 Conflict | | UT-OC-003 | 당직 일괄 등록 (최대 62건) | 201 + count | | UT-OC-004 | 당직 일괄 — 초과 (63건) | 422 Validation Error | | UT-OC-005 | 오늘 당직 조회 | 200 + 교대별 그룹 | | UT-OC-006 | 월별 당직 조회 | 200 + 해당 월 목록 | | UT-OC-007 | 당직 삭제 | 204 No Content | ### 4.8 배치 작업 (batch) 단위 테스트 | 테스트 ID | 테스트명 | 기대 결과 | |-----------|----------|-----------| | UT-BAT-001 | 배치 작업 생성 | 201 + batch_id | | UT-BAT-002 | 위험 명령 차단 (rm -rf /) | 400 Bad Request | | UT-BAT-003 | 위험 명령 차단 (shutdown) | 400 Bad Request | | UT-BAT-004 | 위험 명령 차단 (fork bomb) | 400 Bad Request | | UT-BAT-005 | 배치 수동 실행 요청 | 202 Accepted | | UT-BAT-006 | 배치 실행 이력 조회 | 200 + runs 목록 | | UT-BAT-007 | 배치 실패 시 SR 자동 생성 | SR 생성 확인 (mock) | | UT-BAT-008 | 비활성 배치 스케줄 — 실행 안 함 | is_active=False 체크 | | UT-BAT-009 | 배치 작업 삭제 | 204 No Content | | UT-BAT-010 | TIMEOUT 처리 (asyncio timeout) | 실행 상태=TIMEOUT | ### 4.9 암호화 유틸 단위 테스트 | 테스트 ID | 테스트명 | 기대 결과 | |-----------|----------|-----------| | UT-CRYPTO-001 | AES-256-GCM 암호화/복호화 왕복 | 원문 복원 성공 | | UT-CRYPTO-002 | 서로 다른 평문 → 다른 암호문 | 결정론적 아님 확인 | | UT-CRYPTO-003 | 잘못된 키로 복호화 | 예외 발생 | | UT-CRYPTO-004 | 빈 문자열 암호화 | 정상 처리 | | UT-CRYPTO-005 | 한국어 포함 문자열 암호화 | UTF-8 왕복 성공 | ### 4.10 스케줄러 단위 테스트 ```python # tests/unit/test_scheduler.py import pytest from datetime import datetime from freezegun import freeze_time from core.scheduler import _calc_next from models import PmSchedule, PmFrequency @pytest.mark.asyncio async def test_calc_next_weekly(): with freeze_time("2026-01-01"): sched = PmSchedule(frequency=PmFrequency.WEEKLY) result = _calc_next(sched, datetime(2026, 1, 1)) assert result == datetime(2026, 1, 8) @pytest.mark.asyncio async def test_calc_next_monthly_end_of_month(): # 1월 31일에서 +1달 → 2월 28일 (말일 처리) sched = PmSchedule(frequency=PmFrequency.MONTHLY, day_of_month=31) result = _calc_next(sched, datetime(2026, 1, 31)) assert result.day == 28 # 2026년 2월 말일 ``` --- ## 5. 통합 테스트 계획 ### 5.1 SR 처리 전체 워크플로우 **시나리오**: 고객 SR 접수 → 담당자 배정 → 처리 → 완료 → 만족도 평가 ``` TC-INT-SR-001: SR 접수부터 완료까지 전체 흐름 1. POST /tasks/ — SR 생성 (CUSTOMER 역할) 2. GET /tasks/{id} — 생성 확인 3. PATCH /assign/{sr_id} — 담당자 배정 (PM 역할) 4. PATCH /tasks/{id}/status — IN_PROGRESS 전환 5. POST /work/ — 처리 내역 등록 6. PATCH /tasks/{id}/status — RESOLVED 전환 7. POST /rating/ — 만족도 5점 등록 8. GET /dashboard/summary — 통계 반영 확인 기대 결과: 전 단계 성공, 최종 SR 상태=RESOLVED, rating=5 ``` ### 5.2 결재 워크플로우 **시나리오**: CHANGE 유형 SR → 결재 요청 → 승인 → 실행 ``` TC-INT-APV-001: 결재 승인 워크플로우 1. POST /tasks/ — CHANGE SR 생성 2. POST /approvals/ — 결재 요청 (결재자 2단계 설정) 3. PATCH /approvals/{id}/approve — 1단계 승인 4. PATCH /approvals/{id}/approve — 2단계 승인 5. GET /tasks/{sr_id} — SR 상태 = APPROVED 확인 6. PATCH /tasks/{id}/status — IMPLEMENTING 전환 TC-INT-APV-002: 결재 거부 워크플로우 1. POST /tasks/ + POST /approvals/ 2. PATCH /approvals/{id}/reject — 거부 (사유 필수) 3. GET /tasks/{sr_id} — SR 상태 = REJECTED 확인 ``` ### 5.3 PM 정기점검 워크플로우 ``` TC-INT-PM-001: PM 스케줄 → 자동 작업 생성 1. POST /pm/templates/ — 체크리스트 템플릿 생성 2. POST /pm/schedules/ — MONTHLY 스케줄 등록 3. (스케줄러 _auto_generate_pm 함수 직접 호출) 4. GET /timetable/ — WorkTimetable 생성 확인 5. GET /pm/works/{id} — PM 작업 목록 확인 TC-INT-PM-002: PM 결과 저장 및 Excel 보고서 1. (PM 작업 생성 선행) 2. POST /pm/works/{id}/results — 각 항목 PASS/FAIL 저장 3. PATCH /pm/works/{id}/complete — 완료 처리 4. GET /pm/works/{id}/report — Excel 다운로드 5. openpyxl로 파일 열기 → PASS/FAIL 색상 확인 ``` ### 5.4 장애 처리 워크플로우 ``` TC-INT-INC-001: P1 장애 발생 → 처리 → 종결 1. POST /incidents/ — P1 장애 등록 → Messenger 알림 mock 확인 2. POST /incidents/{id}/timeline — "장애 감지 완료" 기록 3. PATCH /incidents/{id}/status — INVESTIGATING 4. PATCH /incidents/{id}/status — MITIGATED (조치 내역 포함) 5. PATCH /incidents/{id}/status — RESOLVED (임시 해결) 6. POST /incidents/{id}/close — RCA 문서 포함 종결 7. GET /incidents/{id} — 전체 타임라인 확인 ``` ### 5.5 배치 실행 및 실패 알림 ``` TC-INT-BAT-001: 배치 실행 실패 → SR 자동 생성 1. POST /batch/jobs — 의도적 실패 명령으로 배치 등록 (예: "exit 1" 명령) 2. POST /batch/jobs/{id}/run — 수동 실행 3. (BackgroundTask 완료 대기) 4. GET /batch/jobs/{id}/runs — 상태 = FAILED 확인 5. GET /tasks/ — 자동 생성된 SR 확인 (sr_type=LOG, priority=HIGH) ``` ### 5.6 Messenger 알림 통합 테스트 ``` TC-INT-MSG-001: ITSM 이벤트 → Messenger 채널 발송 1. mock Messenger API (respx) 2. P1 장애 생성 → /incidents/ POST 3. respx 캡처된 요청에서 채널 ID, 메시지 내용 검증 4. 메시지에 incident_id, priority, title 포함 확인 5. 서버 IP/비밀번호 미포함 확인 (보안) TC-INT-MSG-002: Bot 명령 처리 1. POST /messenger/webhook — "/itsm sr list" 명령 2. 응답 메시지에 SR 목록 포함 확인 3. POST /messenger/webhook — "/itsm help" 4. 응답 메시지에 명령어 목록 확인 ``` --- ## 6. 비기능 테스트 계획 ### 6.1 성능 테스트 | 항목 | 목표 | 측정 방법 | |------|------|-----------| | API 응답 시간 (평균) | ≤ 500ms | locust or k6 | | API 응답 시간 (P99) | ≤ 2000ms | locust or k6 | | 동시 사용자 | 50명 이상 정상 처리 | locust 동시 접속 | | DB 트랜잭션 | 100 TPS 이상 | pytest-benchmark | | 파일 업로드 | 10MB 이하 ≤ 3초 | httpx 직접 테스트 | ### 6.2 보안 테스트 | 항목 | 테스트 방법 | 기대 결과 | |------|------------|-----------| | SQL 인젝션 | 검색 파라미터에 SQL 구문 주입 | 에러 없이 빈 결과 | | XSS | SR 제목에 `` | HTML 이스케이프 | | JWT 위조 | 임의 서명 토큰 사용 | 401 | | 경로 순회 | `../../../etc/passwd` 첨부파일 | 400 | | SSH 명령 인젝션 | `; rm -rf /` 포함 명령 | 400 | | 서버 정보 노출 | API 응답에 IP/PW 포함 여부 | 미포함 확인 | | 스택트레이스 노출 | 의도적 오류 발생 | SR ID + 요약만 반환 | ### 6.3 감사 로그 무결성 테스트 ``` TC-AUDIT-001: 해시 체인 무결성 1. 여러 감사 로그 생성 (API 호출 10회) 2. GET /audit/logs — 전체 조회 3. 각 로그의 prev_hash → 이전 로그 hash 일치 확인 4. DB에서 중간 로그 직접 수정 5. GET /audit/verify — 무결성 깨짐 감지 확인 ``` --- ## 7. 테스트 케이스 목록 요약 | 모듈 | 단위 TC | 통합 TC | 합계 | |------|---------|---------|------| | auth | 8 | 2 | 10 | | tasks/SR | 10 | 4 | 14 | | approvals | 6 | 3 | 9 | | ssl_manager | 10 | 2 | 12 | | pm | 12 | 3 | 15 | | incidents | 10 | 2 | 12 | | oncall | 7 | 1 | 8 | | batch | 10 | 2 | 12 | | scheduler | 6 | 2 | 8 | | crypto/security | 5 | 3 | 8 | | messenger | 4 | 3 | 7 | | audit | 4 | 2 | 6 | | **합계** | **92** | **29** | **121** | --- ## 8. 결함 관리 ### 8.1 결함 등급 | 등급 | 정의 | 처리 기한 | |------|------|-----------| | Critical | 시스템 중단, 데이터 손실, 보안 취약점 | 즉시 (24시간 내) | | Major | 핵심 기능 비정상, 결과 오류 | 3일 내 | | Minor | 비핵심 기능 문제, UI 오류 | 7일 내 | | Trivial | 오타, 경미한 표시 오류 | 다음 버전 | ### 8.2 결함 생명주기 ``` NEW → ASSIGNED → IN_PROGRESS → FIXED → VERIFIED → CLOSED ↓ REOPENED (재현 시) ``` ### 8.3 결함 보고서 양식 ``` 결함 ID: BUG-YYYY-NNN 제목: [모듈] 결함 요약 심각도: Critical/Major/Minor/Trivial 재현 단계: 1. ... 2. ... 실제 결과: ... 기대 결과: ... 스크린샷/로그: (첨부) 발견일: YYYY-MM-DD 발견자: ... ``` --- ## 9. 완료 기준 ### 9.1 단계별 완료 기준 **단위 테스트 완료**: - [ ] 모든 단위 TC 실행 완료 - [ ] 코드 커버리지 80% 이상 - [ ] Critical/Major 결함 0건 **통합 테스트 완료**: - [ ] 모든 통합 시나리오 TC 실행 완료 - [ ] 주요 워크플로우 E2E 검증 완료 - [ ] Critical/Major 결함 0건 **보안 테스트 완료**: - [ ] 보안 TC 전 항목 통과 - [ ] API 응답에 민감 정보 미포함 확인 - [ ] 감사 로그 무결성 확인 ### 9.2 테스트 실행 명령 ```bash # 전체 단위 테스트 cd C:\GUARDiA\itsm pytest tests/unit/ -v --cov=. --cov-report=html # 통합 테스트 pytest tests/integration/ -v --timeout=30 # 특정 모듈 pytest tests/unit/test_incidents.py -v # 커버리지 HTML 보고서 확인 open htmlcov/index.html ``` --- *본 테스트 계획서는 GUARDiA ITSM v1.0 기준으로 작성되었으며, 버전 업그레이드 시 갱신이 필요합니다.*