Skip to content

Develop

Yena Lee edited this page Jun 16, 2025 · 14 revisions

1. 구글 로그인

1) 프론트엔드 구현

핵심 로직

a. Navbar.jsx (메인 로그인 관리):

  • 로그인 상태 관리 및 UI 표시
  • 구글 로그인 팝업 처리
  • 로그아웃 처리

b. Home.jsx:

  • 로그인 상태 확인 및 관리
  • 로그인되지 않은 경우 설문 페이지로 리다이렉트

c. SurveyMain.jsx:

  • 로그인 상태 확인 (현재 주석 처리됨)
  • 로그인되지 않은 경우 설문 시작 제한

d. Recommendation1.jsx (추천 페이지):

  • 사용자 정보 확인 (/api/userinfo 엔드포인트 사용)
  • 로그인된 사용자 이름 표시
  • 추천 API 호출 시 세션 쿠키 포함

디버깅 포인트

a. Navbar.jsx:

console.log("Login check response:", data);
console.error("Error checking login status:", error);

b. Home.jsx:

console.log('Login check response:', data);
console.log('Login state updated:', data.loggedIn);
console.error('Login check error:', error);

c. SurveyMain.jsx:

console.log('Current login state:', isLoggedIn);
console.log('Login check response:', data);
console.error('Login check error:', error);

d. Recommendation1.jsx:

console.error("이름 불러오기 실패:", err);
console.error("추천 요청 실패:", err);

2) 백엔드 구현

핵심 로직

a. 앱 설정 (app.py):

  • Flask 앱 초기화 및 CORS 설정
  • 세션 쿠키 설정 (크로스 사이트 요청 지원)
  • 구글 OAuth 클라이언트 설정

b. 사용자 인증:

  • Flask-Login을 통한 사용자 세션 관리
  • 구글 OAuth2.0 인증 처리
  • 사용자 정보 검증 및 저장

c. 데이터베이스 관리 (db.py):

  • SQLite 데이터베이스 연결 및 초기화
  • 사용자 정보 저장 및 조회

d. 사용자 모델 (user.py):

  • 사용자 정보 관리 (ID, 이름, 이메일, 프로필 사진)
  • 데이터베이스 CRUD 작업

디버깅 포인트

print(f"Loading user with ID: {user_id}")
print(f"Loaded user: {user}")
print("Checking authentication status...")
print("Session data:", dict(session))
print("Current user:", current_user)
print("Session cookie:", request.cookies.get('session'))
print("All cookies:", request.cookies)
print("Request headers:", dict(request.headers))

2. 설문조사

1) 프론트엔드 구현

설문 흐름

a. SurveyMain.jsx: 설문 시작점

  • 환영 배너와 설명문 표시
  • "설문 시작" 버튼으로 첫 설문 단계로 이동
  • 현재는 로그인 체크 기능이 주석 처리되어 있음

b. 설문 단계별 구현

  • Survey1.jsx: 첫 번째 여행 스타일 선택

    • 4가지 여행 스타일 중 선택 (인증형, 맛집탐방형, 관광형, 휴식형)
    • 선택된 값은 travel_style_1로 저장
  • Survey1-1.jsx: 여행 시 중요 요소 선택

    • 음식점, 액티비티, 관광지 중 우선순위 선택
    • 1순위(15점), 2순위(10점), 3순위(5점)로 점수 부여
  • Survey1-2.jsx: 두 번째 여행 스타일 선택

    • 4가지 여행 스타일 중 선택 (관광형, 맛집탐방형, 쇼핑형, 휴식형)
    • 선택된 값은 travel_style_2로 저장
  • Survey2.jsx: 선호하는 장소 선택

    • 바다, 자연, 도심, 이색거리, 역사, 휴양지 중 최대 2개 선택
    • 1개 선택 시 18점, 2개 선택 시 각각 12점 부여
  • Survey2-1.jsx: 여행 목적 선택

    • 지식 쌓기, 체험, 힐링, 탐험 중 최대 2개 선택
    • 1개 선택 시 12점, 2개 선택 시 각각 8점 부여
  • Survey2-2.jsx: 필수 방문 장소 선택

    • 스테디셀러, 트렌디, 홍대병 스팟 중 선택
    • 필터 태그로 사용되며 점수는 부여되지 않음

공통 UI 구성요소

