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

Artikel terkait