Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
8 changes: 6 additions & 2 deletions .github/workflows/unit-test.yml
Original file line number Diff line number Diff line change
@@ -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:
Expand Down Expand Up @@ -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 }}
Expand All @@ -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
2 changes: 2 additions & 0 deletions gradle/libs.versions.toml
Original file line number Diff line number Diff line change
Expand Up @@ -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" }
Expand Down Expand Up @@ -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" }
Expand Down
17 changes: 17 additions & 0 deletions konsist/build.gradle.kts
Original file line number Diff line number Diff line change
@@ -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)
}
Original file line number Diff line number Diff line change
@@ -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(\..*)?$""")
}
}
3 changes: 3 additions & 0 deletions settings.gradle.kts
Original file line number Diff line number Diff line change
Expand Up @@ -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")
Loading