a. 디자인 요소:

  • 라디오 버튼 선택 방식
  • 점선으로 된 진행 상태 표시바
  • 배경 이미지가 있는 배너
  • 이전/다음 네비게이션 버튼
  • 일관된 디자인을 위한 스타일드 컴포넌트

b. 데이터 처리:

  • localStorage에 응답 저장
  • 백엔드로 POST 요청 전송
  • 오류 발생 시 적절한 메시지 표시

디버깅 포인트

a. 로그인 상태:

console.log('Current login state:', isLoggedIn);

b. 설문 데이터:

console.log("Sending survey data:", { travel_style_1: selectedStyle });
console.log("Survey response:", data);

2) 백엔드 구현

핵심 로직

a. 설문 제출 처리:

  • POST 요청으로 설문 데이터 수신
  • 응답에 따른 태그 점수 계산
  • 사용자 프로필을 JSON 파일로 저장
  • 성공/오류 응답 반환

b. 점수 계산 방식:

  • 첫 번째 여행 스타일: 30점 (균등 분배)
  • 중요 요소: 1순위 15점, 2순위 10점, 3순위 5점
  • 두 번째 여행 스타일: 30점 (균등 분배)
  • 선호 장소: 1개 선택 시 18점, 2개 선택 시 각각 12점
  • 여행 목적: 1개 선택 시 12점, 2개 선택 시 각각 8점
  • 필수 방문 장소: 필터 태그로만 사용 (점수 없음)

태그 매핑

a. 여행 스타일:

style_map_1 = {
    "인증형": ["사진명소", "이색"],
    "맛집탐방형": ["맛집"],
    "관광형": ["문화", "역사", "도심"],
    "휴식형": ["힐링", "자연"]
}

b. 중요 요소:

priority_map = {
    "음식점": ["맛집"],
    "액티비티": ["액티비티", "가족"],
    "관광지": ["문화", "역사", "도심", "사진명소", "이색"]
}

디버깅 포인트

a. 설문 데이터 수신:

print("Received survey data:", data)
print("▶▶▶ Writing survey profile to:", user_file)

b. 점수 계산:

print("Computing scores for:", survey_answers)
print(f"Style 1 scores for {style_1}:", {tag: weights[tag] for tag in style_map_1.get(style_1, [])})
print("Final weights:", dict(weights))

3) 기타

a. SurveyLoading.jsx:

  • 설문 처리 중 표시되는 로딩 컴포넌트
  • 추천 페이지에서 설문 결과 처리 중에 사용

b. Home.jsxNavbar.jsx:

  • 설문 이력 확인 로직 포함
  • 설문 미완료 시 설문 페이지로 리다이렉트
  • 디버깅 포인트:
    console.log('User is logged in, checking survey history...');
    console.log('Survey history response:', surveyData);

c. user_profile.json:

  • 최종 설문 결과와 계산된 점수 저장
  • 설문 데이터와 태그 가중치 포함

3. 도시 추천

1) 핵심 로직

a. 데이터 로드

  • 사용자 프로필(JSON)에서 태그별 점수(user_tag_scores) 로드

  • tagged_contents.json에서 콘텐츠별 태그 카운트(city_tag_data) 로드 b. 점수 계산

  • 각 도시(city)마다 태그별 카운트 × 사용자 점수를 곱해 총합 계산

  • 계산된 점수를 기준으로 상위 N개 도시 추출 c. 결과 반환

  • /recommend/cities 엔드포인트로 JSON 형태의 추천 도시 리스트 반환

2) 블루프린트 설정

# city_recommend.py
from flask import Blueprint, jsonify, session
import json

city_recommend_bp = Blueprint('city_recommend', __name__, url_prefix='/recommend')

@city_recommend_bp.route('/cities', methods=['GET'])
def recommend_cities_route():
    # 1) 사용자 프로필 로드
    user_file = f"profiles/{session['user_id']}.json"
    with open(user_file, 'r', encoding='utf-8') as f:
        user_tag_scores = json.load(f)

    # 2) 콘텐츠별 태그 카운트 로드
    with open("tagged_contents.json", 'r', encoding='utf-8') as f:
        all_contents = json.load(f)
    # city_tag_data: { "서울": Counter({...}), "부산": Counter({...}), ... }
    city_tag_data = build_city_tag_data(all_contents)

    # 3) 점수 계산
    scores = {}
    for city, tag_counter in city_tag_data.items():
        scores[city] = sum(tag_counter.get(tag, 0) * user_tag_scores.get(tag, 0)
                           for tag in user_tag_scores if tag != "필터")

    # 4) 상위 3개 도시 선택
    top_cities = sorted(scores.items(), key=lambda x: -x[1])[:3]
    return jsonify([{"city": city, "score": score} for city, score in top_cities])

