Skip to content

Conversation

@DongChyeon
Copy link
Member

@DongChyeon DongChyeon commented Nov 28, 2025

💡 Issue

🌱 Key changes

UnconfinedTestDispatcher 사용을 위한 DispatcherRule, 테스트 관련 Fake 객체를 격리시키기 위해 core:testing 모듈을 생성했습니다.
당장에는 FakeRepository만 있는 상황이라 data-api에 Fake 객체를 두어도 무방했지만, 추후 다른 모듈에 대한 Fake 객체도 필요할 가능성을 대비해서 모듈을 분리했습니다.

테스트 데이터의 경우 함수로 제공하였는데요.
변수에 저장해서 사용하는 방식에 비해 매번 호출되어 생성하는 비용이 발생하지만

fakeScheduleRepository = FakeScheduleRepository(
    upcomingSessionInfo = ScheduleTestData.upcomingSessionInfo().copy(
        canCheckIn = true
    )
)

copy() 메소드를 통해 테스트 데이터를 유연하게 조정할 수 있기 때문에 함수로 해뒀습니다.

또한 Repository의 경우 Stub이나 Mock이 아닌 Fake로 선언했는데요
Repository 내에서 인메모리 캐싱과 같은 로직이 있기도 해서 실제 동작을 흉내낸 Fake가 더 적합하다고 생각했습니다.

✅ To Reviewers

a1805c2 저번 PR에서 누락된 커밋도 같이 올렸어요

새로고침, API 캐싱 로직, 출석코드 입력쪽에 대한 검증을 주로 했는데요
추가하거나 없애도 될 부분 있으면 말씀해주세요!
저도 ViewModel 쪽 테스트 코드 작성은 처음이라서 피드백 환영해요!

📸 스크린샷

스크린샷
파일첨부바람

Summary by CodeRabbit

테스트

  • 테스트 인프라 및 테스트 커버리지 확대
  • 홈 화면 뷰모델에 대한 종합 테스트 추가

업데이트

  • 코루틴 라이브러리 버전 업그레이드 (1.8.1 → 1.10.1)
  • 모킹 및 테스트 라이브러리 의존성 추가

기타

  • 스케줄 화면 상태 관리 개선
  • 테스트 레이아웃 최적화

✏️ Tip: You can customize this high-level summary in your review settings.

@coderabbitai
Copy link

coderabbitai bot commented Nov 28, 2025

Important

Review skipped

Auto reviews are limited based on label configuration.

🏷️ Required labels (at least one) (1)
  • CodeRabbit

Please check the settings in the CodeRabbit UI or the .coderabbit.yaml file in this repository. To trigger a single review, invoke the @coderabbitai review command.

You can disable this status message by setting the reviews.review_status to false in the CodeRabbit configuration file.

Walkthrough

테스트 인프라 모듈을 추가하여 단위 테스트 기반을 구축합니다. 테스트 라이브러리 의존성을 도입하고, 테스트 유틸리티(MainDispatcherRule), 테스트 데이터 팩토리, 가짜 저장소 구현을 제공하며, HomeViewModel 테스트 스위트를 작성합니다.

Changes

코호트 / 파일(들) 변경 요약
테스트 모듈 설정
core/testing/.gitignore, core/testing/build.gradle.kts, settings.gradle.kts
새로운 테스트 모듈 생성 및 Gradle 프로젝트에 등록, build 디렉토리 무시 규칙 추가
테스트 유틸리티
core/testing/src/main/java/com/yapp/testing/MainDispatcherRule.kt
TestWatcher를 확장한 디스패처 관리 유틸리티로, 테스트 실행 중 메인 디스패처를 UnconfinedTestDispatcher로 설정
테스트 데이터 팩토리
core/testing/src/main/java/com/yapp/testing/data/AttendanceTestData.kt, OperationsTestData.kt, ScheduleTestData.kt
출석, 운영, 일정 관련 모델의 미리 정의된 테스트 데이터 인스턴스를 생성하는 팩토리 객체들 추가
가짜 저장소 구현
core/testing/src/main/java/com/yapp/testing/repository/FakeAttendanceRepository.kt, FakeOperationsRepository.kt, FakeScheduleRepository.kt
실제 저장소 인터페이스를 구현하는 테스트 더블로, 구성 가능한 상태와 요청 추적 기능 제공
빌드 설정 업데이트
build-logic/src/main/java/com/yapp/KotlinAndroid.kt, yapp.android.feature.gradle.kts, gradle/libs.versions.toml
junit, mockk, kotlinx-coroutines-test 라이브러리 추가, 기존 코루틴 버전 업데이트 (1.8.1 → 1.10.1)
HomeViewModel 테스트
feature/home/src/test/java/com/yapp/home/HomeViewModelTest.kt
로딩 상태, 세션 조회, 새로고침, 규칙 링크 캐싱, 출석코드 입력 등 11개 테스트 케이스 추가
테스트 코드 정리
feature/home/src/test/java/com/yapp/home/ExampleUnitTest.kt
샘플 단위 테스트 파일 삭제
Schedule 화면 리팩토링
feature/schedule/src/main/java/com/yapp/feature/schedule/ScheduleScreen.kt
새로운 SchedulePagerState 추상화 계층 추가로 탭과 페이저 상태 동기화 로직 개선

