الموكس والفيكستشرز في Kotlin: دليل اختبار بدون ألم

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

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

ابدأ الدورة

مقدمة: لماذا الاختبارات بدون موكس مثل القهوة بدون كافيين؟

\n\n

مرحباً يا زميل! إذا كنت تكتب بلغة Kotlin وتريد ألا يتعطل كودك مع كل commit ثاني، عاجلاً أم آجلاً ستصل إلى الاختبارات. وفي الاختبارات - إلى كائنات mock والفيكستشرز. بدونها، تتحول اختبارات الوحدة إلى اختبارات تكاملية: فأنت لا تختبر فئة واحدة، بل سلسلة كاملة من التبعيات. وإذا تعطلت قاعدة البيانات أو أعادت API خطأ 500 - سيفشل الاختبار، على الرغم من أن كودك قد يكون مثالياً.

\n\n

في هذه المقالة، سنشرح كيفية استخدام كائنات Mock والفيكستشرز في اختبارات Kotlin بشكل صحيح، بحيث تكون الاختبارات سريعة وموثوقة وتخبرك بالحقيقة، وليس "كل شيء معطل، أصلحه". هيا بنا!

\n\n

ما هو mock ولماذا نحتاجه في Kotlin؟

\n\n

كائن Mock هو محاكاة لكائن حقيقي. بدلاً من تشغيل خادم كامل أو قاعدة بيانات، نقوم بإنشاء "بديل" يتصرف كما نطلب. هذا يسمح بعزل الكود المختبر عن العالم الخارجي.

\n\n

المفاهيم الأساسية

\n
    \n
  • Mock - كائن نتحكم في سلوك دواله. يمكننا تحديد ما سيعيده، والتحقق مما إذا تم استدعاؤه.
  • \n
  • Stub - حالة خاصة من mock، تعيد فقط قيماً محددة مسبقاً. لا تتحقق من الاستدعاءات.
  • \n
  • Fixture (فيكستشر) - مجموعة من البيانات أو الحالات المعدة مسبقاً اللازمة لتنفيذ الاختبار. على سبيل المثال، مستخدم اختباري أو إعدادات قاعدة بيانات.
  • \n
\n\n

في عالم Kotlin، هناك لاعبان رئيسيان: MockK (الأصلي لـ Kotlin) و Mockito (الشائع في Java). سنستخدم MockK - فهو يتعامل بشكل أفضل مع coroutines ودوال التوسيع والفئات المختومة (sealed classes).

\n\n

إعداد المشروع: إضافة MockK و JUnit 5

\n\n

في البداية، نضيف التبعيات في ملف build.gradle.kts:

\n\n
dependencies {\n    testImplementation("io.mockk:mockk:1.13.8")\n    testImplementation("org.junit.jupiter:junit-jupiter:5.10.0")\n}\n
\n\n

الآن يمكننا كتابة الاختبارات. سننشئ خدمة بسيطة ونختبرها باستخدام الموكس.

\n\n

المثال الأول: mocking المستودع

\n\n

تخيل أن لدينا UserService يعتمد على UserRepository:

\n\n
class UserService(private val repository: UserRepository) {\n    fun getUserName(id: Long): String {\n        val user = repository.findById(id)\n            ?: throw IllegalArgumentException("User not found")\n        return user.name\n    }\n}\n
\n\n

الآن نكتب اختباراً باستخدام كائن mock للمستودع:

\n\n
import io.mockk.every\nimport io.mockk.mockk\nimport io.mockk.verify\nimport org.junit.jupiter.api.Test\nimport kotlin.test.assertEquals\nimport kotlin.test.assertFailsWith\n\nclass UserServiceTest {\n\n    private val repository = mockk<UserRepository>()\n    private val service = UserService(repository)\n\n    @Test\n    fun `should return user name when user exists`() {\n        // Arrange\n        every { repository.findById(1L) } returns User(1L, "Alice")\n\n        // Act\n        val name = service.getUserName(1L)\n\n        // Assert\n        assertEquals("Alice", name)\n        verify { repository.findById(1L) }\n    }\n\n    @Test\n    fun `should throw exception when user not found`() {\n        // Arrange\n        every { repository.findById(any()) } returns null\n\n        // Act & Assert\n        assertFailsWith<IllegalArgumentException> {\n            service.getUserName(99L)\n        }\n    }\n}\n
\n\n

لاحظ: every يحدد سلوك mock، و verify يتحقق من استدعاء الدالة. هذا هو أساس كائنات mock والفيكستشرز في اختبارات Kotlin.

\n\n

الفيكستشرز: إعداد البيانات مسبقاً

\n\n

عندما يكون هناك العديد من الاختبارات، فإن تكرار إنشاء البيانات هو ممارسة سيئة. هنا تأتي الفيكستشرز للإنقاذ. في Kotlin، من السهل

المدونات

توصيات الكتب