3) 주요 함수

from collections import defaultdict, Counter

def build_city_tag_data(contents):
    """
    전체 콘텐츠 목록에서 도시별 태그 카운트를 생성.
    :param contents: List[dict] (tour API 응답 형태)
    :return: Dict[str, Counter] (도시명: Counter({태그: 개수, ...}), ...)
    """
    city_tag_data = defaultdict(Counter)
    for item in contents:
        city = extract_city(item.get("addr1", ""))
        tags = item.get("tags", [])
        if city and tags:
            city_tag_data[city].update(tags)
    return city_tag_data

def extract_city(addr: str) -> str:
    """
    addr1 문자열에서 “서울”, “부산” 등 도시명 추출.
    """
    # 예: "서울특별시 강남구 역삼동" → "서울"
    return addr.split()[0] if addr else ""

4) 디버깅 포인트

# 사용자 프로필 로드
print(f"[city_recommend] Loading user profile from: {user_file}", user_tag_scores)

# 콘텐츠·도시별 태그 데이터
print(f"[city_recommend] Built city_tag_data for {len(city_tag_data)} cities")

# 점수 계산
print(f"[city_recommend] Calculated scores:", scores)

# 응답 직전
print("[city_recommend] Top cities to return:", top_cities)

4. 콘텐츠 추천

1) 핵심 로직

a. 엔드포인트

  • GET /recommend/contents?city=<도시명>

  • 쿼리 파라미터 city 로 요청된 도시의 콘텐츠만 필터링 b. 데이터 로드

  • 사용자 프로필(JSON)에서 태그별 점수(user_tag_scores) 로드

  • tagged_contents.json 에서 전체 콘텐츠 목록(all_contents) 로드 c. 필터링 & 점수 계산

  • 요청된 city 와 일치하는 콘텐츠만 선별

  • 각 콘텐츠의 태그 리스트를 순회하며, 사용자 점수와 곱해 합산 d. 상위 N개 선택

  • 기본값: 상위 5개 콘텐츠 e. 결과 반환

  • JSON 배열로 [{"id","title","score","tags","firstimage","addr1"}, …] 형태

2) 블루프린트 설정

# content_recommend.py
from flask import Blueprint, request, jsonify, session
import json

content_recommend_bp = Blueprint('content_recommend', __name__, url_prefix='/recommend')

@content_recommend_bp.route('/contents', methods=['GET'])
def recommend_contents_route():
    # 1) city 파라미터
    city = request.args.get('city')
    if not city:
        return jsonify({"error": "city parameter is required"}), 400

    # 2) 사용자 프로필 로드
    user_file = f"profiles/{session['user_id']}.json"
    with open(user_file, 'r', encoding='utf-8') as f:
        user_tag_scores = json.load(f)

    # 3) 전체 콘텐츠 로드
    with open("tagged_contents.json", 'r', encoding='utf-8') as f:
        all_contents = json.load(f)

    # 4) 필터링 및 점수 계산
    scored = []
    for item in all_contents:
        if extract_city(item.get("addr1", "")) != city:
            continue
        tags = item.get("tags", [])
        score = sum(user_tag_scores.get(tag, 0) for tag in tags if tag != "필터")
        scored.append((item, score))

    # 5) 상위 5개 콘텐츠 선택
    top_n = 5
    top_contents = sorted(scored, key=lambda x: -x[1])[:top_n]

    # 6) JSON 응답 포맷팅
    result = []
    for item, score in top_contents:
        result.append({
            "id":         item.get("contentid"),
            "title":      item.get("title"),
            "score":      score,
            "tags":       item.get("tags", []),
            "firstimage": item.get("firstimage"),
            "addr":       item.get("addr1")
        })
    return jsonify(result)

3) 보조 함수

from collections import defaultdict, Counter

def extract_city(addr: str) -> str:
    """
    addr1 예: "서울특별시 강남구 역삼동" → "서울"
    """
    return addr.split()[0] if addr else ""

4) 디버깅 포인트

print(f"[content_recommend] Requested city: {city}")
print(f"[content_recommend] Loaded profile: {user_file}{user_tag_scores}")
print(f"[content_recommend] Total contents: {len(all_contents)}")
print(f"[content_recommend] Candidates for {city}: {len(scored)}")
print(f"[content_recommend] Top {len(top_contents)} results:", [c[0]["contentid"] for c in top_contents])