Estimated code review effort

🎯 3 (Moderate) | ⏱️ ~20 minutes

  • FakeScheduleRepository.kt: 복잡한 상태 관리 및 월별 일정 캐싱 로직 검토 필요
  • HomeViewModelTest.kt: 11개 테스트 케이스의 테스트 논리 및 가짜 저장소 설정 검증
  • ScheduleScreen.kt의 SchedulePagerState: 새로운 상태 추상화 계층의 LaunchedEffect 동기화 로직 확인
  • 테스트 데이터 팩토리들: 테스트 데이터의 정확성 및 실제 모델 구조와의 일치성 검증

Possibly related PRs

Suggested reviewers

  • TaeseongYun
  • ashwon12
  • jinukeu
  • HyungMinKims

Poem

🐰 테스트의 성 지었네,
가짜 저장소로 진실 담고,
코루틴 디스패처를 차곡이 정리하니,
홈화면 뷰모델도 웃네—
안정된 검증의 날개로 날아오르다! 🚀

Pre-merge checks and finishing touches

❌ Failed checks (1 warning)
Check name Status Explanation Resolution
Docstring Coverage ⚠️ Warning Docstring coverage is 2.00% which is insufficient. The required threshold is 80.00%. You can run @coderabbitai generate docstrings to improve docstring coverage.
✅ Passed checks (4 passed)
Check name Status Explanation
Description Check ✅ Passed Check skipped - CodeRabbit’s high-level summary is enabled.
Title check ✅ Passed The PR title '[Test] HomeViewModel 테스트 코드 작성' clearly and directly describes the main objective: writing test code for HomeViewModel. It is concise and specific.
Linked Issues check ✅ Passed The PR fulfills the linked issue #237 requirement by implementing comprehensive HomeViewModel test code, including test infrastructure (MainDispatcherRule, test data utilities, fake repositories), and test cases covering key scenarios.
Out of Scope Changes check ✅ Passed All changes are scoped to supporting HomeViewModel tests: new core:testing module with test utilities, fake repositories, test data, gradle configuration updates, and actual HomeViewModel test implementation. No unrelated modifications detected.

Thanks for using CodeRabbit! It's free for OSS, and your support helps us grow. If you like it, consider giving us a shout-out.

❤️ Share

Comment @coderabbitai help to get the list of available commands and usage tips.

@DongChyeon
Copy link
Member Author

@coderabbitai review

@coderabbitai
Copy link

coderabbitai bot commented Nov 28, 2025

✅ Actions performed

Review triggered.

Note: CodeRabbit is an incremental review system and does not re-review already reviewed commits. This command is applicable only when automatic reviews are paused.

Copy link

@coderabbitai coderabbitai bot left a comment

Choose a reason for hiding this comment

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

Actionable comments posted: 9

📜 Review details

Configuration used: CodeRabbit UI

Review profile: ASSERTIVE

Plan: Pro

📥 Commits

Reviewing files that changed from the base of the PR and between c4f20cd and 65cf5ff.

