Skip to content

Conversation

@asn6878
Copy link
Member

@asn6878 asn6878 commented Nov 21, 2025

🔨 테스크

Issue

📋 작업 내용

추상클래스가 사용된 로직의 유닛 테스트를 어떻게 해야 할까?

추상 클래스를 활용하는 케이스는 현재 feed-crawler내에 크게 두가지가 존재합니다.

  1. BaseFeedParser (이 경우는 FeedParserManager를 테스틀하여 처리합니다.)
  2. AbstractQueueWorker

이러한 패턴에서는 추상 클래스가 "공통적으로 어떤 순서로 무엇을 할지"를 정의하고, 구체 클래스는 그중 특정 구현체마다 달라진 작업을 "어떻게 할지"만 구현합니다. 이때 내부 구현 메서드들은 protected으로 캡슐화 되어 관리됩니다.

AbstractQueueWorker.start()
├── logger.info("작업 시작")
├── processQueue()        ← 구체 클래스가 구현
├── catch → logger.error()
└── logger.info("작업 완료")

이런식의 구성이 존재할 때, 저희는 processQueue()가 어떻게 동작하고 어떤 결과를 수행했는지는 관심이 없으며 테스트 대상에서 제외됩니다. (이 포스팅을 읽어보시면 좋습니다.)

중점적으로 테스트 할 대상

  • 템플릿의 실행 순서가 올바른가?
  • 에러 발생 시 정상적으로 핸들링 하는가?

근데 ClaudeEventWorker는 왜 테스트 하나요?

ClaudeEventWorker는 AI 에이전트에게 별도의 호출이 필요한 케이스이기에 예외적으로 구현체를 테스트 하였습니다.

  • 외부 서비스 의존 로직 특성상 데이터 포맷에 예민함.
  • 비용이 발생하는 서비스로, 재시도 로직이 엄격 해야함.

그 외 핵심 로직 유닛 테스트 작성

feed-crawler 및 parser-util등 비즈니스 로직 실행에 필요한 로직들에 대한 테스트를 작성하였습니다.

요약

테스트 파일 검증 대상 핵심 검증 포인트
abstract-queue-worker.spec.ts 템플릿 메서드 패턴 시작/완료 로그, 에러 처리 후 작업 완료
parser.spec.ts RSS/Atom 파서 형식 식별, 링크 추출, 특수문자 처리
feed-parser-manager.spec.ts 파서 선택 로직 Short-circuit 동작, 에러 복원력
feed-crawler.spec.ts 크롤링 워크플로우 조기 종료, 병렬 처리, 에러 전파
claude-event-worker.spec.ts AI 처리 워커 재시도 경계값(deathCount), API 응답 파싱, Rate Limiting
image

@asn6878 asn6878 self-assigned this Nov 21, 2025
@asn6878 asn6878 added the ✅ Test 테스트 관련 (storybook, vitest, jest 등) label Nov 21, 2025
Copilot finished reviewing on behalf of asn6878 November 21, 2025 12:05
Copy link

Copilot AI left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Pull Request Overview

이 PR은 feed-crawler 모듈의 핵심 비즈니스 로직에 대한 포괄적인 유닛 테스트를 추가합니다. 추상 클래스 패턴을 사용하는 코드의 템플릿 메서드 실행 흐름과 에러 처리를 중점적으로 검증하며, RSS/Atom 파서, 크롤러 워크플로우, AI 워커의 재시도 로직 등을 테스트합니다.

주요 변경사항:

  • 고정된 날짜를 사용하여 시간 의존적인 테스트의 안정성을 개선
  • URL 기반 조건부 fetch 모킹으로 순서 의존성 제거
  • 추상 클래스의 템플릿 메서드 패턴과 에러 핸들링 검증에 집중

Reviewed Changes

Copilot reviewed 6 out of 6 changed files in this pull request and generated 5 comments.

Show a summary per file
File Description
feed-crawler/test/unit/parser.spec.ts 고정 날짜 사용으로 시간 의존성 제거, URL 기반 조건부 모킹으로 테스트 안정성 향상
feed-crawler/test/unit/parser-util.spec.ts ParserUtil의 썸네일 추출, URL 처리, HTML 엔티티 변환 로직에 대한 포괄적 테스트 추가
feed-crawler/test/unit/feed-parser-manager.spec.ts 파서 선택 로직, 에러 복원력, short-circuit 동작 검증 테스트 추가
feed-crawler/test/unit/feed-crawler.spec.ts 크롤링 워크플로우, 조기 종료 시나리오, 병렬 처리 동작 테스트 추가
feed-crawler/test/unit/claude-event-worker.spec.ts AI 워커의 재시도 로직, deathCount 경계값, API 응답 파싱, rate limiting 테스트 추가
feed-crawler/test/unit/abstract-queue-worker.spec.ts 템플릿 메서드 패턴의 실행 순서와 에러 처리 검증 테스트 추가

