Menguji di Kotlin dengan Spock

Tujuan artikel ini adalah untuk menunjukkan kesulitan apa yang muncul saat menggunakan Spock dengan Kotlin, apa cara untuk menyelesaikannya dan menjawab pertanyaan apakah akan menggunakan Spock jika Anda mengembangkannya di Kotlin. Detail di bawah potongan.

Saya bekerja untuk perusahaan yang mempraktikkan pemrograman ekstrim . Salah satu metode utama pemrograman ekstrim yang kami gunakan dalam pekerjaan sehari-hari adalah TDD (test-driven development). Ini berarti bahwa sebelum mengubah kode, kami menulis tes yang mencakup perubahan yang diinginkan. Dengan demikian, kami secara teratur menulis tes dan memiliki cakupan kode dengan tes mendekati 100%. Ini membuat persyaratan tertentu untuk memilih kerangka uji: itu adalah satu hal untuk menulis tes seminggu sekali, itu cukup lain untuk melakukannya setiap hari.

Kami sedang mengembangkan di Kotlin dan pada titik tertentu kami memilih Spock sebagai kerangka kerja utama. Sekitar 6 bulan telah berlalu sejak saat itu, rasa euforia dan kebaruan telah berlalu, jadi artikel itu adalah semacam retrospektif di mana saya akan mencoba untuk memberi tahu Anda kesulitan apa yang kami temui selama ini dan bagaimana kami menyelesaikannya.

Kenapa tepatnya Spock?




Pertama-tama, Anda perlu mencari tahu kerangka kerja mana yang memungkinkan Kotlin untuk diuji dan apa keuntungan yang diberikan Spock dibandingkan dengan mereka.

Salah satu kelebihan Kotlin adalah kompatibilitasnya dengan Java, yang memungkinkan Anda untuk menggunakan kerangka kerja Java apa pun untuk pengujian, seperti Junit , TestNG , Spock , dll. Pada saat yang sama, ada kerangka kerja yang dirancang khusus untuk Kotlin seperti Spek dan Kotest . Mengapa kami memilih Spock?

Saya akan menyoroti keunggulan berikut:

  • Pertama, Spock ditulis dalam bahasa Groovy. Baik atau buruk - menilai sendiri. Tentang Groovy, Anda dapat membaca artikel di sini . Groovy secara pribadi menarik saya ke sintaks singkat, keberadaan pengetikan dinamis, sintaks bawaan untuk daftar, array reguler dan asosiatif, dan konversi jenis yang benar-benar gila.
  • Kedua, Spock sudah berisi kerangka kerja mock (MockingApi) dan perpustakaan pernyataan;
  • Spock juga bagus untuk menulis tes berparameter:

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

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

Ini adalah perbandingan yang sangat dangkal (jika bisa disebut perbandingan sama sekali), tetapi ini kira-kira alasan mengapa kami memilih Spock. Secara alami, dalam minggu-minggu dan bulan-bulan berikutnya kami menghadapi beberapa kesulitan.

Masalah muncul saat menguji Kotlin dengan Spock dan menyelesaikannya


Di sini saya segera membuat reservasi bahwa masalah - masalah yang dijelaskan di bawah ini tidak unik untuk Spock, mereka akan relevan untuk kerangka kerja Java dan mereka terutama terkait dengan kompatibilitas dengan Kotlin .

1. final secara default


Masalah

Tidak seperti Java, semua kelas Kotlin memiliki pengubah secara default final, yang mencegah keturunan lebih lanjut dan penimpaan metode. Masalahnya di sini adalah ketika membuat objek tiruan dari kelas apa pun, sebagian besar kerangka tiruan mencoba membuat objek proxy, yang hanya merupakan turunan dari kelas asli dan menimpa metodenya.

Karena itu, jika Anda memiliki layanan:

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

dan Anda mencoba membuat tiruan dari layanan ini di Groovy:

def customerServiceMock = Mock(CustomerService)