📒 Files selected for processing (16)
  • build-logic/src/main/java/com/yapp/KotlinAndroid.kt (1 hunks)
  • build-logic/src/main/java/yapp.android.feature.gradle.kts (1 hunks)
  • core/testing/.gitignore (1 hunks)
  • core/testing/build.gradle.kts (1 hunks)
  • core/testing/src/main/java/com/yapp/testing/MainDispatcherRule.kt (1 hunks)
  • core/testing/src/main/java/com/yapp/testing/data/AttendanceTestData.kt (1 hunks)
  • core/testing/src/main/java/com/yapp/testing/data/OperationsTestData.kt (1 hunks)
  • core/testing/src/main/java/com/yapp/testing/data/ScheduleTestData.kt (1 hunks)
  • core/testing/src/main/java/com/yapp/testing/repository/FakeAttendanceRepository.kt (1 hunks)
  • core/testing/src/main/java/com/yapp/testing/repository/FakeOperationsRepository.kt (1 hunks)
  • core/testing/src/main/java/com/yapp/testing/repository/FakeScheduleRepository.kt (1 hunks)
  • feature/home/src/test/java/com/yapp/home/ExampleUnitTest.kt (0 hunks)
  • feature/home/src/test/java/com/yapp/home/HomeViewModelTest.kt (1 hunks)
  • feature/schedule/src/main/java/com/yapp/feature/schedule/ScheduleScreen.kt (6 hunks)
  • gradle/libs.versions.toml (3 hunks)
  • settings.gradle.kts (1 hunks)
💤 Files with no reviewable changes (1)
  • feature/home/src/test/java/com/yapp/home/ExampleUnitTest.kt
🧰 Additional context used
📓 Path-based instructions (6)
**/*.gradle.kts

📄 CodeRabbit inference engine (AGENTS.md)

Use Gradle Kotlin DSL for build scripts (.gradle.kts)

Files:

  • settings.gradle.kts
  • build-logic/src/main/java/yapp.android.feature.gradle.kts
  • core/testing/build.gradle.kts
**/*.kt

📄 CodeRabbit inference engine (AGENTS.md)

**/*.kt: Use 4-space indentation in Kotlin source files
Avoid wildcard imports and keep imports sorted in Kotlin files
Naming — Classes/Objects use UpperCamelCase
Naming — Functions and variables use lowerCamelCase
Naming — Constants use UPPER_SNAKE_CASE
Naming — Packages are lowercase

Files:

  • core/testing/src/main/java/com/yapp/testing/data/OperationsTestData.kt
  • core/testing/src/main/java/com/yapp/testing/data/AttendanceTestData.kt
  • feature/schedule/src/main/java/com/yapp/feature/schedule/ScheduleScreen.kt
  • core/testing/src/main/java/com/yapp/testing/repository/FakeAttendanceRepository.kt
  • core/testing/src/main/java/com/yapp/testing/data/ScheduleTestData.kt
  • core/testing/src/main/java/com/yapp/testing/MainDispatcherRule.kt
  • build-logic/src/main/java/com/yapp/KotlinAndroid.kt
  • feature/home/src/test/java/com/yapp/home/HomeViewModelTest.kt
  • core/testing/src/main/java/com/yapp/testing/repository/FakeOperationsRepository.kt
  • core/testing/src/main/java/com/yapp/testing/repository/FakeScheduleRepository.kt
**/src/main/java/**/*.kt

📄 CodeRabbit inference engine (AGENTS.md)

Place Kotlin production source under module/src/main/java

