From ad5af2671334243611c7a3b9ee9b70573755a4d4 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=EC=9D=BC=ED=98=81?= Date: Wed, 3 Jun 2026 15:18:11 +0900 Subject: [PATCH] =?UTF-8?q?test:=20Konsist=20=EB=8F=84=EC=9E=85=20?= =?UTF-8?q?=EB=B0=8F=20=EB=A0=88=EC=9D=B4=EC=96=B4=20=EC=9D=98=EC=A1=B4?= =?UTF-8?q?=EC=84=B1=20=EA=B7=9C=EC=B9=99=20=EA=B2=80=EC=A6=9D=20=ED=85=8C?= =?UTF-8?q?=EC=8A=A4=ED=8A=B8=20=EC=B6=94=EA=B0=80?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit - 아키텍처 가드 라이브러리 `Konsist` 도입 및 검증 전용 `:konsist` 모듈 추가 - `LayerDependencyKonsistTest` 구현: domain 레이어가 data/presentation 레이어 및 `core:model` 이외의 코어 모듈(network, ui 등)을 참조하지 않도록 의존성 방향 제한 - GitHub Actions 워크플로우(`unit-test.yml`)에 `:konsist:test` 태스크를 추가하여 CI 단계에서 아키텍처 규칙 자동 검증 - 테스트 결과 리포트 보관 경로에 `:konsist` 모듈의 테스트 결과 추가 - `settings.gradle.kts` 및 `libs.versions.toml`에 신규 모듈 및 라이브러리 정의 반영 --- .github/workflows/unit-test.yml | 8 ++- gradle/libs.versions.toml | 2 + konsist/build.gradle.kts | 17 +++++++ .../konsist/LayerDependencyKonsistTest.kt | 50 +++++++++++++++++++ settings.gradle.kts | 3 ++ 5 files changed, 78 insertions(+), 2 deletions(-) create mode 100644 konsist/build.gradle.kts create mode 100644 konsist/src/test/kotlin/com/afternote/konsist/LayerDependencyKonsistTest.kt diff --git a/.github/workflows/unit-test.yml b/.github/workflows/unit-test.yml index 5f612450..878a36e7 100644 --- a/.github/workflows/unit-test.yml +++ b/.github/workflows/unit-test.yml @@ -1,6 +1,8 @@ name: Unit Test -# PR 대상 main/develop 일 때 모든 모듈의 JVM unit test (./gradlew testDebugUnitTest) 실행. +# PR 대상 main/develop 일 때 JVM unit test 실행: +# - ./gradlew testDebugUnitTest : Android 모듈 debug unit test +# - :konsist:test : 순수 JVM 모듈(testDebugUnitTest 미보유)이라 별도 명시. 레이어 의존 아키텍처 가드. # lint.yml 과 setup 이 거의 동일하지만 분리 — lint 실패가 test 결과를 가리지 않도록 fail-fast 독립. on: @@ -51,7 +53,7 @@ jobs: echo "$GOOGLE_SERVICES_JSON_B64" | base64 -d > app/google-services.json - name: Run unit tests - run: ./gradlew testDebugUnitTest --continue --parallel + run: ./gradlew testDebugUnitTest :konsist:test --continue --parallel env: KAKAO_NATIVE_APP_KEY: ${{ secrets.KAKAO_NATIVE_APP_KEY }} GOOGLE_WEB_CLIENT_ID: ${{ secrets.GOOGLE_WEB_CLIENT_ID }} @@ -65,4 +67,6 @@ jobs: path: | **/build/reports/tests/testDebugUnitTest **/build/test-results/testDebugUnitTest + **/build/reports/tests/test + **/build/test-results/test retention-days: 7 diff --git a/gradle/libs.versions.toml b/gradle/libs.versions.toml index 4d231a7c..3a4b2eee 100644 --- a/gradle/libs.versions.toml +++ b/gradle/libs.versions.toml @@ -45,6 +45,7 @@ paging = "3.5.0" screenshot = "0.0.1-alpha15" googleServices = "4.4.4" firebaseAppDistribution = "5.2.1" +konsist = "0.17.3" [libraries] androidx-biometric = { module = "androidx.biometric:biometric", version.ref = "biometric" } @@ -107,6 +108,7 @@ androidx-paging-common = { group = "androidx.paging", name = "paging-common", ve androidx-paging-runtime = { group = "androidx.paging", name = "paging-runtime", version.ref = "paging" } androidx-paging-compose = { group = "androidx.paging", name = "paging-compose", version.ref = "paging" } screenshot-validation-api = { group = "com.android.tools.screenshot", name = "screenshot-validation-api", version.ref = "screenshot" } +konsist = { group = "com.lemonappdev", name = "konsist", version.ref = "konsist" } [plugins] android-application = { id = "com.android.application", version.ref = "agp" } diff --git a/konsist/build.gradle.kts b/konsist/build.gradle.kts new file mode 100644 index 00000000..9db9f771 --- /dev/null +++ b/konsist/build.gradle.kts @@ -0,0 +1,17 @@ +plugins { + id("java-library") + alias(libs.plugins.jetbrains.kotlin.jvm) +} +java { + sourceCompatibility = JavaVersion.VERSION_11 + targetCompatibility = JavaVersion.VERSION_11 +} +kotlin { + compilerOptions { + jvmTarget = org.jetbrains.kotlin.gradle.dsl.JvmTarget.JVM_11 + } +} +dependencies { + testImplementation(libs.konsist) + testImplementation(libs.junit) +} diff --git a/konsist/src/test/kotlin/com/afternote/konsist/LayerDependencyKonsistTest.kt b/konsist/src/test/kotlin/com/afternote/konsist/LayerDependencyKonsistTest.kt new file mode 100644 index 00000000..0e2672f5 --- /dev/null +++ b/konsist/src/test/kotlin/com/afternote/konsist/LayerDependencyKonsistTest.kt @@ -0,0 +1,50 @@ +package com.afternote.konsist + +import com.lemonappdev.konsist.api.Konsist +import com.lemonappdev.konsist.api.ext.list.withPackage +import com.lemonappdev.konsist.api.verify.assertFalse +import org.junit.Test + +/** + * 레이어 의존 방향 회귀 가드. + * + * `presentation(UI) → domain ← data` — UI·data 가 domain 에 의존하고, domain 은 다른 레이어나 + * 비-`core:model` 코어에 **역의존하지 않는다**(의존 역전). Repository 추상을 domain 에 두는 구조를 보호한다. + * + * domain 패키지(`com.afternote..domain..`) 파일의 import 만 검사한다. + * - 허용: `core:model`(`com.afternote.core.model.*`), 같은 domain 레이어(`com.afternote..domain..`; cross-domain 포함) + * - 금지: data·presentation 레이어, 비-`core:model` 코어(network/ui/common/datastore/di/startup) + * + * 외부 빌드 그래프가 아니라 소스 import 를 스캔하므로, Gradle 사이클 탐지가 못 잡는 + * `domain → core:network` 같은 위반까지 막는다. + */ +class LayerDependencyKonsistTest { + private fun domainFiles() = Konsist.scopeFromProject().files.withPackage("com.afternote..domain..") + + @Test + fun `domain 은 data 와 presentation 레이어에 의존하지 않는다`() { + domainFiles().assertFalse { file -> + file.imports.any { import -> FORBIDDEN_LAYER_IMPORT.matches(import.name) } + } + } + + @Test + fun `domain 은 core_model 과 같은 domain 레이어 외 afternote 모듈에 의존하지 않는다`() { + domainFiles().assertFalse { file -> + file.imports.any { import -> + import.name.startsWith(AFTERNOTE_PREFIX) && !ALLOWED_INTERNAL_IMPORT.matches(import.name) + } + } + } + + private companion object { + const val AFTERNOTE_PREFIX = "com.afternote." + + /** data / presentation 레이어 패키지. */ + val FORBIDDEN_LAYER_IMPORT = Regex("""^com\.afternote\..*\.(data|presentation)\..*$""") + + /** core:model 또는 같은 domain 레이어만 허용 (그 외 com.afternote.* 는 위반). */ + val ALLOWED_INTERNAL_IMPORT = + Regex("""^com\.afternote\.core\.model\..*$|^com\.afternote\..*\.domain(\..*)?$""") + } +} diff --git a/settings.gradle.kts b/settings.gradle.kts index 675e201e..c6df5163 100644 --- a/settings.gradle.kts +++ b/settings.gradle.kts @@ -65,3 +65,6 @@ include(":feature:timeletter:data") include(":feature:timeletter:domain") include(":feature:timeletter:presentation") include(":feature:timeletter:res") + +// Architecture test module (Konsist) — 레이어 의존 방향 회귀 가드 +include(":konsist")