maka Anda mendapatkan kesalahan:
org.spockframework.mock.CannotCreateMockException: Cannot create mock for class CustomerService because Java mocks cannot mock final classes

Keputusan:

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

2.


Masalah

Di Kotlin, argumen fungsi atau konstruktor dapat memiliki nilai default yang digunakan jika argumen fungsi tidak ditentukan saat dipanggil. Kesulitannya adalah bytecode Java kehilangan nilai argumen default dan nama parameter fungsi, dan karenanya, ketika memanggil fungsi atau konstruktor Kotlin dari Spock, semua nilai harus ditentukan secara eksplisit.

Pertimbangkan kelas Customeryang memiliki konstruktor dengan 2 bidang wajib emaildan namedan bidang opsional yang memiliki nilai default:

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
)

Untuk membuat turunan dari kelas ini dalam tes Groovy Spock, Anda harus memberikan nilai untuk semua argumen ke konstruktor:

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

Keputusan:


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


Ini mungkin area yang cukup sempit, tetapi jika Anda berencana untuk menggunakan refleksi dalam tes Anda, Anda harus memikirkan apakah akan menggunakan Groovy. Dalam proyek kami, kami menggunakan banyak anotasi dan, agar tidak lupa memberi anotasi pada kelas atau bidang tertentu dengan anotasi, kami menulis tes menggunakan refleksi. Di sinilah kesulitan muncul. Kelas

Masalah

dan anotasi ditulis dalam Kotlin, logika yang terkait dengan tes dan refleksi di Groovy. Karena itu, tes menghasilkan hash dari Java Reflection API dan Kotlin Reflection:

Konversi kelas Java ke Kotlin:

def kotlinClass = new KClassImpl(clazz)

Memanggil fungsi ekstensi Kotlin sebagai metode statis:
ReflectJvmMapping.getJavaType(property.returnType)

Kode yang sulit dibaca di mana jenis Kotlin akan digunakan:
private static boolean isOfCollectionType(KProperty1<Object, ?> property) {
    // Some logic
}

Tentu saja, ini bukan sesuatu yang berlebihan atau tidak mungkin. Satu-satunya pertanyaan adalah, bukankah lebih mudah melakukan ini dengan kerangka kerja pengujian Kotlin, di mana dimungkinkan untuk menggunakan fungsi ekstensi dan Refleksi Kotlin murni ??

4. Coroutines


Jika Anda berencana untuk menggunakan coroutine, maka ini adalah alasan lain untuk berpikir apakah Anda ingin memilih kerangka kerja Kotlin untuk pengujian.

Masalah

Jika Anda membuat suspendfungsi:

suspend fun someSuspendFun() {
    // Some logic
}

, lalu dikompilasi sebagai berikut:

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

di mana Lanjutan adalah kelas Java yang merangkum logika untuk mengeksekusi coroutine. Di sinilah muncul masalah, bagaimana cara membuat objek kelas ini? Juga, runBlocking tidak tersedia di Groovy . Jadi umumnya tidak begitu jelas cara menguji kode dengan coroutine di Spock.

Ringkasan


Lebih dari enam bulan menggunakan Spock, itu membawa kami lebih baik daripada buruk dan kami tidak menyesali pilihan: tes mudah dibaca dan dipelihara, menulis tes parameter adalah kesenangan. Seekor lalat di salep dalam kasus kami ternyata adalah pantulan, tetapi digunakan hanya di beberapa tempat, sama dengan coroutine.

Saat memilih kerangka kerja untuk menguji proyek yang sedang dikembangkan di Kotlin, Anda harus berpikir dengan hati-hati tentang fitur bahasa apa yang Anda rencanakan untuk digunakan. Jika Anda menulis aplikasi CRUD sederhana, Spock adalah solusi yang tepat. Apakah Anda menggunakan coroutine dan refleksi? Maka lebih baik lihat Spek atau Kotest .

Materi tidak mengklaim lengkap, jadi jika Anda menemui kesulitan lain saat menggunakan Spock dan Kotlin - tulis di komentar.

tautan yang bermanfaat



All Articles