الاختبار في Kotlin مع Spock

الغرض من المقالة هو إظهار الصعوبات التي تنشأ عند استخدام Spock مع Kotlin ، وما هي طرق حلها والإجابة على السؤال عما إذا كان الأمر يستحق استخدام Spock إذا كنت تقوم بالتطوير على Kotlin. التفاصيل تحت القطع.

أنا أعمل في شركة تمارس البرمجة المتطرفة . إحدى الطرق الرئيسية للبرمجة المتطرفة التي نستخدمها في عملنا اليومي هي TDD (التطوير القائم على الاختبار). هذا يعني أنه قبل تغيير الرمز ، نكتب اختبارًا يغطي التغيير المطلوب. وبالتالي ، نكتب الاختبارات بانتظام ولدينا تغطية كود مع اختبارات قريبة من 100 ٪. هذا يتطلب متطلبات معينة لاختيار إطار الاختبار: إن كتابة الاختبارات مرة واحدة في الأسبوع شيء ، وهو أمر مختلف تمامًا للقيام به كل يوم.

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

لماذا سبوك بالضبط؟




بادئ ذي بدء ، تحتاج إلى معرفة أي الأطر تسمح باختبار Kotlin وما المزايا التي يمنحها Spock مقارنة بها.

واحدة من مزايا Kotlin هي توافقه مع Java ، مما يسمح لك باستخدام أي أطر Java للاختبار ، مثل Junit و TestNG و Spock ، إلخ. في الوقت نفسه ، هناك أطر مصممة خصيصًا لكوتلن مثل Spek و Kotest . لماذا اخترنا سبوك؟

أود أن أسلط الضوء على المزايا التالية:

  • أولاً ، تمت كتابة Spock في Groovy. جيد أو سيئ - تحكم على نفسك. حول Groovy ، يمكنك قراءة المقال هنا . يجذبني Groovy شخصيًا إلى البنية المقتضبة ، ووجود الكتابة الديناميكية ، والبناء المدمج للقوائم ، والمصفوفات المنتظمة والمرتبطة ، وتحويلات النوع المجنونة تمامًا.
  • ثانيًا ، يحتوي Spock بالفعل على إطار عمل وهمي (MockingApi) ومكتبة تأكيدية ؛
  • يعتبر Spock أيضًا رائعًا لكتابة الاختبارات ذات المعلمات:

def "maximum of two numbers"() {
    expect:
    Math.max(a, b) == c

    where:
    a | b | c
    1 | 3 | 3
    7 | 4 | 7
    0 | 0 | 0
  }

هذه مقارنة سطحية للغاية (إذا كان يمكن تسميتها مقارنة على الإطلاق) ، ولكن هذه هي الأسباب تقريبًا لاختيارنا Spock. بطبيعة الحال ، واجهنا بعض الصعوبات في الأسابيع والأشهر التالية.

واجهت مشاكل عند اختبار Kotlin مع Spock وحلها


هنا أقوم بالحجز على الفور بأن المشاكل الموضحة أدناه ليست فريدة من نوعها لـ Spock ، وستكون ذات صلة بأي إطار عمل Java ، وهي مرتبطة بشكل أساسي بالتوافق مع Kotlin .

1. النهائي بشكل افتراضي


المشكلة على

عكس Java ، تحتوي جميع فئات Kotlin على مُعدِّل افتراضيًا final، مما يمنع المزيد من السلالات وتجاوز الأساليب. المشكلة هنا هي أنه عند إنشاء كائنات وهمية من أي فئة ، تحاول معظم أطر العمل الوهمية إنشاء كائن وكيل ، وهو مجرد سليل للفئة الأصلية ويتجاوز أساليبه.

لذلك ، إذا كان لديك خدمة:

class CustomerService {
    fun getCustomer(id: String): Customer {
        // Some logic
    }
}

وحاولت إنشاء وهمية لهذه الخدمة في Groovy:

def customerServiceMock = Mock(CustomerService)

ثم تحصل على خطأ:
org.spockframework.mock.CannotCreateMockException: Cannot create mock for class CustomerService because Java mocks cannot mock final classes

القرار:

  • , , open, . «» , , , , ;
  • all-open . . Spring Framework Hibernate, cglib(Code Generation Library);
  • , mock-, Kotlin (, mockk). , , Spock Spock MockingApi.

2.


المشكلة

في Kotlin ، يمكن أن تحتوي وسيطات الدالة أو المُنشئ على قيم افتراضية يتم استخدامها إذا لم يتم تحديد وسيطة الدالة عند استدعاؤها. تكمن الصعوبة في أن Java bytecode يفقد قيم الوسيطة الافتراضية وأسماء معلمات الدالة ، وبالتالي ، عند استدعاء دالة أو مُنشئ Kotlin من Spock ، يجب تحديد جميع القيم بشكل صريح.

