Skip to content

Commit 61371e2

Browse files
kirich1409claude
andcommitted
Add test coverage, static analysis (Detekt, ktlint), code coverage (Kover), and fix CI
- Add Detekt, ktlint, and Kover to convention plugin for all library modules - Add unit tests for vbpd-core (LazyViewBindingProperty, EagerViewBindingProperty) - Add lifecycle tests for vbpd (Activity, Fragment, ViewGroup, ViewHolder) - Add tests for vbpd-reflection (ViewBindingCache, CreateMethod) - Fix minor code style issues (double spaces, extra blank lines) - Update CI pipelines: remove continue-on-error, add test/lint/coverage steps - Add Detekt configuration file (config/detekt/detekt.yml) Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
1 parent 6ee5cba commit 61371e2

20 files changed

Lines changed: 545 additions & 39 deletions

File tree

‎.github/workflows/android.yml‎

Lines changed: 23 additions & 16 deletions
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,4 @@
1-
name: Android quality checks
1+
name: Quality Checks
22

33
on:
44
push:
@@ -8,12 +8,12 @@ on:
88

99
jobs:
1010
check:
11-
1211
runs-on: ubuntu-latest
1312

1413
steps:
1514
- uses: actions/checkout@v4
16-
- name: set up JDK
15+
16+
- name: Set up JDK
1717
uses: actions/setup-java@v4
1818
with:
1919
java-version: '21'
@@ -22,19 +22,26 @@ jobs:
2222

2323
- name: Grant execute permission for gradlew
2424
run: chmod +x gradlew
25-
26-
- name: Check build
25+
26+
- name: Run all checks
2727
run: ./gradlew check
28-
continue-on-error: true
29-
30-
- name: Save Gradle cache
31-
uses: actions/cache@v4
32-
continue-on-error: true
28+
29+
- name: Generate coverage report
30+
run: ./gradlew koverXmlReport
31+
32+
- name: Upload test results
33+
if: always()
34+
uses: actions/upload-artifact@v4
35+
with:
36+
name: test-results
37+
path: |
38+
**/build/test-results/
39+
**/build/reports/tests/
40+
41+
- name: Upload coverage reports
42+
if: always()
43+
uses: actions/upload-artifact@v4
3344
with:
45+
name: coverage-reports
3446
path: |
35-
~/.gradle/caches
36-
~/.gradle/wrapper
37-
key: ${{ runner.os }}-gradle-${{ hashFiles('**/*.gradle*', '**/gradle-wrapper.properties') }}
38-
restore-keys: |
39-
${{ runner.os }}-gradle-
40-
47+
**/build/reports/kover/

‎.github/workflows/build.yml‎

Lines changed: 32 additions & 19 deletions
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,4 @@
1-
name: Build libs
1+
name: Build & Test
22

33
on:
44
push:
@@ -7,12 +7,12 @@ on:
77

88
jobs:
99
check:
10-
1110
runs-on: ubuntu-latest
1211

1312
steps:
1413
- uses: actions/checkout@v4
15-
- name: set up JDK
14+
15+
- name: Set up JDK
1616
uses: actions/setup-java@v4
1717
with:
1818
java-version: '21'
@@ -21,28 +21,41 @@ jobs:
2121

