تطوير موجه بالاختبار (TDD) لمكوّن Gradle Kotlin قابل للتكوين مع JBake وJGit
Publié le 09 July 2025
يقدم هذا المقال العملية الكاملة للتصميم والاختبار (TDD) لإضافة Gradle مكتوبة بلغة Kotlin DSL. ي automate هذه الإضافة إنشاء المواقع الثابتة باستخدام JBake و النشر عبر Git عبر JGit، كل ذلك بدءًا من ملف إعدادات YAML.
الهدف
إنشاء إضافة Gradle :
-
قابل للتكوين في Kotlin DSL ;
-
قادر على :
-
إنشاء موقع ثابت من JBake;
-
نشر على مستودع Git عن بعد عبر 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.
بنية الإضافة
تعريف امتداد 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"))
}
}
الخاتمة
فصل المسؤوليات عبر`GitAdapter`يسمح بتطبيق TDD بالكامل في تطوير إضافة Gradle :
-
استدعاءات جيت مجردة وقابلة للاختبار ;
-
إعدادات DSL نظيفة وواضحة;
-
يظل البرنامج المساعد محايداً من حيث التنفيذ الفعلي
هذا النهج يضمن قابلية اختبار عالية وتوسعية (إلى GitHub API، GitLab، وما إلى ذلك).
الخطوات التالية
-
دمج توليد JBake كـ`BakeAdapter`
-
أضف اختبارات التكامل مع`GradleRunner`
-
دعم تحميل تلقائي الملفات`site.yml`عبر SnakeYAML