5. 챗봇

1) 핵심 로직

프론트엔드 (FloatingChat.jsx)

a. 기본 구조

  • React 컴포넌트로 구현된 챗봇 UI
  • 사용자 위치 정보 수집 및 관리
  • 메시지 상태 관리 (대화 내역, 로딩 상태 등)

b. 주요 기능

  • 사용자 위치 정보 수집 (Geolocation API 사용)
  • 메시지 전송 및 응답 처리
  • 장소 추천 결과 표시
  • 추가 추천 기능 (아직 구현 중)

c. API 통신

2) 백엔드

a. 자연어 처리 (parser.py)

  • OpenAI API 활용
    • GPT-4.1-nano 모델 사용
    • 사용자 입력에서 다음 정보 추출:
      • category: 추천받고 싶은 장소/음식 카테고리
      • radius: 검색 반경 (미터 단위)
      • sort_by: 정렬 기준 (distance/rating)

b. 장소 검색 및 추천 (routes.py)

  • Google Maps API 통합

    • 장소 검색 및 상세 정보 조회
    • 대중교통 경로 정보 조회
    • 거리 계산 (Haversine 공식 사용)
  • 추천 로직

    1. 사용자 위치 기반 반경 내 장소 검색
    2. 카테고리별 장소 검색 (최대 3페이지)
    3. 거리 기반 필터링
    4. 평점/거리 기준 정렬
    5. 상위 5개 장소 추천

c. 데이터 관리

  • 세션 관리
    • 사용자별 최근 요청 정보 저장
    • 추천된 장소 ID 관리
    • 중복 추천 방지

3) 디버깅 포인트

프론트엔드

a. 위치 정보 디버깅

console.error("위치 정보 오류:", error);

b. API 통신 디버깅

console.log("API 요청 시작:", {
  latitude: location.latitude,
  longitude: location.longitude,
  user_input: input
});
console.log("API 응답:", response.data);

c. 에러 처리 디버깅

console.error("API Error:", error);
console.error("서버 응답:", error.response.data);
console.error("서버 응답 없음:", error.request);
console.error("요청 설정 오류:", error.message);

백엔드

a. 장소 검색 디버깅

print(f"Searching for category: {category}")
print(f"With radius: {radius}")
print(f"Places found: {len(items)}")

b. 장소 처리 에러 디버깅

print(f"Error processing place: {str(e)}")

6. 테스트

1) 프론트엔드 테스트 코드

1-1) 단위 테스트 (Jest + React Testing Library)

  1. 필요한 패키지 설치
cd frontend
npm install --save-dev @testing-library/react @testing-library/jest-dom @testing-library/user-event jest
  1. 테스트 디렉터리 구조
frontend/
  src/
    components/
      Navbar.jsx
      __tests__/
        Navbar.test.jsx   ← 단위 테스트 파일
  1. 테스트 코드: Navbar.test.jsx
import { render, screen, fireEvent, waitFor, act } from '@testing-library/react';
import { BrowserRouter } from 'react-router-dom';
import Navbar from '../Navbar';

// Mock 설정
global.fetch = jest.fn();
const mockOpen = jest.fn();
window.open = mockOpen;
const mockAlert = jest.fn();
window.alert = mockAlert;
console.error = jest.fn();