Files:

  • core/testing/src/main/java/com/yapp/testing/data/OperationsTestData.kt
  • core/testing/src/main/java/com/yapp/testing/data/AttendanceTestData.kt
  • feature/schedule/src/main/java/com/yapp/feature/schedule/ScheduleScreen.kt
  • core/testing/src/main/java/com/yapp/testing/repository/FakeAttendanceRepository.kt
  • core/testing/src/main/java/com/yapp/testing/data/ScheduleTestData.kt
  • core/testing/src/main/java/com/yapp/testing/MainDispatcherRule.kt
  • build-logic/src/main/java/com/yapp/KotlinAndroid.kt
  • core/testing/src/main/java/com/yapp/testing/repository/FakeOperationsRepository.kt
  • core/testing/src/main/java/com/yapp/testing/repository/FakeScheduleRepository.kt
{core/ui,core/designsystem,feature/*}/src/main/java/**/*.kt

📄 CodeRabbit inference engine (AGENTS.md)

{core/ui,core/designsystem,feature/*}/src/main/java/**/*.kt: Compose/UI: limit to one public component per file
Compose/UI: filename must match the public class/composable it contains

Files:

  • feature/schedule/src/main/java/com/yapp/feature/schedule/ScheduleScreen.kt
**/src/{test,androidTest}/**/*Test.kt

📄 CodeRabbit inference engine (AGENTS.md)

Name test classes/files with the suffix "Test"

Files:

  • feature/home/src/test/java/com/yapp/home/HomeViewModelTest.kt
**/src/test/**/*.kt

📄 CodeRabbit inference engine (AGENTS.md)

Place unit tests under module/src/test

Files:

  • feature/home/src/test/java/com/yapp/home/HomeViewModelTest.kt
🧠 Learnings (9)
📚 Learning: 2025-01-09T07:44:48.036Z
Learnt from: ashwon12
Repo: YAPP-admin/yappu-world-android PR: 5
File: feature/signup/build.gradle.kts:1-47
Timestamp: 2025-01-09T07:44:48.036Z
Learning: Pure Kotlin modules (model, data-api) should only have Kotlin dependencies and no Android-specific dependencies.

Applied to files:

  • gradle/libs.versions.toml
  • build-logic/src/main/java/com/yapp/KotlinAndroid.kt
  • core/testing/build.gradle.kts
📚 Learning: 2025-01-08T06:56:07.931Z
Learnt from: jinukeu
Repo: YAPP-admin/yappu-world-android PR: 5
File: core/domain/build.gradle.kts:1-4
Timestamp: 2025-01-08T06:56:07.931Z
Learning: In this project's architecture, the domain module is designed as an Android library module, not a pure Kotlin library, following Google's recommended architecture guidelines.

Applied to files:

  • gradle/libs.versions.toml
  • build-logic/src/main/java/yapp.android.feature.gradle.kts
  • core/testing/build.gradle.kts
📚 Learning: 2025-09-19T11:37:27.391Z
Learnt from: CR
Repo: YAPP-admin/yappu-world-android PR: 0
File: AGENTS.md:0-0
Timestamp: 2025-09-19T11:37:27.391Z
Learning: Applies to **/src/androidTest/**/*.kt : Place instrumented tests under module/src/androidTest

Applied to files:

  • gradle/libs.versions.toml
  • settings.gradle.kts
  • core/testing/src/main/java/com/yapp/testing/data/OperationsTestData.kt
  • build-logic/src/main/java/yapp.android.feature.gradle.kts
  • build-logic/src/main/java/com/yapp/KotlinAndroid.kt
  • feature/home/src/test/java/com/yapp/home/HomeViewModelTest.kt
  • core/testing/build.gradle.kts
📚 Learning: 2025-09-19T11:37:27.391Z
Learnt from: CR
Repo: YAPP-admin/yappu-world-android PR: 0
File: AGENTS.md:0-0
Timestamp: 2025-09-19T11:37:27.391Z
Learning: Applies to **/src/test/**/*.kt : Place unit tests under module/src/test

Applied to files:

  • gradle/libs.versions.toml
  • settings.gradle.kts
  • build-logic/src/main/java/com/yapp/KotlinAndroid.kt
  • feature/home/src/test/java/com/yapp/home/HomeViewModelTest.kt
  • core/testing/build.gradle.kts
📚 Learning: 2025-09-19T11:37:27.391Z
Learnt from: CR
Repo: YAPP-admin/yappu-world-android PR: 0
File: AGENTS.md:0-0
Timestamp: 2025-09-19T11:37:27.391Z
Learning: Applies to **/src/{test,androidTest}/**/*Test.kt : Name test classes/files with the suffix "Test"

Applied to files:

  • gradle/libs.versions.toml
  • core/testing/src/main/java/com/yapp/testing/data/OperationsTestData.kt
  • core/testing/src/main/java/com/yapp/testing/data/ScheduleTestData.kt
  • build-logic/src/main/java/com/yapp/KotlinAndroid.kt
  • feature/home/src/test/java/com/yapp/home/HomeViewModelTest.kt
  • core/testing/build.gradle.kts
📚 Learning: 2025-01-08T06:56:08.678Z
Learnt from: jinukeu
Repo: YAPP-admin/yappu-world-android PR: 5
File: settings.gradle.kts:38-42
Timestamp: 2025-01-08T06:56:08.678Z
Learning: In Gradle settings files, module declarations should be unique to prevent build issues. Each module should only be included once in the `include` statements.

Applied to files:

  • settings.gradle.kts
📚 Learning: 2025-09-19T11:37:27.391Z
Learnt from: CR
Repo: YAPP-admin/yappu-world-android PR: 0
File: AGENTS.md:0-0
Timestamp: 2025-09-19T11:37:27.391Z
Learning: Applies to **/src/main/java/**/*.kt : Place Kotlin production source under module/src/main/java

Applied to files:

  • build-logic/src/main/java/com/yapp/KotlinAndroid.kt
  • core/testing/build.gradle.kts
📚 Learning: 2025-09-19T11:37:27.391Z
Learnt from: CR
Repo: YAPP-admin/yappu-world-android PR: 0
File: AGENTS.md:0-0
Timestamp: 2025-09-19T11:37:27.391Z
Learning: Applies to **/*.kt : Avoid wildcard imports and keep imports sorted in Kotlin files

Applied to files:

  • build-logic/src/main/java/com/yapp/KotlinAndroid.kt
📚 Learning: 2025-09-19T11:37:27.391Z
Learnt from: CR
Repo: YAPP-admin/yappu-world-android PR: 0
File: AGENTS.md:0-0
Timestamp: 2025-09-19T11:37:27.391Z
Learning: Applies to **/*.gradle.kts : Use Gradle Kotlin DSL for build scripts (.gradle.kts)

Applied to files:

  • build-logic/src/main/java/com/yapp/KotlinAndroid.kt
  • core/testing/build.gradle.kts
🧬 Code graph analysis (1)
core/testing/src/main/java/com/yapp/testing/data/ScheduleTestData.kt (1)
feature/schedule/src/main/java/com/yapp/feature/schedule/component/DateGroupedScheduleItem.kt (1)
  • ScheduleList (140-197)
🔇 Additional comments (20)
core/testing/.gitignore (1)

1-1: LGTM!

새로운 core/testing 모듈의 빌드 출력을 올바르게 무시하는 표준 .gitignore 항목입니다.

gradle/libs.versions.toml (1)

80-81: 테스트 라이브러리 추가가 적절합니다.

mockkkotlinx-coroutines-test 라이브러리가 올바르게 선언되었습니다. 기존 파일의 패턴을 따르고 있으며, 테스트 인프라 구축에 필요한 핵심 의존성입니다.

core/testing/src/main/java/com/yapp/testing/data/ScheduleTestData.kt (1)

22-155: 잘 구성된 테스트 데이터 팩토리입니다.

PR 목적에 맞게 변수 대신 함수로 구현하여 copy()를 통한 유연한 조정이 가능합니다. 다양한 상태(PENDING, DONE, ONGOING)와 타입(SESSION, TASK)을 커버하는 포괄적인 테스트 데이터를 제공하며, notices() 헬퍼 함수가 적절히 private으로 캡슐화되어 있습니다.

feature/schedule/src/main/java/com/yapp/feature/schedule/ScheduleScreen.kt (2)

163-197: 양방향 동기화 로직이 올바르게 구현되었습니다.

선택된 탭과 페이저 간의 양방향 동기화가 rememberUpdatedStateLaunchedEffect를 적절히 활용하여 구현되었습니다. distinctUntilChanged()를 사용해 중복 이벤트를 방지하는 점도 좋습니다.


199-211: 구현이 올바릅니다만, PR 범위와의 관련성을 확인해주세요.

SchedulePagerState 클래스는 @Stable 어노테이션과 함께 적절하게 구현되었으며, 불필요한 애니메이션을 방지하는 최적화(Line 206)도 포함되어 있습니다. 다만 이 변경사항은 HomeViewModel 테스트 작성이라는 PR의 주요 목표와 직접적인 관련이 없어 보입니다.

이 변경사항이 PR 설명에 언급된 "previously omitted commit (a1805c2)"에 해당하는지 확인해주세요.

core/testing/src/main/java/com/yapp/testing/data/OperationsTestData.kt (1)

3-5: 테스트 데이터 구조가 적절합니다.

간단하고 명확한 테스트 데이터 팩토리입니다. PR 설명에서 언급한 대로 함수 형태로 제공되어 copy()를 통한 유연한 조정이 가능합니다.

core/testing/src/main/java/com/yapp/testing/data/AttendanceTestData.kt (1)

10-41: 테스트 데이터가 잘 구조화되어 있습니다.

출석 통계와 이력 데이터가 다양한 시나리오(정상 출석, 지각, 결석)를 대표하도록 적절하게 구성되어 있습니다. @file:Suppress("MagicNumber") 어노테이션도 테스트 데이터의 특성상 적절합니다.

feature/home/src/test/java/com/yapp/home/HomeViewModelTest.kt (7)

24-46: 테스트 셋업이 올바르게 구성되었습니다.

MainDispatcherRule을 사용한 코루틴 테스트 환경 설정과 Fake Repository를 통한 의존성 주입이 적절하게 구현되었습니다.


48-77: 초기 상태 및 진입 테스트가 잘 작성되었습니다.

로딩 상태 확인, 첫 진입시 API 호출, 그리고 재진입시 중복 호출 방지에 대한 테스트가 명확하게 구현되어 있습니다. 특히 캐싱 동작을 검증하는 idempotency 테스트가 좋습니다.


79-115: 새로고침 및 캐싱 로직 테스트가 적절합니다.

명시적 새로고침시 항상 API를 호출하는 동작과, 기본 규칙 링크의 캐싱 동작이 잘 검증되고 있습니다. async를 사용한 side effect 검증 패턴도 올바릅니다.


117-143: 출석 코드 다이얼로그 테스트가 잘 구현되었습니다.

다이얼로그의 상태 초기화와 입력값에 따른 버튼 활성화 로직이 명확하게 검증되고 있습니다.


145-168: 출석 요청 성공 시나리오가 포괄적으로 테스트되었습니다.

출석 코드 제출 후 다이얼로그 닫힘, 출석 상태 갱신, 체크인 가능 여부 업데이트, 그리고 실제 출석 데이터 기록까지 전체 플로우가 잘 검증되고 있습니다.


170-185: 에러 처리 테스트가 올바르게 작성되었습니다.

출석 코드가 틀렸을 때 에러 상태가 올바르게 표시되는지 검증하고 있습니다. CodeNotCorrectException을 사용한 실패 시나리오 테스트가 적절합니다.


24-186: HomeViewModel 테스트 스위트가 매우 잘 작성되었습니다.

PR의 핵심인 HomeViewModel 테스트가 다음과 같은 측면에서 훌륭하게 구현되었습니다:

  • 초기 상태, 진입, 새로고침, 캐싱, 에러 처리 등 주요 시나리오 커버
  • Fake Repository를 활용한 격리된 단위 테스트
  • 멱등성(idempotency) 검증
  • 명확한 테스트 메서드 이름 (한글 사용이 프로젝트 컨텍스트에 적합함)
  • AAA(Arrange-Act-Assert) 패턴 준수

첫 ViewModel 테스트 작성치고는 매우 완성도가 높습니다!

core/testing/src/main/java/com/yapp/testing/repository/FakeAttendanceRepository.kt (1)

9-29: Fake Repository가 잘 설계되었습니다.

PR 설명에서 언급한 대로 stub/mock 대신 Fake 구현을 선택한 것이 적절합니다. 다음과 같은 장점이 있습니다:

  • var 속성으로 테스트별 데이터 커스터마이징 가능
  • postedAttendances 리스트로 호출 검증 가능
  • postAttendanceResult로 성공/실패 시나리오 제어 가능
  • 인메모리 동작을 잘 모사하여 실제 Repository의 동작과 유사
core/testing/src/main/java/com/yapp/testing/repository/FakeOperationsRepository.kt (2)

20-21: 테스트 검증을 위한 카운터가 잘 구현되었습니다.

basicRuleRequestCount를 사용하여 getBasicRuleLink() 호출 횟수를 추적하는 것은 테스트 검증에 유용합니다. private set을 사용하여 외부에서 읽기만 가능하도록 제한한 점도 좋습니다.


40-42: Flow emission을 위한 헬퍼 메서드가 적절합니다.

emitPositionConfigs() 메서드를 통해 테스트에서 positionConfigs Flow의 새로운 값을 emit할 수 있도록 한 점이 좋습니다. MutableStateFlow를 직접 노출하지 않고 메서드로 감싸서 캡슐화를 유지한 것도 적절합니다.

core/testing/src/main/java/com/yapp/testing/repository/FakeScheduleRepository.kt (3)

20-25: 기본값 폴백 로직이 잘 구현되었습니다.

sessionDetailMap이 비어있을 경우 기본 세션 상세 정보를 추가하는 로직이 적절합니다. 이를 통해 getSessionDetail() 메서드에서 안전한 폴백이 가능합니다.


28-36: 테스트 검증을 위한 추적 상태가 잘 구현되었습니다.

카운터(refreshUpcomingSessionsCount)와 마지막 요청 파라미터 추적(lastSessionsRange, lastScheduleYearMonth, lastRequestedSessionId)을 통해 테스트에서 리포지토리 메서드 호출을 검증할 수 있습니다. private set을 사용하여 외부에서는 읽기만 가능하도록 제한한 점도 좋습니다.


67-79: 헬퍼 메서드와 캐싱 로직이 적절합니다.

setMonthlySchedule()setSessionDetail()을 통해 테스트에서 동적으로 데이터를 설정할 수 있고, fetchMonthlySchedule()의 per-year/month 캐싱 로직도 잘 구현되었습니다. 이를 통해 테스트에서 다양한 시나리오를 쉽게 검증할 수 있습니다.

Comment on lines 57 to 67
@Test
fun_화면에_입장하면_다가오는_세션을_가져오고_로딩을_종료한다() = runTest {
// when
viewModel.store.onIntent(HomeIntent.EnterHomeScreen)

// then
assertEquals(1, fakeScheduleRepository.refreshUpcomingSessionsCount)
val state = viewModel.store.uiState.value
assertFalse(state.isLoading)
assertEquals(fakeScheduleRepository.upcomingSessionInfo, state.upcomingSession)
}
Copy link
Member

Choose a reason for hiding this comment

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

행위 기반을 위주로 테스트 코드를 작성하셨군요.

행위 기반 테스트의 경우 내성이 약하다는 단점이 있어서 상태 기반 테스트로 작성하는건 어떨까요?

예를 들어서, '강아지가 온다'를 각각 테스트로 작성한다 하면 ...

상태 기반 테스트

class Dog(var position: Int = 0) {
    fun come() {
        position += 10
    }
}

@Test
fun `come should move dog to position 10`() {
    val dog = Dog()
    
    dog.come()
    
    // 상태 검증: 강아지가 어디에 있는가?
    assertEquals(10, dog.position)
}

행위 기반 테스트

interface Leg {
    fun move()
}

class Dog(private val leg: Leg) {
    fun come() {
        leg.move()
        leg.move()
        leg.move()
        leg.move()
    }
}

@Test
fun `come should move legs 4 times`() {
    val leg = mockk<Leg>()
    every { leg.move() } just Runs
    val dog = Dog(leg)
    
    dog.come()
    
    // 행위 검증: 다리를 4번 움직였는가?
    verify(exactly = 4) { leg.move() }
}

행위 기반 테스트의 경우 '다리를 4번 움직였는가'를 검증하는데 이건 구현 세부사항이 결합되어 있어요. 만약 구현 세부사항이 강아지가 뛰어서 2번만에 온다는 걸로 바뀌게 된다면 테스트가 실패하게됩니다. (실제로 강아지가 움직인 위치는 동일함에도 테스트가 실패하기 때문에 테스트 신뢰도가 낮아지게 돼요)

반면에 상태 기반은 강아지가 원하는 위치에 왔는가만 검증하니, 리팩토링에 훨씬 유연합니다.

Copy link
Member

Choose a reason for hiding this comment

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

행위 기반을 사용하는 경우는 에러가 발생한 경우 Crashlytics 로그를 전송한다. 가 떠오르네요

Copy link
Member

Choose a reason for hiding this comment

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

assertEquals(1, fakeScheduleRepository.refreshUpcomingSessionsCount)

이 부분에 대한 검증은 행위 검증이라서 빼면 어떨까 싶습니다.

다른 코드에서도 행위 검증 관련 코드는 빼면 어떨까요?

Copy link
Member Author

@DongChyeon DongChyeon Nov 29, 2025

Choose a reason for hiding this comment

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

@jinukeu
ViewModel 테스트의 경우 사용자가 행동(Intent)를 했을 때 State가 제대로 바뀌었는지 검증하기 위한 것이라고 생각하는데요

캐싱 로직이나 초기화된 상태에서 화면 재진입 시 API가 다시 호출되지 않는지 테스트하기 위해 API 호출 횟수 또한 검증했는데 이를 상태 기반만으로 검증을 한다면

@Test
fun_화면에_입장하면_다가오는_세션을_가져오고_로딩을_종료한다() = runTest {
    // given
    fakeScheduleRepository.upcomingSessionInfo = ScheduleTestData.upcomingSessionInfo().copy(
        sessionId = "12345"
    ) 

    // when
    viewModel.store.onIntent(HomeIntent.EnterHomeScreen)

    // then
    val state = viewModel.store.uiState.value
    assertFalse(state.isLoading)
    assertEquals(fakeScheduleRepository.upcomingSessionInfo, state.upcomingSession)
}
@Test
fun_화면에_재진입해도_처음_한번만_다가오는_세션을_조회한다() = runTest {
    // given
    fakeScheduleRepository.upcomingSessionInfo = ScheduleTestData.upcomingSessionInfo().copy(
        sessionId = "12345"
    )

    // when
    viewModel.store.onIntent(HomeIntent.EnterHomeScreen)
    fakeScheduleRepository.upcomingSessionInfo = ScheduleTestData.upcomingSessionInfo().copy(
        sessionId = "2345"
    )
    viewModel.store.onIntent(HomeIntent.EnterHomeScreen)

    // then
    val state = viewModel.store.uiState.value
    assertEquals(state.upcomingSessionInfo.sessionId, "12345")
}
@Test
fun 기본_규칙_링크를_최초로_불러오면_캐싱된다() = runTest {
    // given
    fakeOperationRepository.basicRuleLink = "https://www.yapp.co.kr"

    // when
    viewModel.store.onIntent(HomeIntent.ClickBasicRuleLink)

    // then
    val state = viewModel.store.uiState.value
    assertEquals(fakeOperationRepository.cachedBasicRuleLink, state.basicRuleLink)
}

이런 식을 말씀하시는 걸까요??

Copy link
Member Author

Choose a reason for hiding this comment

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

캐싱이 올바르게 되는지는 Repository 구현체 테스트에 더 알맞은 것 같아서
아예, ViewModel 쪽에서 검증을 안하는 방법도 있을 것 같긴 합니다.

호출 횟수와 같은 행위에 가까운 로직보다 ViewModel의 State가 올바르게 변경되는지만 ViewModel 테스트에서 판단하면요

Copy link
Member

Choose a reason for hiding this comment

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

넵 댓글에 써주신 코드 스니펫이 제가 생각한 방법이에요

캐싱 관련 로직은 Repository 테스트 단에서 하는게 더 맞을거같아요!

Copy link
Member Author

Choose a reason for hiding this comment

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

넵! 캐싱로직은 Repository 테스트에서 작성하는 걸로 할게요.
property 방식 써도 copy()는 사용 가능하서 테스트 데이터 변수로 바꿨습니다

Copy link
Member

@jinukeu jinukeu left a comment

Choose a reason for hiding this comment

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

테스트 코드를 행위 기반으로 작성할지, 상태 기반으로 작성할지 한번 논의해보면 좋을거같아요

@jinukeu
Copy link
Member

jinukeu commented Nov 29, 2025

혹시 AGP 버전도 내려주실 수 있을까요 .. ㅎ

@DongChyeon DongChyeon requested a review from jinukeu November 29, 2025 11:59
Copy link
Member

@jinukeu jinukeu left a comment

Choose a reason for hiding this comment

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

LGTM 입니다!

@DongChyeon DongChyeon merged commit 615a35e into release/1.2.0 Nov 29, 2025
4 checks passed
@DongChyeon DongChyeon deleted the test/#237-home-viewmodel branch November 29, 2025 14:34
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

3 participants