2222
- name: Grant execute permission for gradlew
2323
run: chmod +x gradlew
24-
25-
- name: Check build
24+
25+
- name: Build libraries
2626
run: ./gradlew :vbpd-core:assembleRelease :vbpd:assembleRelease :vbpd-reflection:assembleRelease
27-
continue-on-error: true
28-
29-
- name: Save Gradle cache
30-
uses: actions/cache@v4
31-
continue-on-error: true
27+
28+
- name: Run tests
29+
run: ./gradlew testDebugUnitTest
30+
31+
- name: Run code quality checks
32+
run: ./gradlew detekt ktlintCheck
33+
34+
- name: Generate coverage report
35+
run: ./gradlew koverXmlReport
36+
37+
- name: Upload test results
38+
if: always()
39+
uses: actions/upload-artifact@v4
3240
with:
41+
name: test-results
3342
path: |
34-
~/.gradle/caches
35-
~/.gradle/wrapper
36-
key: ${{ runner.os }}-gradle-${{ hashFiles('**/*.gradle*', '**/gradle-wrapper.properties') }}
37-
restore-keys: |
38-
${{ runner.os }}-gradle-
43+
**/build/test-results/
44+
**/build/reports/tests/
3945
40-
- name: Save build artifacts
46+
- name: Upload coverage reports
47+
if: always()
48+
uses: actions/upload-artifact@v4
49+
with:
50+
name: coverage-reports
51+
path: |
52+
**/build/reports/kover/
53+
54+
- name: Upload build artifacts
4155
uses: actions/upload-artifact@v4
4256
with:
4357
name: vbpd-libs-builds
44-
path:
45-
vbpd/build/outputs/aar/*.aar
58+
path: |
4659
vbpd-core/build/outputs/aar/*.aar
60+
vbpd/build/outputs/aar/*.aar
4761
vbpd-reflection/build/outputs/aar/*.aar
48-

‎config/detekt/detekt.yml‎

Lines changed: 29 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,29 @@
1+
build:
2+
maxIssues: 0
3+
4+
complexity:
5+
LongMethod:
6+
threshold: 60
7+
LongParameterList:
8+
functionThreshold: 8
9+
constructorThreshold: 8
10+
TooManyFunctions:
11+
thresholdInFiles: 20
12+
thresholdInClasses: 15
13+
thresholdInInterfaces: 10
14+
15+
style:
16+
MaxLineLength:
17+
maxLineLength: 120
18+
WildcardImport:
19+
active: false
20+
MagicNumber:
21+
active: false
22+
ReturnCount:
23+
max: 3
24+
UnusedPrivateMember:
25+
active: true
26+
27+
naming:
28+
FunctionNaming:
29+
functionPattern: '[a-zA-Z][a-zA-Z0-9]*'

‎gradle/convetions-plugins/vbpd-library-base/build.gradle.kts‎

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -9,6 +9,9 @@ dependencies {
99
implementation(libs.gradleplugins.android)
1010
implementation(libs.gradleplugins.kotlin)
1111
implementation(libs.gradleplugins.vanniktechMavenPublish)
12+
implementation(libs.gradleplugins.detekt)
13+
implementation(libs.gradleplugins.ktlint)
14+
implementation(libs.gradleplugins.kover)
1215

1316
// Workaround for version catalog working inside precompiled scripts
1417
// Issue - https://github.com/gradle/gradle/issues/15383

‎gradle/convetions-plugins/vbpd-library-base/src/main/kotlin/vbpdconfig.gradle.kts‎

Lines changed: 15 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -11,6 +11,9 @@ plugins.apply(libs.plugins.android.library.get().pluginId)
1111
plugins.apply("maven-publish")
1212
plugins.apply(libs.plugins.vanniktechMavenPublish.get().pluginId)
1313
plugins.apply("vbpdpublish")
14+
plugins.apply(libs.plugins.detekt.get().pluginId)
15+
plugins.apply(libs.plugins.ktlint.get().pluginId)
16+
plugins.apply(libs.plugins.kover.get().pluginId)
1417

1518
val libraryId = "${project.group}.${project.name.replace("vbpd-", "").replace("-", ".")}"
1619

@@ -43,9 +46,21 @@ androidLibraryConfig {
4346
jvmTarget = libs.versions.jvmTarget.get()
4447
freeCompilerArgs += listOf("-module-name", libraryId)
4548
}
49+
50+
testOptions {
51+
unitTests {
52+
isIncludeAndroidResources = true
53+
}
54+
}
4655
}
4756

4857
kotlinConfig {
4958
jvmToolchain(libs.versions.jvmTarget.get().toInt())
5059
explicitApi()
5160
}
61+
62+
extensions.configure<io.gitlab.arturbosch.detekt.extensions.DetektExtension> {
63+
config.setFrom(rootProject.files("config/detekt/detekt.yml"))
64+
buildUponDefaultConfig = true
65+
parallel = true
66+
}

‎gradle/libs.versions.toml‎

Lines changed: 22 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -14,6 +14,14 @@ kotlin = "2.1.10"
1414
vanniktechMavenPublish = "0.30.0"
1515
jvmTarget = "11"
1616
vbpd = "2.0.4"
17+
detekt = "1.23.8"
18+
ktlint = "14.0.1"
19+
kover = "0.9.7"
20+
robolectric = "4.16.1"
21+
mockk = "1.14.9"
22+
androidx-test-core = "1.6.1"
23+
androidx-test-runner = "1.6.2"
24+
junit = "4.13.2"
1725

1826
[libraries]
1927
androidx-activity = { module = "androidx.activity:activity", version.ref = "androidx-activity" }
@@ -27,10 +35,24 @@ androidx-viewbinding = { module = "androidx.databinding:viewbinding", version.re
2735
gradleplugins-android = { module = "com.android.tools.build:gradle", version.ref = "agp" }
2836
gradleplugins-kotlin = { module = "org.jetbrains.kotlin:kotlin-gradle-plugin", version.ref = "kotlin" }
2937
gradleplugins-vanniktechMavenPublish = { module = "com.vanniktech.maven.publish:com.vanniktech.maven.publish.gradle.plugin", version.ref = "vanniktechMavenPublish" }
38+
gradleplugins-detekt = { module = "io.gitlab.arturbosch.detekt:detekt-gradle-plugin", version.ref = "detekt" }
39+
gradleplugins-ktlint = { module = "org.jlleitschuh.gradle:ktlint-gradle", version.ref = "ktlint" }
40+
gradleplugins-kover = { module = "org.jetbrains.kotlinx:kover-gradle-plugin", version.ref = "kover" }
41+
42+
test-robolectric = { module = "org.robolectric:robolectric", version.ref = "robolectric" }
43+
test-mockk = { module = "io.mockk:mockk", version.ref = "mockk" }
44+
test-junit = { module = "junit:junit", version.ref = "junit" }
45+
test-kotlin = { module = "org.jetbrains.kotlin:kotlin-test", version.ref = "kotlin" }
46+
test-androidx-core = { module = "androidx.test:core-ktx", version.ref = "androidx-test-core" }
47+
test-androidx-runner = { module = "androidx.test:runner", version.ref = "androidx-test-runner" }
48+
test-fragment = { module = "androidx.fragment:fragment-testing", version.ref = "androidx-fragment" }
3049

3150
[plugins]
3251
android-application = { id = "com.android.application", version.ref = "agp" }
3352
android-library = { id = "com.android.library", version.ref = "agp" }
3453
jetbrains-kotlin-android = { id = "org.jetbrains.kotlin.android", version.ref = "kotlin" }
3554
vanniktechMavenPublish = { id = "com.vanniktech.maven.publish", version.ref = "vanniktechMavenPublish" }
3655
parcelize = { id = "org.jetbrains.kotlin.plugin.parcelize", version.ref = "kotlin" }
56+
detekt = { id = "io.gitlab.arturbosch.detekt", version.ref = "detekt" }
57+
ktlint = { id = "org.jlleitschuh.gradle.ktlint", version.ref = "ktlint" }
58+
kover = { id = "org.jetbrains.kotlinx.kover", version.ref = "kover" }

‎vbpd-core/build.gradle.kts‎

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -5,4 +5,10 @@ plugins {
55
dependencies {
66
api(libs.androidx.viewbinding)
77
implementation(libs.androidx.annotation)
8+
9+
testImplementation(libs.test.junit)
10+
testImplementation(libs.test.kotlin)
11+
testImplementation(libs.test.mockk)
12+
testImplementation(libs.test.robolectric)
13+
testImplementation(libs.test.androidx.core)
814
}
Lines changed: 41 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,41 @@
1+
package dev.androidbroadcast.vbpd
2+
3+
import android.view.View
4+
import androidx.viewbinding.ViewBinding
5+
import io.mockk.every
6+
import io.mockk.mockk
7+
import org.junit.Test
8+
import org.junit.runner.RunWith
9+
import org.robolectric.RobolectricTestRunner
10+
import kotlin.test.assertEquals
11+
12+
@RunWith(RobolectricTestRunner::class)
13+
class EagerViewBindingPropertyTest {
14+
15+
private fun createMockBinding(): ViewBinding {
16+
val view = mockk<View>()
17+
return mockk<ViewBinding> {
18+
every { root } returns view
19+
}
20+
}
21+
22+
@Test
23+
fun `getValue returns the provided binding`() {
24+
val expectedBinding = createMockBinding()
25+
val property = EagerViewBindingProperty<Any, ViewBinding>(expectedBinding)
26+
27+
val result = property.getValue(Any(), ::result)
28+
assertEquals(expectedBinding, result)
29+
}
30+
31+
@Test
32+
fun `getValue always returns same instance`() {
33+
val binding = createMockBinding()
34+
val property = EagerViewBindingProperty<Any, ViewBinding>(binding)
35+
val thisRef = Any()
36+
37+
val first = property.getValue(thisRef, ::first)
38+
val second = property.getValue(thisRef, ::second)
39+
assertEquals(first, second)
40+
}
41+
}
Lines changed: 76 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,76 @@
1+
package dev.androidbroadcast.vbpd
2+
3+
import android.view.View
4+
import androidx.viewbinding.ViewBinding
5+
import io.mockk.every
6+
import io.mockk.mockk
7+
import org.junit.Test
8+
import org.junit.runner.RunWith
9+
import org.robolectric.RobolectricTestRunner
10+
import kotlin.test.assertEquals
11+
12+
@RunWith(RobolectricTestRunner::class)
13+
class LazyViewBindingPropertyTest {
14+
15+
private fun createMockBinding(): ViewBinding {
16+
val view = mockk<View>()
17+
return mockk<ViewBinding> {
18+
every { root } returns view
19+
}
20+
}
21+
22+
@Test
23+
fun `getValue returns binding created by viewBinder`() {
24+
val expectedBinding = createMockBinding()
25+
val property = LazyViewBindingProperty<Any, ViewBinding> { expectedBinding }
26+
val thisRef = Any()
27+
28+
val result = property.getValue(thisRef, ::result)
29+
assertEquals(expectedBinding, result)
30+
}
31+
32+
@Test
33+
fun `getValue returns same instance on subsequent calls`() {
34+
var callCount = 0
35+
val binding = createMockBinding()
36+
val property = LazyViewBindingProperty<Any, ViewBinding> {
37+
callCount++
38+
binding
39+
}
40+
val thisRef = Any()
41+
42+
val first = property.getValue(thisRef, ::first)
43+
val second = property.getValue(thisRef, ::second)
44+
45+
assertEquals(first, second)
46+
assertEquals(1, callCount, "viewBinder should be called only once")
47+
}
48+
49+
@Test
50+
fun `clear resets cached binding`() {
51+
var callCount = 0
52+
val property = LazyViewBindingProperty<Any, ViewBinding> {
53+
callCount++
54+
createMockBinding()
55+
}
56+
val thisRef = Any()
57+
58+
property.getValue(thisRef, ::thisRef)
59+
property.clear()
60+
property.getValue(thisRef, ::thisRef)
61+
62+
assertEquals(2, callCount, "viewBinder should be called again after clear()")
63+
}
64+
65+
@Test
66+
fun `viewBinder receives correct thisRef`() {
67+
var receivedRef: Any? = null
68+
val property = LazyViewBindingProperty<String, ViewBinding> { ref ->
69+
receivedRef = ref
70+
createMockBinding()
71+
}
72+
73+
property.getValue("test", ::receivedRef)
74+
assertEquals("test", receivedRef)
75+
}
76+
}

‎vbpd-reflection/build.gradle.kts‎

Lines changed: 11 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -18,4 +18,15 @@ dependencies {
1818
compileOnly(libs.androidx.recyclerview)
1919
compileOnly(libs.androidx.activity)
2020
api(projects.vbpd)
21+
22+
testImplementation(libs.test.junit)
23+
testImplementation(libs.test.kotlin)
24+
testImplementation(libs.test.mockk)
25+
testImplementation(libs.test.robolectric)
26+
testImplementation(libs.test.androidx.core)
27+
testImplementation(libs.test.androidx.runner)
28+
testImplementation(libs.androidx.fragment)
29+
testImplementation(libs.androidx.activity)
30+
testImplementation(libs.androidx.recyclerview)
31+
testImplementation(libs.androidx.appcompat)
2132
}

0 commit comments

Comments
 (0)