describe('Navbar Component', () => {
  // 테스트 환경 설정
  const renderNavbar = () => {
    return render(
      <BrowserRouter>
        <Navbar />
      </BrowserRouter>
    );
  };

  beforeEach(() => {
    jest.clearAllMocks();
    fetch.mockReset();
    localStorage.clear();
  });

  // 1. 기본 UI 테스트
  test('renders login button when user is not logged in', () => {
    // Given: 로그인하지 않은 상태
    fetch.mockResolvedValueOnce({
      ok: true,
      json: () => Promise.resolve({ loggedIn: false })
    });

    // When: 컴포넌트 렌더링
    renderNavbar();

    // Then: 로그인 버튼 표시
    const loginButton = screen.getByText('Login');
    expect(loginButton).toBeInTheDocument();
  });

  // 2. 로그인 기능 테스트
  test('opens Google login popup when login button is clicked', () => {
    // Given: 로그인하지 않은 상태
    fetch.mockResolvedValueOnce({
      ok: true,
      json: () => Promise.resolve({ loggedIn: false })
    });

    // When: 로그인 버튼 클릭
    renderNavbar();
    const loginButton = screen.getByText('Login');
    fireEvent.click(loginButton);

    // Then: Google 로그인 팝업 열기
    expect(mockOpen).toHaveBeenCalledWith(
      'https://127.0.0.1:5000/',
      'googleLogin',
      'width=500,height=600'
    );
  });

  // 3. 로그아웃 기능 테스트
  test('handles logout successfully', async () => {
    // Given: 로그인 상태와 로그아웃 API 응답
    fetch
      .mockResolvedValueOnce({
        ok: true,
        json: () => Promise.resolve({ loggedIn: true })
      })
      .mockResolvedValueOnce({
        ok: true,
        json: () => Promise.resolve({ status: 'success' })
      });

    // When: 로그아웃 버튼 클릭
    renderNavbar();
    const logoutButton = await screen.findByText('Logout');
    fireEvent.click(logoutButton);

    // Then: 로그아웃 API 호출 및 알림
    await waitFor(() => {
      expect(fetch).toHaveBeenCalledWith(
        'https://127.0.0.1:5000/logout',
        expect.objectContaining({
          method: 'GET',
          credentials: 'include',
          mode: 'cors',
          headers: {
            'Accept': 'application/json'
          }
        })
      );
      expect(mockAlert).toHaveBeenCalledWith('로그아웃이 완료됐습니다.');
    });
  });

  // 4. 에러 처리 테스트
  test('handles login check error gracefully', async () => {
    // Given: 네트워크 에러
    fetch.mockRejectedValueOnce(new Error('Network error'));

    // When: 컴포넌트 렌더링
    renderNavbar();

    // Then: 로그인 버튼 유지
    const loginButton = screen.getByText('Login');
    expect(loginButton).toBeInTheDocument();
  });
});
  1. 실행
cd frontend
npm test

2-2) 테스트 커버리지 리포트 생성

npm test -- --coverage

이 명령어를 실행하면 frontend/coverage 디렉토리에 HTML 형식의 커버리지 리포트가 생성됩니다.

3-3) 테스트 실행 시 주의사항

  1. 환경 변수

    • .env.test 파일을 생성하여 테스트용 환경 변수 설정
    • REACT_APP_API_URL=https://127.0.0.1:5000
  2. Mock 설정

    • jest.config.js에서 필요한 mock 설정
    • setupTests.js에서 전역 mock 설정
  3. 비동기 처리

    • async/awaitwaitFor 사용
    • act로 상태 업데이트 감싸기
  4. 테스트 커버리지

    • package.jsonjest 설정에서 커버리지 기준 설정
    • 최소 80% 이상의 커버리지 유지 권장

2. 백엔드 테스트 코드

1. 패키지 설치

cd backend
pip install --save-dev pytest pytest-flask

2. 디렉터리 구조

backend/
  app.py
  city_recommend.py
  content_recommend.py
  survey_routes.py
  tests/
    __init__.py
    conftest.py
    test_city_recommend.py
    test_content_recommend.py

3. tests/test_city_recommend.py

import pytest
from collections import Counter
import backend.survey.city_recommend as city_mod
from backend.survey.city_recommend import extract_city, recommend_cities

# 1) extract_city 함수 테스트
def test_extract_city_with_metropolis_and_province():
    # 특별시/광역시 매칭
    assert extract_city("서울특별시 종로구") == "서울특별시"
    assert extract_city("부산광역시 해운대구") == "부산광역시"
    # 도 매칭
    assert extract_city("경기도 수원시") == "경기도"

def test_extract_city_no_match_or_empty():
    assert extract_city("") is None
    assert extract_city(None) is None
    assert extract_city("Unknown address") is None

# 2) recommend_cities 함수 테스트
def test_recommend_cities_basic(monkeypatch):
    # fake city_tag_data 주입
    fake_data = {
        "A도시": Counter({"tag1": 2, "tag2": 1}),
        "B도시": Counter({"tag1": 1, "tag3": 3}),
        "C도시": Counter(),  # 태그 없음
    }
    monkeypatch.setattr(city_mod, 'city_tag_data', fake_data)

    # 사용자 태그 점수 정의 (필터 태그는 무시됨)
    user_tag_scores = {
        "tag1": 1,
        "tag2": 2,
        "필터": 5
    }

    # top_n=3 으로 계산
    result = recommend_cities(user_tag_scores, top_n=3)

    # A도시: (2*1 + 1*2) / (2+1) = 4/3 ≒ 1.3333
    # B도시: (1*1 + 0)     / (1+3) = 1/4 = 0.25
    # C도시: content_count=0 이므로 avg_score=0
    expected = [
        ("A도시", pytest.approx(4/3)),
        ("B도시", pytest.approx(0.25)),
        ("C도시", 0),
    ]

    assert result == expected

