TDD dari Plugin Gradle Kotlin yang dapat dikonfigurasi dengan JBake dan JGit
Diterbitkan 09 July 2025
Pos ini menyajikan pendekatan lengkap dalam perancangan dan pengujian (TDD) dari sebuah plugin Gradle yang ditulis dalam Kotlin DSL. Plugin ini mengotomatiskan pembuatan situs statis dengan JBake dan penerapan Git melalui JGit, semuanya dari file konfigurasi YAML.
Tujuan
Membuat plugin Gradle :
-
dapat dikonfigurasi dalam DSL Kotlin ;
-
mampu :
-
menghasilkan situs statis dari JBake ;
-
menyebarkan pada repositori Git jauh melalui JGit ;
-
dapat diintegrasikan dalam alur kerja CI/CD ;
-
diuji sesuai dengan pendekatan TDD.
Konfigurasi deklaratif
Ini adalah contoh konfigurasi YAML untuk dimodelkan dalam DSL kami:
bake:
srcPath: "./site/jbake"
destDirPath: "bake"
pushPage:
from: "bake"
to: "cvs"
repo:
name: "trainings"
repository: "https://github.com/pages-content/trainings.git"
credentials:
username: "USERNAME"
password: "SECRET_TOKEN"
branch: "main"
message: "cheroliv.com"
Konfigurasi ini direpresentasikan melalui ekstensi Gradle yang dapat dideklarasikan dalam Kotlin DSL.
Arsitektur plugin
@startuml
package "plugin mesin" {
class WorkspaceEngineExtension
class GenerateSiteTask
interface GitAdapter
class JGitAdapter
WorkspaceEnginePlugin --> WorkspaceEngineExtension
WorkspaceEnginePlugin --> GenerateSiteTask
GenerateSiteTask --> GitAdapter
JGitAdapter ..|> GitAdapter
}
@enduml
Definisi ekstensi DSL
open class WorkspaceEngineExtension {
var outputDir: String = "build/site"
var template: String = "freemarker"
var repoUrl: String = ""
var branch: String = "main"
var message: String = "Generated by engine"
}
Definisi tugas kustom
abstract class GenerateSiteTask : DefaultTask() {
lateinit var gitAdapter: GitAdapter
@get:Input abstract val repoUrl: Property<String>
@get:InputDirectory abstract val outputDir: DirectoryProperty
@get:Input abstract val branch: Property<String>
@get:Input abstract val message: Property<String>
@TaskAction
fun run() {
val repo = gitAdapter.cloneRepository(repoUrl.get(), outputDir.get().asFile)
gitAdapter.commitAndPush(repo, branch.get(), message.get())
}
}
Antarmuka GitAdapter
interface GitAdapter {
fun cloneRepository(uri: String, directory: File): Repository
fun commitAndPush(repo: Repository, branch: String, message: String)
}
Implementasi konkret dengan JGit
class JGitAdapter : GitAdapter {
override fun cloneRepository(uri: String, directory: File): Repository {
return Git.cloneRepository()
.setURI(uri)
.setDirectory(directory)
.call()
.repository
}
override fun commitAndPush(repo: Repository, branch: String, message: String) {
Git(repo).use {
it.add().addFilepattern(".").call()
it.commit().setMessage(message).call()
it.push().call()
}
}
}
Konfigurasi DSL Kotlin
workspaceEngine {
outputDir = "build/out"
template = "freemarker"
repoUrl = "https://github.com/cheroliv/trainings.git"
branch = "main"
message = "deploy from plugin"
}
Uji unit dengan Mockito
Ketergantungan
testImplementation("org.mockito:mockito-core:5.12.0")
testImplementation("org.mockito.kotlin:mockito-kotlin:5.2.1")
testImplementation("org.junit.jupiter:junit-jupiter:5.10.0")
Uji perilaku Git
class GenerateSiteTaskMockitoTest {
@TempDir
lateinit var tempDir: File
@Test
fun `task should call clone and commitAndPush`() {
val project = ProjectBuilder.builder().build()
val task = project.tasks.create("generateSite", GenerateSiteTask::class.java)
val mockGitAdapter = mock<GitAdapter>()
val mockRepo = mock<Repository>()
whenever(mockGitAdapter.cloneRepository(any(), any())).thenReturn(mockRepo)
task.gitAdapter = mockGitAdapter
task.repoUrl.set("https://github.com/cheroliv/test.git")
task.outputDir.set(project.layout.projectDirectory.dir(tempDir.name))
task.branch.set("main")
task.message.set("test commit")
task.run()
verify(mockGitAdapter).cloneRepository(eq("https://github.com/cheroliv/test.git"), any())
verify(mockGitAdapter).commitAndPush(eq(mockRepo), eq("main"), eq("test commit"))
}
}
@startuml actor "Pengembang" as Dev participant "Gradle ProjectBuilder" as Project participant "GenerateSiteTask" as Task participant "GitAdapter (Mock)" as Adapter participant "JGitAdapter / Mockito" as Impl Dev -> Project : construit projet Gradle Dev -> Task : configure les propriétés Task -> Adapter : cloneRepository() Adapter -> Impl : simulé par Mockito Task -> Adapter : commitAndPush() @enduml
Kesimpulan
Pemisahan tanggung jawab melalui`GitAdapter`memungkinkan penerapan lengkap TDD dalam pengembangan plugin Gradle :
-
panggilan Git abstrak dan dapat diuji;
-
konfigurasi DSL bersih dan jelas;
-
Plugin tetap agnostik terhadap implementasi sebenarnya.
Pendekatan ini menjamin keteruji-an yang tinggi dan ekstensibilitas (menuju GitHub API, GitLab, dst.).
Langkah selanjutnya
-
Mengintegrasikan generasi JBake sebagai`BakeAdapter`
-
Menambahkan tes integrasi dengan`GradleRunner`
-
Mendukung pemuatan otomatis file`site.yml`melalui SnakeYAML