TDD پلاگین Gradle Kotlin قابل تنظیم با JBake و JGit
منتشر شده در 09 July 2025
این پست، رویکرد کامل طراحی و تست (TDD) یک پلاگین Gradle که در Kotlin DSL نوشته شده را معرفی میکند. این پلاگین، تولید سایتهای استاتیک را با JBake و déploiement Git از طریق JGit خودکار میسازد، همه اینها تنها از طریق یک فایل YAML پیکربندی انجام میشود.
هدف
ایجاد یک پلاگین Gradle :
-
قابلتنظیم در DSL Kotlin ;
-
توانای :
-
ایجاد یک وبسایت استاتیک از JBake ;
-
استقرار روی یک مخزن گیت دور با JGit ;
-
قابل ادغام در یک گردش کار 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 قابل تعریف در DSL Kotlin نمایش داده میشود.
ساختار پلاگین
@startuml
package "پلاگین موتور" {
class WorkspaceEngineExtension
class GenerateSiteTask
interface GitAdapter
class JGitAdapter
WorkspaceEnginePlugin --> WorkspaceEngineExtension
WorkspaceEnginePlugin --> GenerateSiteTask
GenerateSiteTask --> GitAdapter
JGitAdapter ..|> GitAdapter
}
@enduml
تعریف افزونه 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()
}
}
}
Configuration 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"))
}
}
@startuml actor "توسعهدهنده" as Dev participant "Gradle ProjectBuilder" as Project participant "GenerateSiteTask" as Task participant "GitAdapter (نمونه)" 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
نتیجه
تقسیم مسئولیتها از طریق`GitAdapter`اجازه میدهد تا TDD را بهطور کامل در توسعه پلاگین Gradle اعمال نماید :
-
فراخوانیهای Git انتزاعی و قابل آزمون هستند؛
-
پیکربندی DSL تمیز و واضح است ;
-
پلاگین همچنان بیتعلق به پیادهسازی واقعی میماند.
این رویکرد اطمینان میدهد که قابلیت تست و گسترش بالا (به سمت GitHub API, GitLab و غیره) است.
مراحل بعدی
-
يکيسازي توليد JBake به صورت یک`BakeAdapter`
-
افزودن تستهای یکپارچهسازی با`GradleRunner`
-
پشتیبانی از بارگذاری خودکار فایلها`site.yml`via SnakeYAML