4. tests/test_content_recommend.py

import json
import os
import pytest

from backend.survey.content_recommend import (
    load_city_contents,
    recommend_grouped_contents,
    recommend_grouped_detail,
    TAGGED_CONTENTS_PATH,
)

# 1) load_city_contents 함수 테스트
def test_load_city_contents_filters_by_city_and_tags(tmp_path, monkeypatch):
    # 임시 JSON 파일 생성
    sample = [
        {"areacode": "1", "tags": ["맛집"], "title": "서울맛집", "firstimage": ""},
        {"areacode": "1", "tags": [],       "title": "태그없음", "firstimage": ""},
        {"areacode": "2", "tags": ["액티비티"], "title": "인천액티", "firstimage": ""},
    ]
    file = tmp_path / "tagged_contents.json"
    file.write_text(json.dumps(sample, ensure_ascii=False), encoding="utf-8")

    # 모듈 상수 경로를 임시 파일로 덮어쓰기
    monkeypatch.setattr(
        "backend.survey.content_recommend.TAGGED_CONTENTS_PATH",
        str(file),
    )

    # 서울특별시 코드는 1
    results = load_city_contents("서울특별시")
    assert isinstance(results, list)
    # 태그가 있는 항목만 한 건 남아야 함
    assert len(results) == 1
    assert results[0]["title"] == "서울맛집"

# 2) recommend_grouped_contents 함수 테스트
def test_recommend_grouped_contents_ranking_and_grouping():
    # city_contents 샘플: 다양한 그룹·이미지 유무·점수 요소
    city_contents = [
        # group1 (맛집) + 이미지
        {"tags": ["맛집", "액티비티"], "firstimage": "img1",  "firstimage2": ""},
        # group1 (맛집) but no 이미지
        {"tags": ["맛집"],          "firstimage": "",      "firstimage2": ""},
        # group2 (액티비티) + 이미지
        {"tags": ["액티비티"],      "firstimage": "",      "firstimage2": "img2"},
        # group2 (액티비티) no 이미지
        {"tags": ["액티비티"],      "firstimage": "",      "firstimage2": ""},
        # group3 (기타) + 이미지
        {"tags": ["문화"],          "firstimage": "img3",  "firstimage2": ""},
        # 태그 없음 → 아예 무시
        {"tags": [],               "firstimage": "img_ignore", "firstimage2": ""},
    ]
    user_tags = ["맛집", "액티비티"]

    out = recommend_grouped_contents(city_contents, user_tags, top_n_each=2)

    # 반드시 세 개의 그룹이 존재
    assert set(out.keys()) == {"group1", "group2", "group3"}

    # group1: 맛집 포함된 항목만 → 2개
    g1 = out["group1"]
    assert all("맛집" in c["tags"] for c in g1)
    # 이미지 있는 항목이 먼저 와야 함
    assert g1[0]["firstimage"] == "img1"
    assert g1[1]["firstimage"] == ""

    # group2: 맛집 제외 + 액티비티 포함 → 2개
    g2 = out["group2"]
    assert all("액티비티" in c["tags"] and "맛집" not in c["tags"] for c in g2)
    # 이미지 있는 항목 우선
    assert g2[0]["firstimage2"] == "img2"

    # group3: 위 두 그룹에 속하지 않는 나머지(태그 있고)
    g3 = out["group3"]
    assert all(("맛집" not in c["tags"] and "액티비티" not in c["tags"]) for c in g3)
    

5. 실행 및 커버리지

cd backend
pytest --maxfail=1 --disable-warnings -q
# 커버리지 리포트
pytest --cov=backend --cov-report=html

주의사항

  • 세션 설정: client.session_transaction()user_id 를 주입해야 인증이 필요한 라우트 테스트 가능
  • 파일 경로: 테스트용 임시 디렉터리(tmp_path)를 사용해 실제 프로덕션 데이터를 건드리지 않도록 구성
  • 환경변수: 필요 시 PROFILE_DIR 같은 설정을 conftest.py 에서 오버라이드

Clone this wiki locally