카테고리 없음

Questack 개발 일지 - 2026-05-30

montmer27 2026. 5. 30. 22:31
오늘은 Questack의 수집 파이프라인을 live API 없이 검증 가능한 구조로 만드는 데 집중했다. 어제까지는 GitHub/RSS 수집, 랭킹, 브리핑 생성 기능이 동작하는 상태였다면, 오늘은 외부 서비스 상태와 무관하게 “수집기가 같은 입력에 대해 같은 결과를 내는지”를 테스트로 고정했다.

구현한 기능

첫 번째 작업은 GitHub 수집기 fixture replay 테스트였다. GitHub Search API 응답 예시를 src/test/resources/fixtures/github/search-repositories.json에 저장하고, 테스트에서 이 JSON을 GithubRepositorySearchResponse로 역직렬화하도록 했다. 실제 GitHub API를 호출하지 않고도 GithubCollector가 repository item을 CollectedItem으로 잘 정규화하는지 확인할 수 있게 된 것이다.

이 테스트에서는 다음 흐름을 검증했다.

  • GitHub fixture JSON을 client DTO로 읽기
  • GithubSearchClient mock이 fixture item을 반환
  • GithubCollector가 Source를 생성
  • repository title, summary, canonical URL, author, publishedAt을 CollectedItem에 저장
  • 같은 fixture를 다시 replay하면 canonical URL 기준으로 duplicate skip

두 번째 작업은 RSS/Atom 수집기 fixture replay 테스트였다. RSS XML fixture와 Atom XML fixture를 각각 추가하고, MockRestServiceServer가 실제 feed URL 대신 fixture XML을 반환하도록 했다. 이 덕분에 live RSS feed에 접근하지 않고도 RssCollector의 fetch, parse, normalize, persist 흐름을 통합 테스트할 수 있게 됐다.

RSS replay 테스트에서는 다음을 검증했다.

  • RSS item과 Atom entry parsing
  • feed별 Source 생성
  • RssTextNormalizer를 통한 HTML 제거와 텍스트 정규화
  • link 없는 item skip
  • canonical URL duplicate skip
  • CollectedItemType.BLOG_POST 저장

오늘 작업으로 milestone의 앞쪽 두 이슈가 해결됐다.

  • #9 Add fixture replay tests for GitHub collection
  • #8 Add fixture replay tests for RSS collection

이제 Questack은 GitHub/RSS 수집 파이프라인을 live API 없이 재현 가능한 fixture로 검증할 수 있다. MVP Done Criteria 중 “A replay harness can test collection/ranking without live API calls”에 한 걸음 크게 가까워졌다.

트러블슈팅

첫 번째 문제는 GitHub replay 테스트를 추가한 뒤 기존 RankingServiceTest가 실패한 것이다. 에러는 DataIntegrityViolationException이었고, 원인은 sources.name unique 제약 충돌이었다.

여러 @SpringBootTest가 같은 H2 application context를 재사용할 수 있는데, 기존 ranking 테스트는 테스트 시작 전에 DB를 정리하지 않았다. 하네스 테스트가 늘어나면서 이전 테스트 데이터가 남아 Source("Test GitHub", ...) 저장 시점에 충돌이 발생했다.

해결은 명확했다. repository를 직접 사용하는 Spring Boot 테스트에서 @BeforeEach cleanup을 추가했다. FK 순서를 고려해 RankingScoreRepository, CollectedItemRepository, SourceRepository 순서로 정리했다. 이 내용은 TR-006으로 기록했다.

두 번째 문제는 RSS replay 테스트에서 MockRestServiceServer expectation을 중간에 다시 추가하다가 IllegalStateException이 난 것이다. 같은 테스트 안에서 첫 번째 replay 요청을 실행한 뒤, 검증/초기화 없이 두 번째 replay expectation을 추가한 것이 원인이었다.

해결은 첫 번째 rssCollector.collect() 직후 server.verify()와 server.reset()을 호출한 뒤 두 번째 expectation을 등록하는 방식이었다. 이 내용은 TR-007로 기록했다.

기술적 의사결정

오늘 추가된 결정은 두 가지다.

TD-017: GitHub 수집기는 fixture replay 테스트로 검증하기로 했다. live GitHub API 호출은 rate limit, 네트워크, 응답 데이터 변화에 영향을 받기 때문에 기본 테스트로 적합하지 않다. 대신 JSON fixture를 client DTO로 역직렬화하고, mock client가 반환하게 해서 수집기의 정규화 계약을 고정했다.

TD-018: RSS 수집기도 fixture replay 테스트로 검증하기로 했다. RSS feed는 외부 사이트 상태와 feed 내용 변화에 영향을 받기 쉽기 때문에, XML fixture를 통해 RSS/Atom parsing부터 persistence까지 검증하도록 했다. HTTP mock은 별도 라이브러리를 추가하지 않고 Spring test의 MockRestServiceServer를 사용했다.

두 결정 모두 기존 방향과 충돌하지 않는다. TD-012의 project harness 강화, TD-013의 RSS 통합, TD-015의 milestone/issue 기반 개발 플로우와 잘 이어진다.

관련 커밋 / PR

 

test: add RSS collector fixture replay by ginsengcandy · Pull Request #18 · ginsengcandy/Questack

Summary add RSS and Atom XML fixtures under test resources add a replay test that verifies RSS/Atom fetch, parse, normalize, and persistence without live feed requests cover blank-link skipping an...

github.com

 

 

test: add GitHub collector fixture replay by ginsengcandy · Pull Request #17 · ginsengcandy/Questack

Summary add a GitHub Search API JSON fixture under test resources add a replay test that verifies GitHub repository normalization into CollectedItem without live API calls cover canonical URL dupl...

github.com

 

관련 merge commit:

  • 3544046 Merge pull request #17 from ginsengcandy/test/9-github-fixture-replay
  • 202b8b4 Merge pull request #18 from ginsengcandy/test/8-rss-fixture-replay

오늘의 핵심 성과는 “외부 데이터를 수집한다”에서 “외부 데이터 수집을 재현 가능하게 검증한다”로 넘어간 것이다. 다음 순서는 milestone 기준으로 #7 Lock Top 3 ranking quality with labeled fixtures다. 이제 수집 fixture 위에 ranking 품질을 고정하면, Questack의 Week 1 파이프라인은 꽤 단단한 뼈대를 갖추게 된다.