Dengan beberapa bahasa yang paling umum (Java, C #, Java, dll) kadang-kadang tampaknya Anda bekerja bertentangan dengan bahasa ketika Anda ingin sepenuhnya TDD kode Anda.
Sebagai contoh, di Java dan C # Anda ingin mengejek dependensi kelas Anda dan sebagian besar kerangka kerja mengejek akan merekomendasikan agar Anda mengejek antarmuka bukan kelas. Ini sering berarti bahwa Anda memiliki banyak antarmuka dengan implementasi tunggal (efek ini bahkan lebih terlihat karena TDD akan memaksa Anda untuk menulis lebih banyak kelas kecil). Solusi yang memungkinkan Anda mengejek kelas beton dengan benar melakukan hal-hal seperti mengubah kompiler atau menimpa pemuat kelas dll, yang cukup buruk.
Jadi, seperti apa sebuah bahasa jika dirancang dari awal untuk menjadi hebat bagi TDD? Mungkin beberapa cara tingkat bahasa cara menggambarkan dependensi (daripada melewati antarmuka ke konstruktor) dan mampu memisahkan antarmuka kelas tanpa melakukannya secara eksplisit?
Jawaban:
Bertahun-tahun yang lalu saya melemparkan prototipe yang membahas pertanyaan serupa; inilah tangkapan layar:
Idenya adalah bahwa pernyataan sejalan dengan kode itu sendiri, dan semua tes berjalan pada dasarnya pada setiap keystroke. Jadi segera setelah Anda lulus tes, Anda melihat metode berubah menjadi hijau.
sumber
Ini akan diketik secara dinamis daripada diketik secara statis. Mengetik bebek kemudian akan melakukan pekerjaan yang sama seperti antarmuka dalam bahasa yang diketik secara statis. Juga, kelas-kelasnya akan dapat dimodifikasi saat runtime sehingga kerangka kerja pengujian dapat dengan mudah mematikan atau mengolok-olok metode pada kelas yang ada. Ruby adalah salah satu bahasa seperti itu; rspec adalah kerangka uji utama untuk TDD.
Bagaimana pengujian alat bantu pengetikan dinamis
Dengan pengetikan dinamis, Anda dapat membuat objek tiruan dengan hanya membuat kelas yang memiliki antarmuka yang sama (tanda tangan metode) objek kolaborator yang Anda butuhkan untuk mengejek. Misalnya, Anda memiliki beberapa kelas yang mengirim pesan:
Katakanlah kita memiliki MessageSenderUser yang menggunakan instance MessageSender:
Perhatikan penggunaan injeksi ketergantungan di sini , pokok pengujian unit. Kami akan kembali ke sana.
Anda ingin menguji bahwa
MessageSenderUser#do_stuff
panggilan mengirim dua kali. Sama seperti yang Anda lakukan dalam bahasa yang diketik secara statis, Anda dapat membuat MessageSender tiruan yang menghitung berapa kalisend
dipanggil. Tetapi tidak seperti bahasa yang diketik secara statis, Anda tidak perlu kelas antarmuka. Anda tinggal lanjutkan dan buat itu:Dan gunakan dalam tes Anda:
Dengan sendirinya, "bebek mengetik" dari bahasa yang diketik secara dinamis tidak menambah banyak pengujian dibandingkan dengan bahasa yang diketik secara statis. Tetapi bagaimana jika kelas tidak ditutup, tetapi dapat dimodifikasi saat runtime? Itu adalah pengubah game. Mari kita lihat caranya.
Bagaimana jika Anda tidak harus menggunakan injeksi ketergantungan untuk membuat kelas dapat diuji?
Misalkan MessageSenderUser hanya akan menggunakan MessageSender untuk mengirim pesan, dan Anda tidak perlu mengizinkan substitusi MessageSender dengan kelas lain. Dalam satu program sering terjadi. Mari kita menulis ulang MessageSenderUser sehingga hanya membuat dan menggunakan MessageSender, tanpa suntikan ketergantungan.
MessageSenderUser sekarang lebih mudah digunakan: Tidak ada yang membuatnya perlu membuat MessageSender agar dapat digunakan. Ini tidak terlihat seperti peningkatan besar dalam contoh sederhana ini, tetapi sekarang bayangkan bahwa MessageSenderUser dibuat di lebih dari satu tempat, atau memiliki tiga dependensi. Sekarang sistem memiliki banyak sekali contoh yang melintas hanya untuk membuat unit test senang, bukan karena itu perlu meningkatkan desain sama sekali.
Kelas terbuka memungkinkan Anda menguji tanpa injeksi ketergantungan
Kerangka uji dalam bahasa dengan pengetikan dinamis dan kelas terbuka dapat membuat TDD cukup bagus. Berikut cuplikan kode dari tes rspec untuk MessageSenderUser:
Itu seluruh tes. Jika
MessageSenderUser#do_stuff
tidak memanggilMessageSender#send
tepat dua kali, tes ini gagal. Kelas MessageSender yang sebenarnya tidak pernah dipanggil: Kami memberi tahu tes bahwa setiap kali seseorang mencoba membuat MessageSender, mereka seharusnya mendapatkan MessageSender tiruan kita sebagai gantinya. Tidak diperlukan injeksi ketergantungan.Sangat menyenangkan untuk melakukan begitu banyak tes sederhana. Lebih baik tidak harus menggunakan injeksi ketergantungan kecuali jika itu benar-benar masuk akal untuk desain Anda.
Tapi apa hubungannya ini dengan kelas terbuka? Perhatikan panggilan ke
MessageSender.should_receive
. Kami tidak mendefinisikan #should_receive ketika kami menulis MessageSender, jadi siapa yang melakukannya? Jawabannya adalah bahwa kerangka kerja pengujian, membuat beberapa modifikasi hati-hati dari kelas sistem, dapat membuatnya muncul karena melalui #should_receive didefinisikan pada setiap objek. Jika Anda berpikir bahwa memodifikasi kelas sistem seperti itu membutuhkan perhatian, Anda benar. Tetapi ini adalah hal yang sempurna untuk apa yang dilakukan oleh perpustakaan tes di sini, dan kelas terbuka memungkinkannya.sumber
'bekerja dengan baik dengan TDD' tentu tidak cukup untuk menggambarkan suatu bahasa, sehingga bisa "terlihat" seperti apa pun. Gangguan, Prolog, C ++, Ruby, Python ... pilihlah.
Selain itu, tidak jelas bahwa mendukung TDD adalah sesuatu yang paling baik ditangani oleh bahasa itu sendiri. Tentu, Anda dapat membuat bahasa tempat setiap fungsi atau metode memiliki tes terkait, dan Anda dapat membangun dukungan untuk menemukan dan menjalankan tes tersebut. Tetapi kerangka pengujian unit sudah menangani bagian penemuan dan eksekusi dengan baik, dan sulit untuk melihat bagaimana menambahkan persyaratan pengujian untuk setiap fungsi secara bersih. Apakah tes juga perlu tes? Atau ada dua kelas fungsi - yang normal yang membutuhkan tes dan fungsi tes yang tidak membutuhkannya? Itu tidak terlihat sangat elegan.
Mungkin lebih baik untuk mendukung TDD dengan alat dan kerangka kerja. Bangun ke dalam IDE. Buat proses pengembangan yang mendorongnya.
Juga, jika Anda mendesain bahasa, ada baiknya berpikir jangka panjang. Ingatlah bahwa TDD hanyalah satu metodologi, dan bukan cara kerja yang disukai semua orang. Mungkin sulit untuk dibayangkan, tetapi mungkin saja cara yang lebih baik akan datang. Sebagai perancang bahasa, apakah Anda ingin orang-orang harus meninggalkan bahasa Anda ketika itu terjadi?
Yang dapat Anda benar-benar katakan untuk menjawab pertanyaan adalah bahwa bahasa seperti itu akan kondusif untuk pengujian. Saya tahu itu tidak banyak membantu, tapi saya pikir masalahnya ada pada pertanyaan.
sumber
Nah, bahasa yang diketik secara dinamis tidak memerlukan antarmuka eksplisit. Lihat Ruby atau PHP, dll.
Di sisi lain, bahasa yang diketik secara statis seperti Java dan C # atau C ++ memberlakukan jenis dan memaksa Anda untuk menulis antarmuka itu.
Yang tidak saya mengerti adalah apa masalah Anda dengan mereka. Antarmuka adalah elemen kunci dari desain dan mereka digunakan di seluruh pola desain dan dalam menghormati prinsip-prinsip SOLID. Saya, misalnya, sering menggunakan antarmuka dalam PHP karena mereka membuat desain eksplisit dan mereka juga menegakkan desain. Di sisi lain di Ruby Anda tidak memiliki cara untuk menegakkan suatu jenis, itu adalah bahasa yang diketik bebek. Tapi tetap saja, Anda harus membayangkan antarmuka di sana dan Anda harus mengabstraksi desain dalam pikiran Anda untuk mengimplementasikannya dengan benar.
Jadi, meskipun pertanyaan Anda mungkin terdengar menarik, itu menyiratkan bahwa Anda memiliki masalah dengan pemahaman atau menerapkan teknik injeksi ketergantungan.
Dan untuk langsung menjawab pertanyaan Anda, Ruby dan PHP memiliki infrastruktur mengejek yang baik, yang dibangun dalam kerangka pengujian unit dan dikirim secara terpisah (lihat Mockery untuk PHP). Dalam beberapa kasus, kerangka kerja ini bahkan memungkinkan Anda untuk melakukan apa yang Anda sarankan, hal-hal seperti mengejek panggilan statis atau inisialisasi objek tanpa menyuntikkan ketergantungan secara eksplisit.
sumber