Comment on lines +217 to +240
// 병렬 실행 검증: 첫 번째 호출에 지연을 주어 병렬 실행 시 순서가 뒤바뀌는지 확인
mockFeedParserManager.fetchAndParse
.mockImplementationOnce(async () => {
await new Promise((resolve) => setTimeout(resolve, 50));
callOrder.push(1);
return [mockFeedDetails[0]];
})
.mockImplementationOnce(async () => {
callOrder.push(2);
return [mockFeedDetails[1]];
});

// When
const result = await feedCrawler['feedGroupByRss'](
mockRssObjects,
startTime,
);

// Then
expect(mockFeedParserManager.fetchAndParse).toHaveBeenCalledTimes(2);
expect(result).toEqual([[mockFeedDetails[0]], [mockFeedDetails[1]]]);
// 병렬 실행이면 지연이 없는 두 번째가 먼저 완료됨
expect(callOrder).toEqual([2, 1]);
});
Copy link

Copilot AI Nov 21, 2025

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

[nitpick] P5) [테스트 안정성] setTimeout을 사용한 병렬 처리 테스트는 테스트 환경이나 시스템 부하에 따라 불안정할 수 있습니다. 50ms 지연이 항상 순서를 보장하지 않을 수 있습니다.

더 안정적인 방법은:

  1. Promise.all이 호출되는지 확인하거나
  2. 지연 시간을 충분히 늘리거나 (100ms 이상)
  3. 각 호출이 완료되기 전에 다음 호출이 시작되는지 확인하는 방식

현재 테스트는 동작하지만, CI/CD 환경에서 간헐적으로 실패할 가능성이 있습니다.

Copilot uses AI. Check for mistakes.
import { FeedParserManager } from '../../src/common/parser/feed-parser-manager';
import { RssObj } from '../../src/common/types';

// test시에는 필터링 하는 시간대가 매우 광범위하기에, 시간대를 신경 쓸 필요없음.
Copy link

Copilot AI Nov 21, 2025

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

P4) [맞춤법] 주석의 맞춤법을 수정해야 합니다:

  • "test시에는" → "테스트 시에는" (한글로 통일)
  • "필터링 하는" → "필터링하는" (띄어쓰기)
  • "필요없음" → "필요 없음" (띄어쓰기)
Suggested change
// test시에는 필터링 하는 시간대가 매우 광범위하기에, 시간대를 신경 쓸 필요없음.
// 테스트 시에는 필터링하는 시간대가 매우 광범위하기에, 시간대를 신경 쓸 필요 없음.

Copilot uses AI. Check for mistakes.

it('정상적인 feedDetail을 반환해야 한다.', async () => {
// 고정 날짜보다 이전 시간을 startTime으로 사용하여 피드가 필터링되도록 함
const startTime = new Date();
Copy link

Copilot AI Nov 21, 2025

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

P3) [문서화] 주석과 코드가 일치하지 않습니다. 주석에는 "고정 날짜보다 이전 시간을 startTime으로 사용하여 피드가 필터링되도록 함"이라고 설명하고 있지만, 코드는 new Date()로 현재 시간을 사용하고 있습니다.

FIXED_DATE는 테스트 상단에서 이미 new Date()로 생성되어 피드의 pubDate에 사용되고 있으므로, 여기서 다시 new Date()를 호출하면 약간의 시간차가 발생할 수 있습니다.

의도대로라면 다음과 같이 수정하는 것이 좋겠습니다:

// FIXED_DATE 이전 시간을 사용하여 모든 피드가 포함되도록 함
const startTime = new Date(FIXED_DATE.getTime() - 1000); // 1초 전

또는 주석을 코드에 맞게 수정:

// 현재 시간을 startTime으로 사용 (테스트에서는 시간 범위가 광범위함)
const startTime = new Date();
Suggested change
const startTime = new Date();
const startTime = new Date(FIXED_DATE.getTime() - 1000); // 1초 전

Copilot uses AI. Check for mistakes.

it('정상적인 feedDetail을 반환해야 한다.', async () => {
// 고정 날짜보다 이전 시간을 startTime으로 사용하여 피드가 필터링되도록 함
const startTime = new Date();
Copy link

Copilot AI Nov 21, 2025

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

P3) [문서화] 주석과 코드가 일치하지 않습니다. RSS 2.0 테스트(line 151-152)와 동일한 문제가 있습니다. 주석에는 "고정 날짜보다 이전 시간을 startTime으로 사용"이라고 하지만 실제로는 new Date()를 사용하고 있어 일관성이 없습니다.

Suggested change
const startTime = new Date();
const startTime = FIXED_DATE;

Copilot uses AI. Check for mistakes.
);

// When
const result = await claudeEventWorker['loadFeeds']();
Copy link

Copilot AI Nov 21, 2025

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Unused variable result.

Suggested change
const result = await claudeEventWorker['loadFeeds']();
await claudeEventWorker['loadFeeds']();

Copilot uses AI. Check for mistakes.
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

✅ Test 테스트 관련 (storybook, vitest, jest 등)

Projects

None yet

Development

Successfully merging this pull request may close these issues.

[BE] feed-crawler 유닛 테스트 추가

2 participants