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
MasalahTidak 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 {
}
}
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.
MasalahDi 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 Customer
yang memiliki konstruktor dengan 2 bidang wajib email
dan name
dan 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() }
static def getNationalIdentifier() { new NationalIdentifier() }
static def getPaymentInfo() { new PaymentInfo() }
}
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. KelasMasalahdan 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.MasalahJika Anda membuat suspend
fungsi:suspend fun someSuspendFun() {
}
, lalu dikompilasi sebagai berikut:public void someSuspendFun(Continuation<? super Unit> $completion) {
}
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