اختبار قائم على الخصائص في Kotlin: كسر الكود أسرع مما تظن

مدرب بايثون أونلاين للمبتدئين

تعلم Python بسهولة دون إرهاق نظري. حل مهام عملية مع التحقق التلقائي واكتب الكود مباشرة في المتصفح.

ابدأ الدورة

لماذا الاختبارات التقليدية تكذب وأنت تصدقها



تخيل: لقد كتبت دالة، وغطيتها بثلاثة اختبارات وحدة. علامة خضراء. CI يمر. الإنتاج — خطأ. مألوف؟



مشكلة اختبارات الوحدة التقليدية هي أنها تتحقق من أمثلة محددة. أنت بنفسك تبتكر بيانات الإدخال وتكتب النتيجة المتوقعة. هذا يشبه امتحانًا حيث يعد الطالب أوراق الأسئلة بنفسه. عاجلاً أم آجلاً، ستغفل عن حالة حدية — null، قائمة فارغة، رقم سالب، أو سلسلة تحتوي على رموز Unicode.



هنا يظهر الاختبار القائم على الخصائص (property-based testing). بدلاً من كتابة: "إذا مررت 5، يجب أن يعيد 25"، تقوم بصياغة خاصية: "لأي عدد صحيح، المربع دائمًا غير سالب". والإطار يولد بنفسه مئات القيم العشوائية ويتحقق من هذه الخاصية.



في هذه المقالة، سنشرح كيفية استخدام الاختبار القائم على الخصائص في Kotlin مع مكتبة Kotest. ستتعلم كيفية كتابة اختبارات موثوقة، واكتشاف الأخطاء في المراحل المبكرة، والتوقف عن الخوف من إعادة الهيكلة. هذه ليست مجرد نظرية مملة — سنكتب كودًا حقيقيًا يمكن تطبيقه فورًا في المشروع.



ما هو الاختبار القائم على الخصائص ولماذا Kotlin؟



الفكرة ليست جديدة — فقد نشرتها مكتبة QuickCheck للغة Haskell في عام 1999. ولكن الآن فقط، مع تزايد تعقيد أنظمة الخلفية والخدمات المصغرة، أصبح الاختبار القائم على الخصائص ضرورة في مجموعة أدوات المطور.



Kotlin هي اللغة المثالية لهذا النهج. لماذا؟



  • الإمكانيات الوظيفية: lambdas، دوال الامتداد، الثبات — كل هذا يبسط كتابة الخصائص النقية.
  • Kotest — إطار قوي يدعم بشكل مدمج اختبارات الخصائص، والمولدات، و Shrinking (خوارزمية تقليل المثال الفاشل).
  • التوافق مع Java: يمكنك اختبار الكود القديم المكتوب بلغة Java باستخدام نفس الأساليب.


لننتقل مباشرة إلى العمل. أدناه مثال سيغير نظرتك للاختبار.



أول اختبار قائم على الخصائص في 5 دقائق



لنفترض أن لدينا دالة تتحقق مما إذا كانت السلسلة متناظرة (Palindrome):



fun isPalindrome(s: String): Boolean {    val cleaned = s.filter { it.isLetterOrDigit() }.lowercase()    return cleaned == cleaned.reversed()}


الاختبار التقليدي يبدو هكذا:



@Testfun `test known palindromes`() {    assertTrue(isPalindrome("A man, a plan, a canal: Panama"))    assertTrue(isPalindrome("racecar"))    assertFalse(isPalindrome("hello"))}


كل شيء يعمل. ولكن ماذا لو كانت بيانات الإدخال تحتوي على مسافات أو أرقام أو رموز؟ وماذا لو كانت السلسلة فارغة؟ أو مكونة من مسافات فقط؟



الآن لنكتب اختبارًا قائمًا على الخصائص باستخدام Kotest:



import io.kotest.core.spec.style.StringSpecimport io.kotest.property.forAllimport io.kotest.property.arbitrary.string

class PalindromePropertyTest : StringSpec({ "أي سلسلة بعد التنظيف يجب أن تكون متناظرة مع نفسها" { forAll<String> { original -> val cleaned = original.filter { it.isLetterOrDigit() }.lowercase() isPalindrome(cleaned) == true } }})


ماذا يحدث هنا؟ forAll<String> يولد سلاسل عشوائية: فارغة، بمسافات، بـ Unicode، بأرقام. ولكل منها يتحقق من الخاصية. إذا انتهكت سلسلة واحدة الادعاء — سيفشل الاختبار، وسيظهر Kotest أصغر مثال مكرر.



بالفعل في هذه المرحلة، يمكنك اكتشاف الأخطاء. على سبيل المثال، إذا كانت السلسلة مكونة من مسافات فقط — فإن filter سيعيد سلسلة فارغة، و isPalindrome("") سيعيد true. الاختبار التقليدي كان سيتجاهل هذا.



المولدات: أد

المدونات

توصيات الكتب