Bu gönderi, Kotlin DSL’de yazılmış bir Gradle eklentisinin tasarım ve test sürecinin tamamı (TDD)ni sunar. Bu eklenti, JBake ile statik sitelerin üretimini ve JGit üzerinden Git dağıtımını otomatikleştirir, bir YAML yapılandırma dosyasından.

Hedef

Bir Gradle plugin oluştur:

  • Kotlin DSL’de yapılandırılabilir ;

  • yapabilecek :

  • JBake’ten statik bir site oluştur ;

  • Uzak bir Git deposuna JGit kullanarak dağıt;

  • CI/CD iş akışı içinde entegre edilebilir ;

  • TDD yaklaşımıyla test edildi.

Bildirim tabanlı yapılandırma

İşte DSL’ümüzde modellenmesi gereken bir YAML yapılandırma örneği:

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"

Bu yapılandırma, Kotlin DSL’de bildirilebilen bir Gradle uzantısı aracılığıyla gösterilir.

Eklenti mimarisi

@startuml
package "motor eklentisi" {
class WorkspaceEngineExtension
class GenerateSiteTask
interface GitAdapter
class JGitAdapter
WorkspaceEnginePlugin --> WorkspaceEngineExtension
WorkspaceEnginePlugin --> GenerateSiteTask
GenerateSiteTask --> GitAdapter
JGitAdapter ..|> GitAdapter
}
@enduml

DSL uzantısı tanımı

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"
}

Özel görev tanımı

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())
    }
}

GitAdapter Arabirimi

interface GitAdapter {
    fun cloneRepository(uri: String, directory: File): Repository
    fun commitAndPush(repo: Repository, branch: String, message: String)
}

JGit ile somut uygulama

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()
        }
    }
}

Kotlin DSL yapılandırması

workspaceEngine {
    outputDir = "build/out"
    template = "freemarker"
    repoUrl = "https://github.com/cheroliv/trainings.git"
    branch = "main"
    message = "deploy from plugin"
}

Mockito ile birim testleri

Bağımlılıklar

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")

Git davranış testi

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 "Geliştirici" as Dev
participant "Gradle Proje Oluşturucu" 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

Sonuç

Sorumluluk ayrımıyla`GitAdapter`Gradle eklenti geliştirmesinde TDD’yi tamamen uygulamasına olanak tanır:

  • Git çağrıları soyut ve test edilebilir ;

  • DSL yapılandırması temiz ve net;

  • eklenti gerçek uygulamadan agnostik kalır.

Bu yaklaşım yüksek test edilebilirlik ve genişletilebilirlik sağlar (GitHub API, GitLab vb. için).

Bir sonraki adımlar

  • JBake üretimi bir olarak entegrate etmek`BakeAdapter`

  • Entegrasyon testleri eklemek`GradleRunner`

  • Dosyaların otomatik olarak yüklenmesini desteklemek`site.yml`SnakeYAML aracılığıyla

İlgili Makaleler