النظر في الطبقة Customerالتي تحتوي على منشئ مع 2 الحقول الإلزامية emailو nameوالحقول الاختيارية التي تحتوي على قيم الافتراضية:

data class Customer(
    val email: String,
    val name: String,
    val surname: String = "",
    val age: Int = 18,
    val identifiers: List<NationalIdentifier> = emptyList(),
    val addresses: List<Address> = emptyList(),
    val paymentInfo: PaymentInfo? = null
)

لإنشاء مثيل من هذه الفئة في اختبار Groovy Spock ، سيكون عليك تمرير القيم لجميع الوسائط إلى المُنشئ:

new Customer("john.doe@gmail.com", "John", "", 18, [], [], null)

القرار:


class ModelFactory {
    static def getCustomer() { 
        new Customer(
            "john.doe@gmail.com", 
            "John", 
            "Doe", 
            18, 
            [nationalIdentifier], 
            [address], 
            paymentInfo
        ) 
    }

    static def getAddress() { new Address(/* arguments */) }

    static def getNationalIdentifier() { new NationalIdentifier(/* arguments */) }

    static def getPaymentInfo() { new PaymentInfo(/* arguments */) }
}

3. Java Reflection vs Kotlin Reflection


قد تكون هذه منطقة ضيقة إلى حد ما ، ولكن إذا كنت تخطط لاستخدام التفكير في اختباراتك ، فعليك التفكير بشكل خاص في ما إذا كنت تريد استخدام Groovy. في مشروعنا ، نستخدم الكثير من التعليقات التوضيحية ، ومن أجل عدم نسيان التعليق على فئات معينة أو حقول معينة مع التعليقات التوضيحية ، نكتب اختبارات باستخدام الانعكاس. هذا حيث تنشأ الصعوبات. يتم كتابة فئات

المشاكل

والشروح في Kotlin ، والمنطق المتعلق بالاختبارات والتأمل في Groovy. ولهذا السبب ، تنتج الاختبارات تجزئة من Java Reflection API و Kotlin Reflection:

تحويل فئات Java إلى Kotlin:

def kotlinClass = new KClassImpl(clazz)

استدعاء وظيفة ملحق Kotlin كطرق ثابتة:
ReflectJvmMapping.getJavaType(property.returnType)

رمز يصعب قراءته حيث سيتم استخدام أنواع Kotlin:
private static boolean isOfCollectionType(KProperty1<Object, ?> property) {
    // Some logic
}

بالطبع ، هذا ليس شيئًا مفرطًا أو مستحيلًا. السؤال الوحيد هو ، أليس من الأسهل القيام بذلك باستخدام إطار اختبار Kotlin ، حيث سيكون من الممكن استخدام وظائف التمديد و Kotlin Reflection النقي؟

4. Coroutines


إذا كنت تخطط لاستخدام coroutines ، فهذا سبب آخر للتفكير في ما إذا كنت ترغب في اختيار إطار Kotlin للاختبار.

المشكلة

إذا قمت بإنشاء suspendدالة:

suspend fun someSuspendFun() {
    // Some logic
}

ثم يجمع ما يلي:

public void someSuspendFun(Continuation<? super Unit> $completion) {
    // Some logic
}

حيث Continuation هي فئة Java تقوم بتغليف منطق تنفيذ Coroutine. هذا هو المكان الذي تنشأ فيه المشكلة ، كيف يمكن إنشاء كائن من هذه الفئة؟ أيضا ، لا يتوفر runBlocking في Groovy . وبالتالي ، من غير الواضح بشكل عام كيفية اختبار الشفرة باستخدام Coroutines في Spock.

ملخص


على مدى ستة أشهر من استخدام Spock ، جلب لنا المزيد من الخير وليس سيئًا ولا نأسف على الاختيار: من السهل قراءة الاختبارات وصيانتها ، وكتابة الاختبارات المعيارية متعة. اتضح أن الذبابة في المرهم في حالتنا هي انعكاس ، ولكنها تستخدم في أماكن قليلة فقط ، مثلما هو الحال مع coroutines.

عند اختيار إطار عمل لاختبار مشروع يتم تطويره على Kotlin ، يجب أن تفكر بعناية في ميزات اللغة التي تخطط لاستخدامها. إذا كنت تكتب تطبيق CRUD بسيط ، فإن Spock هو الحل الأمثل. هل تستخدم coroutines والتفكير؟ ثم انظر بشكل أفضل إلى Spek أو Kotest .

لا تدعي المادة أنها كاملة ، لذلك إذا واجهت صعوبات أخرى عند استخدام Spock و Kotlin - اكتب في التعليقات.

روابط مفيدة



All Articles