From d71b839c6ff607f7f851c629b7215d34cf4905cd Mon Sep 17 00:00:00 2001 From: MoonsuKang Date: Tue, 24 Jun 2025 21:13:59 +0900 Subject: [PATCH 01/10] =?UTF-8?q?[ADD/#224]=20dependency=20=EC=88=98?= =?UTF-8?q?=EC=A0=95=20=EB=B0=8F=20=EC=B6=94=EA=B0=80:=20junit4,=20mockk,?= =?UTF-8?q?=20jacoco?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- gradle/libs.versions.toml | 7 +++++-- 1 file changed, 5 insertions(+), 2 deletions(-) diff --git a/gradle/libs.versions.toml b/gradle/libs.versions.toml index 51c70bb2..1c8ef8ab 100644 --- a/gradle/libs.versions.toml +++ b/gradle/libs.versions.toml @@ -64,12 +64,14 @@ firebase-app-distribution = "5.1.0" firebase-crashlytics = "3.0.3" ## Test -junit = "4.13.2" +junit4 = "4.13.2" mockito = "3.3.3" +mockk = "1.13.9" robolectric = "4.9" androidx-test-ext-junit = "1.2.0" androidx-test-runner = "1.6.0" androidx-test = "1.6.0" +jacoco = "0.8.10" ## Others timber = "5.0.1" @@ -163,8 +165,9 @@ timber = { group = "com.jakewharton.timber", name = "timber", version.ref = "tim coil = { group = "io.coil-kt", name = "coil", version.ref = "coil" } ## Test Libraries -junit = { group = "junit", name = "junit", version.ref = "junit" } +junit4 = { group = "junit", name = "junit", version.ref = "junit4" } mockito = { group = "org.mockito", name = "mockito-core", version.ref = "mockito" } +mockk = { group = "io.mockk", name = "mockk", version.ref = "mockk" } robolectric = { group = "org.robolectric", name = "robolectric", version.ref = "robolectric" } androidx-test-ext-junit = { group = "androidx.test.ext", name = "junit", version.ref = "androidx-test-ext-junit" } androidx-test-runner = { group = "androidx.test", name = "runner", version.ref = "androidx-test-runner" } From 2394183c7ce7bf9006562a348ad34e08258afe5d Mon Sep 17 00:00:00 2001 From: MoonsuKang Date: Tue, 24 Jun 2025 21:15:05 +0900 Subject: [PATCH 02/10] =?UTF-8?q?[ADD/#224]=20feature=20=EB=AA=A8=EB=93=88?= =?UTF-8?q?=EC=97=90=EB=A7=8C=20Compose=20UI=20=ED=85=8C=EC=8A=A4=ED=8A=B8?= =?UTF-8?q?=20=EC=9D=98=EC=A1=B4=EC=84=B1=20=EC=84=A4=EC=A0=95=20=EC=A0=81?= =?UTF-8?q?=EC=9A=A9?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../java/com/yapp/convention/TestAndroid.kt | 24 +++++++++++++------ 1 file changed, 17 insertions(+), 7 deletions(-) diff --git a/build-logic/src/main/java/com/yapp/convention/TestAndroid.kt b/build-logic/src/main/java/com/yapp/convention/TestAndroid.kt index 9fe992f6..55bb6d48 100644 --- a/build-logic/src/main/java/com/yapp/convention/TestAndroid.kt +++ b/build-logic/src/main/java/com/yapp/convention/TestAndroid.kt @@ -1,16 +1,26 @@ package com.yapp.convention import org.gradle.api.Project +import org.gradle.kotlin.dsl.dependencies + internal fun Project.configureTestAndroid() { - configureJUnitAndroid() + // feature 모듈에만 테스트 관련 설정 적용 + if (path.startsWith(":feature:")) { + configureComposeUiTest() + } } -@Suppress("UnstableApiUsage") -internal fun Project.configureJUnitAndroid() { - androidExtension.apply { - testOptions { - unitTests.all { it.useJUnitPlatform() } - } +internal fun Project.configureComposeUiTest() { + val libs = extensions.libs + dependencies { + // Jetpack Compose UI 테스트용 + "androidTestImplementation"(libs.findLibrary("compose-ui-test-junit4").get()) + // 테스트용 AndroidManifest 제공해주는 거 (debug 빌드에서만 사용, 테스트 시 Activity 실행 지원) + "debugImplementation"(libs.findLibrary("compose-ui-test-manifest").get()) + // 테스트를 실제로 돌려주는 실행기 + "androidTestImplementation"(libs.findLibrary("androidx-test-runner").get()) + // JUnit4 기능을 안드로이드 테스트에 연결해주는 어댑터 + "androidTestImplementation"(libs.findLibrary("androidx-test-ext-junit").get()) } } From f9b625c595c54eed52ec9ec5b479eb46d581028c Mon Sep 17 00:00:00 2001 From: MoonsuKang Date: Tue, 24 Jun 2025 21:15:38 +0900 Subject: [PATCH 03/10] =?UTF-8?q?[FIX/#224]=20Junit4=20=EC=9D=98=EC=A1=B4?= =?UTF-8?q?=EC=84=B1=EB=A7=8C=20=EC=84=A4=EC=A0=95?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../main/java/com/yapp/convention/TestKotlin.kt | 17 +++++------------ 1 file changed, 5 insertions(+), 12 deletions(-) diff --git a/build-logic/src/main/java/com/yapp/convention/TestKotlin.kt b/build-logic/src/main/java/com/yapp/convention/TestKotlin.kt index 3b7d98c7..790833c0 100644 --- a/build-logic/src/main/java/com/yapp/convention/TestKotlin.kt +++ b/build-logic/src/main/java/com/yapp/convention/TestKotlin.kt @@ -1,23 +1,16 @@ package com.yapp.convention import org.gradle.api.Project -import org.gradle.api.tasks.testing.Test import org.gradle.kotlin.dsl.dependencies -import org.gradle.kotlin.dsl.withType -internal fun Project.configureTest() { - configureJUnit() +internal fun Project.configureTestKotlin() { val libs = extensions.libs dependencies { + // JUnit4 단위 테스트 프레임워크 "testImplementation"(libs.findLibrary("junit4").get()) - "testImplementation"(libs.findLibrary("junit-jupiter").get()) - "testImplementation"(libs.findLibrary("coroutines-test").get()) + // 코루틴 관련 테스트 도구 (TestCoroutineScope, runTest 등..) + "testImplementation"(libs.findLibrary("kotlinx-coroutines-test").get()) + // Kotlin 기반 mock 객체 생성, 행위 검증 "testImplementation"(libs.findLibrary("mockk").get()) } } - -internal fun Project.configureJUnit() { - tasks.withType().configureEach { - useJUnitPlatform() - } -} From 66960b22aeda9cf463a6e6068d805537f6a926c4 Mon Sep 17 00:00:00 2001 From: MoonsuKang Date: Tue, 24 Jun 2025 21:16:18 +0900 Subject: [PATCH 04/10] =?UTF-8?q?[FIX/#224]=20Jacoco=20=EA=B8=B0=EB=B0=98?= =?UTF-8?q?=20=EC=BB=A4=EB=B2=84=EB=A6=AC=EC=A7=80=20=EC=B8=A1=EC=A0=95=20?= =?UTF-8?q?=EC=84=A4=EC=A0=95=20=EB=B0=8F=20=EB=A6=AC=ED=8F=AC=ED=8A=B8=20?= =?UTF-8?q?=EC=83=9D=EC=84=B1=20Task=20=EC=B6=94=EA=B0=80?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit - 모든 유닛 테스트에 대해 Jacoco 설정 자동 적용 - Android 모듈(Application, Library)에 유닛 테스트 커버리지 활성화 - 커버리지 리포트를 생성하는 generateTestCoverageReport Task 등록 - .exec 파일 미존재 시 createDebugUnitTestCoverageReport Task 자동 스킵 --- .../java/com/yapp/convention/TestCoverage.kt | 55 +++++++++++++++++++ 1 file changed, 55 insertions(+) create mode 100644 build-logic/src/main/java/com/yapp/convention/TestCoverage.kt diff --git a/build-logic/src/main/java/com/yapp/convention/TestCoverage.kt b/build-logic/src/main/java/com/yapp/convention/TestCoverage.kt new file mode 100644 index 00000000..fd54ed18 --- /dev/null +++ b/build-logic/src/main/java/com/yapp/convention/TestCoverage.kt @@ -0,0 +1,55 @@ +package com.yapp.convention + +import com.android.build.api.dsl.ApplicationExtension +import com.android.build.api.dsl.LibraryExtension +import org.gradle.api.Project +import org.gradle.api.tasks.testing.Test +import org.gradle.kotlin.dsl.configure +import org.gradle.kotlin.dsl.withType +import org.gradle.testing.jacoco.plugins.JacocoPluginExtension +import org.gradle.testing.jacoco.plugins.JacocoTaskExtension + +internal fun Project.configureTestCoverage() { + pluginManager.apply("jacoco") + + val libs = extensions.libs + extensions.configure { + toolVersion = libs.findVersion("jacoco").get().toString() + } + + // 모든 유닛 테스트에 Jacoco 설정 적용 + tasks.withType().configureEach { + extensions.configure { + isIncludeNoLocationClasses = true + excludes = listOf("jdk.internal.*") + } + } + + // Android 모듈이면 커버리지 설정 추가 + extensions.findByType(ApplicationExtension::class.java)?.buildTypes?.configureEach { + enableUnitTestCoverage = true + } + + extensions.findByType(LibraryExtension::class.java)?.buildTypes?.configureEach { + enableUnitTestCoverage = true + } + + // 커버리지 리포트 Task 등록 + tasks.register("generateTestCoverageReport") { + group = "verification" + description = "Run unit tests and generate coverage report." + + dependsOn("testDebugUnitTest") + dependsOn("createDebugUnitTestCoverageReport") + } + + // .exec 파일 없을 경우 createDebugUnitTestCoverageReport task 스킵 + tasks.matching { it.name == "createDebugUnitTestCoverageReport" }.configureEach { + onlyIf { + val execFile = layout.buildDirectory + .file("outputs/unit_test_code_coverage/debugUnitTest/testDebugUnitTest.exec") + .get().asFile + execFile.exists() + } + } +} From dc9cff15aa648a325680fa66ce1575d80ff1a97c Mon Sep 17 00:00:00 2001 From: MoonsuKang Date: Tue, 24 Jun 2025 21:16:51 +0900 Subject: [PATCH 05/10] =?UTF-8?q?[ADD/#224]=20=ED=85=8C=EC=8A=A4=ED=8A=B8?= =?UTF-8?q?=20=EB=B0=8F=20=EC=BB=A4=EB=B2=84=EB=A6=AC=EC=A7=80=20=EC=84=A4?= =?UTF-8?q?=EC=A0=95=20=ED=95=A8=EC=88=98=20=EB=93=B1=EB=A1=9D?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- build-logic/src/main/java/orbit.android.library.gradle.kts | 6 ++++++ 1 file changed, 6 insertions(+) diff --git a/build-logic/src/main/java/orbit.android.library.gradle.kts b/build-logic/src/main/java/orbit.android.library.gradle.kts index 3ee18d12..f63f2be4 100644 --- a/build-logic/src/main/java/orbit.android.library.gradle.kts +++ b/build-logic/src/main/java/orbit.android.library.gradle.kts @@ -1,6 +1,9 @@ import com.yapp.convention.configureCoroutine import com.yapp.convention.configureHiltAndroid import com.yapp.convention.configureKotlinAndroid +import com.yapp.convention.configureTestAndroid +import com.yapp.convention.configureTestCoverage +import com.yapp.convention.configureTestKotlin plugins { id("com.android.library") @@ -9,3 +12,6 @@ plugins { configureKotlinAndroid() configureCoroutine() configureHiltAndroid() +configureTestAndroid() +configureTestKotlin() +configureTestCoverage() From f839f43b27c3d115286b271c169d9d7ab264ccb8 Mon Sep 17 00:00:00 2001 From: MoonsuKang Date: Tue, 24 Jun 2025 21:17:18 +0900 Subject: [PATCH 06/10] =?UTF-8?q?[ADD/#224]=20=EC=9C=A0=EB=8B=9B=20?= =?UTF-8?q?=ED=85=8C=EC=8A=A4=ED=8A=B8=20=EB=B0=8F=20=EC=BB=A4=EB=B2=84?= =?UTF-8?q?=EB=A6=AC=EC=A7=80=20=EB=A6=AC=ED=8F=AC=ED=8A=B8=20=EC=83=9D?= =?UTF-8?q?=EC=84=B1=20=EC=B6=94=EA=B0=80?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .github/workflows/android_ci.yml | 26 ++++++++++++++++++++++++++ 1 file changed, 26 insertions(+) diff --git a/.github/workflows/android_ci.yml b/.github/workflows/android_ci.yml index b8974a0d..431ff74a 100644 --- a/.github/workflows/android_ci.yml +++ b/.github/workflows/android_ci.yml @@ -80,3 +80,29 @@ jobs: # Run Lint and Build - name: Run lint and build run: ./gradlew ktlintCheck assembleDebug + + # Run Lint and Build + - name: Run lint and build + run: ./gradlew ktlintCheck assembleDebug + + # Run Unit Test and Generate Coverage + - name: Run unit tests and generate coverage + run: ./gradlew generateTestCoverageReport + + # Comment PR with coverage result + - name: Comment coverage report in PR + if: github.event_name == 'pull_request' + env: + GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }} + run: | + COVERAGE_PATH="data/build/reports/coverage/test/debug/index.html" + if [ -f "$COVERAGE_PATH" ]; then + COMMENT="🚀 테스트 완료 및 커버리지 리포트가 생성되었습니다.\n\n➡️ [클릭하여 커버리지 리포트 보기](https://github.com/${{ github.repository }}/pull/${{ github.event.pull_request.number }}#issuecomment)\n\n리포트 위치: \`${COVERAGE_PATH}\`" + else + COMMENT="️ 테스트는 완료 됐는데 커버리지 리포트 파일이 존재하지 않습니다. 빌드 설정을 확인해주세요." + fi + + curl -s -H "Authorization: token $GITHUB_TOKEN" \ + -X POST \ + -d "{\"body\": \"$COMMENT\"}" \ + "https://api.github.com/repos/${{ github.repository }}/issues/${{ github.event.pull_request.number }}/comments" From 1a588b629f0e5ac78b756b794cddf927538d804b Mon Sep 17 00:00:00 2001 From: MoonsuKang Date: Tue, 24 Jun 2025 21:17:36 +0900 Subject: [PATCH 07/10] =?UTF-8?q?[ADD/#224]=20Data=20Layer=20=EC=83=98?= =?UTF-8?q?=ED=94=8C=20=ED=85=8C=EC=8A=A4=ED=8A=B8=20=EC=BD=94=EB=93=9C=20?= =?UTF-8?q?=EC=B6=94=EA=B0=80?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../yapp/data/FortuneDataSourceImplTest.kt | 83 +++++++++++++++++++ .../kotlin/com/yapp/data/FortuneMapperTest.kt | 57 +++++++++++++ .../yapp/data/FortuneRepositoryImplTest.kt | 69 +++++++++++++++ 3 files changed, 209 insertions(+) create mode 100644 data/src/test/kotlin/com/yapp/data/FortuneDataSourceImplTest.kt create mode 100644 data/src/test/kotlin/com/yapp/data/FortuneMapperTest.kt create mode 100644 data/src/test/kotlin/com/yapp/data/FortuneRepositoryImplTest.kt diff --git a/data/src/test/kotlin/com/yapp/data/FortuneDataSourceImplTest.kt b/data/src/test/kotlin/com/yapp/data/FortuneDataSourceImplTest.kt new file mode 100644 index 00000000..b7d9d0f4 --- /dev/null +++ b/data/src/test/kotlin/com/yapp/data/FortuneDataSourceImplTest.kt @@ -0,0 +1,83 @@ +package com.yapp.data + +import com.yapp.data.remote.datasource.FortuneDataSourceImpl +import com.yapp.data.remote.dto.response.FortuneResponse +import com.yapp.data.remote.service.ApiService +import io.mockk.coEvery +import io.mockk.coVerify +import io.mockk.mockk +import kotlinx.coroutines.test.runTest +import org.junit.Assert.* +import org.junit.Before +import org.junit.Test + +class FortuneDataSourceImplTest { + + private lateinit var dataSource: FortuneDataSourceImpl + private val apiService: ApiService = mockk() + + @Before + fun setup() { + dataSource = FortuneDataSourceImpl(apiService) + } + + @Test + fun `운세 등록에 성공하면 성공 Result를 반환한다`() = runTest { + // Given + val userId = 1L + val mockResponse = mockk() + coEvery { apiService.postFortune(userId) } returns mockResponse + + // When + val result = dataSource.postFortune(userId) + + // Then + assertTrue(result.isSuccess) + assertEquals(mockResponse, result.getOrNull()) + coVerify { apiService.postFortune(userId) } + } + + @Test + fun `운세 등록 중 예외가 발생하면 실패 Result를 반환한다`() = runTest { + // Given + val userId = 1L + coEvery { apiService.postFortune(userId) } throws RuntimeException("Network Error") + + // When + val result = dataSource.postFortune(userId) + + // Then + assertTrue(result.isFailure) + coVerify { apiService.postFortune(userId) } + } + + @Test + fun `운세 조회에 성공하면 성공 Result를 반환한다`() = runTest { + // Given + val fortuneId = 10L + val mockResponse = mockk() + coEvery { apiService.getFortune(fortuneId) } returns mockResponse + + // When + val result = dataSource.getFortune(fortuneId) + + // Then + assertTrue(result.isSuccess) + assertEquals(mockResponse, result.getOrNull()) + coVerify { apiService.getFortune(fortuneId) } + } + + @Test + fun `운세 조회 중 예외가 발생하면 실패 Result를 반환한다`() = runTest { + // Given + val fortuneId = 10L + coEvery { apiService.getFortune(fortuneId) } throws RuntimeException("Network Error") + + // When + val result = dataSource.getFortune(fortuneId) + + // Then + assertTrue(result.isFailure) + coVerify { apiService.getFortune(fortuneId) } + } +} diff --git a/data/src/test/kotlin/com/yapp/data/FortuneMapperTest.kt b/data/src/test/kotlin/com/yapp/data/FortuneMapperTest.kt new file mode 100644 index 00000000..6c3313b7 --- /dev/null +++ b/data/src/test/kotlin/com/yapp/data/FortuneMapperTest.kt @@ -0,0 +1,57 @@ +package com.yapp.data + +import com.yapp.data.remote.dto.response.FortuneDetail +import com.yapp.data.remote.dto.response.FortuneResponse +import com.yapp.data.remote.dto.response.toDomain +import org.junit.Assert.assertEquals +import org.junit.Test + +class FortuneMapperTest { + + @Test + fun `FortuneResponse를 도메인 모델로 매핑하면 올바르게 변환된다`() { + val response = dummyFortuneResponse() + val domain = response.toDomain() + + assertEquals(response.id, domain.id) + assertEquals(response.dailyFortune, domain.dailyFortuneTitle) + assertEquals(response.dailyFortuneDescription, domain.dailyFortuneDescription) + assertEquals(response.avgFortuneScore, domain.avgFortuneScore) + assertEquals(response.studyCareerFortune.toDomain(), domain.studyCareerFortune) + assertEquals(response.luckyFood, domain.luckyFood) + } + + @Test + fun `FortuneDetail을 도메인 모델로 매핑하면 올바르게 변환된다`() { + val detail = FortuneDetail(score = 85, title = "Success", description = "Great things happen") + val domain = detail.toDomain() + + assertEquals(85, domain.score) + assertEquals("Success", domain.title) + assertEquals("Great things happen", domain.description) + } + + private fun dummyFortuneResponse() = FortuneResponse( + id = 123, + dailyFortune = "Today is your lucky day", + dailyFortuneDescription = "You'll find success in your endeavors.", + avgFortuneScore = 88, + studyCareerFortune = dummyDetail(), + wealthFortune = dummyDetail(), + healthFortune = dummyDetail(), + loveFortune = dummyDetail(), + luckyOutfitTop = "T-shirt", + luckyOutfitBottom = "Shorts", + luckyOutfitShoes = "Sneakers", + luckyOutfitAccessory = "Bracelet", + unluckyColor = "Gray", + luckyColor = "Yellow", + luckyFood = "Sushi" + ) + + private fun dummyDetail() = FortuneDetail( + score = 90, + title = "High Energy", + description = "You will feel energetic all day." + ) +} diff --git a/data/src/test/kotlin/com/yapp/data/FortuneRepositoryImplTest.kt b/data/src/test/kotlin/com/yapp/data/FortuneRepositoryImplTest.kt new file mode 100644 index 00000000..7abaf7b3 --- /dev/null +++ b/data/src/test/kotlin/com/yapp/data/FortuneRepositoryImplTest.kt @@ -0,0 +1,69 @@ +package com.yapp.data + +import com.yapp.data.local.datasource.FortuneLocalDataSource +import com.yapp.data.remote.datasource.FortuneDataSource +import com.yapp.data.remote.dto.response.FortuneDetail +import com.yapp.data.remote.dto.response.FortuneResponse +import com.yapp.data.remote.dto.response.toDomain +import com.yapp.data.repositoryimpl.FortuneRepositoryImpl +import io.mockk.coEvery +import io.mockk.mockk +import kotlinx.coroutines.test.runTest +import org.junit.Assert.assertEquals +import org.junit.Test + +class FortuneRepositoryImplTest { + + private val remoteDataSource = mockk() + private val localDataSource = mockk(relaxed = true) + + private val repository = FortuneRepositoryImpl( + fortuneRemoteDataSource = remoteDataSource, + fortuneLocalDataSource = localDataSource, + ) + + @Test + fun `운세 요청에 성공하면 도메인 모델로 반환된다`() = runTest { + val response = dummyFortuneResponse() + coEvery { remoteDataSource.postFortune(1L) } returns Result.success(response) + + val result = repository.postFortune(1L) + + assert(result.isSuccess) + assertEquals(response.toDomain(), result.getOrNull()) + } + + @Test + fun `운세 상세 조회에 실패하면 실패 결과를 반환한다`() = runTest { + val exception = RuntimeException("Not found") + coEvery { remoteDataSource.getFortune(2L) } returns Result.failure(exception) + + val result = repository.getFortune(2L) + + assert(result.isFailure) + } + + private fun dummyFortuneResponse() = FortuneResponse( + id = 1L, + dailyFortune = "Good luck", + dailyFortuneDescription = "You will be lucky today", + avgFortuneScore = 90, + studyCareerFortune = dummyDetail(), + wealthFortune = dummyDetail(), + healthFortune = dummyDetail(), + loveFortune = dummyDetail(), + luckyOutfitTop = "Hoodie", + luckyOutfitBottom = "Jeans", + luckyOutfitShoes = "Sneakers", + luckyOutfitAccessory = "Watch", + unluckyColor = "Black", + luckyColor = "White", + luckyFood = "Pizza", + ) + + private fun dummyDetail() = FortuneDetail( + score = 100, + title = "Title", + description = "Description" + ) +} From 31d921f84df9621bef11d08fe454a18a7a7b034c Mon Sep 17 00:00:00 2001 From: MoonsuKang Date: Tue, 24 Jun 2025 21:17:50 +0900 Subject: [PATCH 08/10] =?UTF-8?q?[ADD/#224]=20kotlin=20refelect=20?= =?UTF-8?q?=EC=9D=98=EC=A1=B4=EC=84=B1=20=EC=B6=94=EA=B0=80?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- app/build.gradle.kts | 1 + 1 file changed, 1 insertion(+) diff --git a/app/build.gradle.kts b/app/build.gradle.kts index 19b02048..b4897244 100644 --- a/app/build.gradle.kts +++ b/app/build.gradle.kts @@ -50,4 +50,5 @@ dependencies { implementation(libs.firebase.analytics) implementation(libs.firebase.crashlytics) implementation(libs.play.services.ads) + implementation(libs.kotlin.reflect) } From a10459f8b68fd18f605c3ce9123526c2773f4c74 Mon Sep 17 00:00:00 2001 From: MoonsuKang Date: Tue, 24 Jun 2025 21:35:22 +0900 Subject: [PATCH 09/10] =?UTF-8?q?[REFACTOR/#224]=20=EC=A4=91=EB=B3=B5=20?= =?UTF-8?q?=EC=BD=94=EB=93=9C=20=EC=A0=9C=EA=B1=B0=20=EB=B0=8F=20=EC=BD=94?= =?UTF-8?q?=EB=93=9C=20=EC=A0=95=EB=A6=AC?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .github/workflows/android_ci.yml | 42 +++++++++++++++----------------- 1 file changed, 19 insertions(+), 23 deletions(-) diff --git a/.github/workflows/android_ci.yml b/.github/workflows/android_ci.yml index 431ff74a..1d526485 100644 --- a/.github/workflows/android_ci.yml +++ b/.github/workflows/android_ci.yml @@ -81,28 +81,24 @@ jobs: - name: Run lint and build run: ./gradlew ktlintCheck assembleDebug - # Run Lint and Build - - name: Run lint and build - run: ./gradlew ktlintCheck assembleDebug + # Run Unit Test and Generate Coverage + - name: Run unit tests and generate coverage + run: ./gradlew generateTestCoverageReport - # Run Unit Test and Generate Coverage - - name: Run unit tests and generate coverage - run: ./gradlew generateTestCoverageReport - - # Comment PR with coverage result - - name: Comment coverage report in PR - if: github.event_name == 'pull_request' - env: - GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }} - run: | - COVERAGE_PATH="data/build/reports/coverage/test/debug/index.html" - if [ -f "$COVERAGE_PATH" ]; then - COMMENT="🚀 테스트 완료 및 커버리지 리포트가 생성되었습니다.\n\n➡️ [클릭하여 커버리지 리포트 보기](https://github.com/${{ github.repository }}/pull/${{ github.event.pull_request.number }}#issuecomment)\n\n리포트 위치: \`${COVERAGE_PATH}\`" - else - COMMENT="️ 테스트는 완료 됐는데 커버리지 리포트 파일이 존재하지 않습니다. 빌드 설정을 확인해주세요." - fi + # Comment PR with coverage result + - name: Comment coverage report in PR + if: github.event_name == 'pull_request' + env: + GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }} + run: | + COVERAGE_PATH="data/build/reports/coverage/test/debug/index.html" + if [ -f "$COVERAGE_PATH" ]; then + COMMENT="🚀 테스트 완료 및 커버리지 리포트가 생성되었습니다.\n\n➡️ [클릭하여 커버리지 리포트 보기](https://github.com/${{ github.repository }}/pull/${{ github.event.pull_request.number }}#issuecomment)\n\n리포트 위치: \`${COVERAGE_PATH}\`" + else + COMMENT="⚠️ 테스트는 완료 됐는데 커버리지 리포트 파일이 존재하지 않습니다. 빌드 설정을 확인해주세요." + fi - curl -s -H "Authorization: token $GITHUB_TOKEN" \ - -X POST \ - -d "{\"body\": \"$COMMENT\"}" \ - "https://api.github.com/repos/${{ github.repository }}/issues/${{ github.event.pull_request.number }}/comments" + curl -s -H "Authorization: token $GITHUB_TOKEN" \ + -X POST \ + -d "{\"body\": \"$COMMENT\"}" \ + "https://api.github.com/repos/${{ github.repository }}/issues/${{ github.event.pull_request.number }}/comments" From a256232529b612aaf336c8c9aa0e040b78f8c815 Mon Sep 17 00:00:00 2001 From: MoonsuKang Date: Tue, 24 Jun 2025 21:47:11 +0900 Subject: [PATCH 10/10] =?UTF-8?q?[REFACTOR/#224]=20=EC=BB=A4=EB=B2=84?= =?UTF-8?q?=EB=A6=AC=EC=A7=80=20=EB=A6=AC=ED=8F=AC=ED=8A=B8=20=EC=97=85?= =?UTF-8?q?=EB=A1=9C=EB=93=9C=20=ED=98=95=EC=8B=9D=EC=9C=BC=EB=A1=9C=20?= =?UTF-8?q?=EC=88=98=EC=A0=95?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .github/workflows/android_ci.yml | 15 ++++++++------- 1 file changed, 8 insertions(+), 7 deletions(-) diff --git a/.github/workflows/android_ci.yml b/.github/workflows/android_ci.yml index 1d526485..d8ca94cd 100644 --- a/.github/workflows/android_ci.yml +++ b/.github/workflows/android_ci.yml @@ -85,19 +85,20 @@ jobs: - name: Run unit tests and generate coverage run: ./gradlew generateTestCoverageReport + # Upload Coverage Report + - name: Upload coverage report + uses: actions/upload-artifact@v4 + with: + name: coverage-report + path: data/build/reports/coverage/test/debug/ + # Comment PR with coverage result - name: Comment coverage report in PR if: github.event_name == 'pull_request' env: GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }} run: | - COVERAGE_PATH="data/build/reports/coverage/test/debug/index.html" - if [ -f "$COVERAGE_PATH" ]; then - COMMENT="🚀 테스트 완료 및 커버리지 리포트가 생성되었습니다.\n\n➡️ [클릭하여 커버리지 리포트 보기](https://github.com/${{ github.repository }}/pull/${{ github.event.pull_request.number }}#issuecomment)\n\n리포트 위치: \`${COVERAGE_PATH}\`" - else - COMMENT="⚠️ 테스트는 완료 됐는데 커버리지 리포트 파일이 존재하지 않습니다. 빌드 설정을 확인해주세요." - fi - + COMMENT="🚀 테스트 완료 및 커버리지 리포트가 생성되었습니다.\n\n➡️ [클릭하여 커버리지 리포트 다운로드](https://github.com/${{ github.repository }}/actions/runs/${{ github.run_id }})" curl -s -H "Authorization: token $GITHUB_TOKEN" \ -X POST \ -d "{\"body\": \"$COMMENT\"}" \