Αυτό το άρθρο παρουσιάζει τη πλήρη διαδικασία σχεδίασης και δοκιμών (TDD) ενός plugin Gradle γραμμένου σε Kotlin DSL. Αυτό το plugin αυτοματοποιεί τη δημιουργία στατικών ιστότοπων με το JBake και τη διαδικασία ανάπτυξης Git μέσω του JGit, όλα από ένα αρχείο YAML ρυθμίσεων.

Στόχος

Δημιουργήστε ένα πρόσθετό Gradle:

  • ρυθμίσιμο σε Kotlin DSL ;

  • Μπορεί να :

  • δημιουργήστε ένα στατικό ιστότοπο από το JBake ;

  • ανάπτυξη σε ένα απομακρυσμένο αποθετήριο Git μέσω JGit ;

  • συμβατός με ένα workflow CI/CD;

  • ελεγμένο σύμφωνα με μια προσέγγιση TDD.

Δηλωτική Διαμόρφωση

Αυτό είναι ένα παράδειγμα ρύθμισης YAML που θα μοντελοποιηθεί στον DSL μας :

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"

Αυτή η ρύθμιση παρουσιάζεται μέσω μιας επέκτασης Gradle που είναι δηλώσιμη σε Kotlin DSL.

Αρχιτεκτονική του plugin

jbake asciidoctor class diagram

Ορισμός της επέκτασης 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"
}

Ορισμός προσαρμοσμένης εργασίας

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

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

Συγκεκριμένη υλοποίηση με το 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()
        }
    }
}

Διαμόρφωση DSL Kotlin

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

Μονάδες δοκιμών με Mockito

εξαρτήσεις

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

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"))
    }
}
jbake asciidoctor sequence diagram

Συμπέρασμα

Η διαχωρισμός των ευθυνών μέσω`GitAdapter`επιτρέπει την πλήρη εφαρμογή του TDD στην ανάπτυξη προσθέτων Gradle :

  • Οι κλήσεις Git είναι αφηρημένες και δοκιμάσιμες;

  • Η ρύθμιση DSL είναι καθαρή και σαφής;

  • το plugin παραμένει αγνωστικό της πραγματικής υλοποίησης.

Αυτή η προσέγγιση εξασφαλίζει υψηλή δοκιμασιμότητα και επεκτασιμότητα (π.χ. GitHub API, GitLab, κλπ.).

Επόμενα βήματα

  • Ενσωμάτωση της γεννήσης JBake ως ένα`BakeAdapter`

  • Προσθέστε δοκιμές ενσωμάτωσης με`GradleRunner`

  • Υποστήριξη αυτόματης φόρτωσης αρχείων`site.yml`Μέσω SnakeYAML

Σχετικά άρθρα