Unlimited Plugins, WordPress themes, videos & courses! Unlimited asset downloads! From $16.50/m
Advertisement
  1. Code
  2. PHP

Pengujian lebih mudah dengan Mockery

by
Read Time:17 minsLanguages:

Indonesian (Bahasa Indonesia) translation by Suci Rohini (you can also view the original English article)

Ini adalah sebuah kebenaran yang tidak menguntungkan bahwa, sementara prinsip dasar di balik pengujian cukup sederhana, sepenuhnya memperkenalkan proses ini ke dalam alur kerja koding sehari-hari Anda lebih sulit daripada yang Anda harapkan. Berbagai jargon saja bisa membuktikan luar biasa! Untungnya, Anda memiliki berbagai alat dibelakangmu, dan membantu membuat prosesnya sesederhana mungkin. Mockery, kerangka objek tiruan utama untuk PHP, adalah salah satu alat seperti itu!

Dalam artikel ini, kita akan menggali apa yang mengejek, mengapa itu berguna, dan bagaimana mengintegrasikan Mockery ke dalam alur kerja pengujian Anda.


Mocking Decoded

Objek tiruan tidak lebih dari sedikit jargon tes yang mengacu pada simulasi perilaku objek nyata. Dalam istilah yang lebih sederhana, sering, ketika menguji, Anda tidak akan ingin menjalankan metode tertentu. Sebaliknya, Anda hanya perlu memastikan bahwa itu sebenarnya disebut.

Mungkin contohnya ada dalam urutan. Bayangkan bahwa kode Anda memicu metode yang akan mencatat sedikit data ke file. Saat menguji logika ini, Anda tentu tidak ingin secara fisik menyentuh sistem file. Ini memiliki potensi untuk secara drastis menurunkan kecepatan tes Anda. Dalam situasi ini, lebih baik untuk meniru kelas sistem file Anda, dan, daripada secara manual membaca file untuk membuktikan bahwa itu diperbarui, hanya memastikan bahwa metode yang berlaku di kelas itu, pada kenyataannya. Inilah Mocking! Tidak ada yang lebih dari itu; mensimulasikan perilaku objek.

Ingat: jargon hanyalah jargon. Jangan pernah membiarkan potongan terminologi yang membingungkan untuk menghalangi Anda mempelajari keterampilan baru.

Terutama ketika proses pengembangan Anda matang - termasuk merangkul prinsip tanggung jawab tunggal dan memanfaatkan injeksi ketergantungan - keakraban dengan ejekan akan menjadi sangat penting.

Mocks vs. Stubs: Peluang sangat tinggi sehingga Anda sering mendengar istilah, mock dan stub, dilontarkan secara bergantian Bahkan, keduanya melayani tujuan yang berbeda. Yang pertama mengacu pada proses mendefinisikan harapan dan memastikan perilaku yang diinginkan. Dengan kata lain, sebuah tiruan berpotensi menyebabkan tes gagal. Sebuah rintisan, di sisi lain, hanyalah kumpulan data bodoh yang dapat diedarkan untuk memenuhi kriteria tertentu.

Perpustakaan pengujian defacto untuk PHP, PHPUnit, kapal dengan API sendiri untuk objek mengejek; Namun, sayangnya, itu dapat membuktikan rumit untuk bekerja dengan. Seperti yang Anda ketahui, pengujian yang lebih sulit adalah, semakin besar kemungkinan pengembangnya (dan sayangnya) tidak akan melakukannya.

Untungnya, berbagai solusi pihak ketiga tersedia melalui Packagist (repositori paket Komposer), yang memungkinkan peningkatan keterbacaan, dan, yang lebih penting, kemampuan menulis. Di antara solusi ini - dan yang paling penting dari set - adalah Mockery, framework agnostik tiruan.

Dirancang sebagai alternatif drop-in bagi mereka yang kewalahan oleh verbositas mengejek PHPUnit, Mockery adalah utilitas sederhana, namun kuat. Seperti yang Anda pasti akan temukan, pada kenyataannya, itu adalah standar industri untuk pengembangan PHP modern.


Instalasi

Seperti kebanyakan alat PHP modern, Mockery dapat diinstal dengan Composer.

Seperti kebanyakan alat PHP hari ini, metode yang disarankan untuk menginstal Mockery adalah melalui Komposer (meskipun tersedia melalui Pear juga).

Tunggu, apa hal Composer ini? Ini adalah alat yang disukai komunitas PHP untuk manajemen ketergantungan. Ini menyediakan cara mudah untuk mendeklarasikan dependensi suatu proyek, dan menarik mereka dengan satu perintah. Sebagai pengembang PHP modern, penting bagi Anda untuk memiliki pemahaman dasar tentang apa itu Composer, dan bagaimana menggunakannya.

Jika bekerja bersama, untuk tujuan pembelajaran, tambahkan file composer.json baru ke proyek kosong dan tambahkan:

Bit JSON ini menetapkan bahwa, untuk pengembangan, aplikasi Anda membutuhkan pustaka Mockery. Dari baris perintah, composer install --dev akan menarik paket.

Sebagai bonus tambahan, Composer mengirim dengan autoloader sendiri secara gratis! Entah tentukan classmap direktori dan pembuat composer dump-autoload, atau ikuti standar PSR-0 dan sesuaikan struktur direktori Anda agar cocok. Lihat Nettuts+ untuk mempelajari lebih lanjut. Jika Anda masih secara manual membutuhkan file yang tak terhitung jumlahnya dalam setiap file PHP, baik, Anda mungkin saja salah melakukannya.


Dilema

Sebelum kita dapat menerapkan solusi, sebaiknya tinjau masalah terlebih dahulu. Bayangkan bahwa Anda perlu menerapkan sistem untuk menangani proses menghasilkan konten dan menulisnya ke file. Mungkin generator mengkompilasi berbagai data, baik dari file bertopik lokal, atau layanan web, dan kemudian data itu ditulis ke sistem file.

Jika mengikuti prinsip tanggung jawab tunggal - yang menyatakan bahwa setiap kelas harus bertanggung jawab untuk satu hal - maka masuk akal bahwa kita harus membagi logika ini menjadi dua kelas: satu untuk menghasilkan konten yang diperlukan, dan yang lain untuk secara fisik menulis data ke file. Kelas Generator dan File, masing-masing, harus melakukan trik.

Tips: Mengapa tidak menggunakan file_put_contents langsung dari kelas Generator? Nah, tanyakan pada diri sendiri: "Bagaimana saya bisa menguji ini?" Ada beberapa teknik, seperti monkey patching, yang memungkinkan Anda untuk membebani hal-hal semacam ini, tetapi, sebagai praktik terbaik, lebih baik membungkus fungsionalitas seperti itu, sehingga mudah meniru dengan tool, seperti Mockery!

Berikut ini struktur dasar (dengan dosis kode pseudo yang sehat) untuk kelas Generator kita.

Injeksi Ketergantungan

Kode ini memanfaatkan apa yang disebut sebagai injeksi ketergantungan. Sekali lagi, ini hanyalah jargon pengembang untuk menyuntikkan dependensi kelas melalui metode konstruktornya, daripada meng-coding-nya.

Mengapa ini bermanfaat? Karena, jika tidak, kita tidak akan bisa meniru kelas File! Tentu, kita dapat meniru kelas File, tetapi jika instantiasinya dikodekan ke kelas yang kita uji, tidak ada cara mudah untuk mengganti instance dengan versi yang ditiru.

Cara terbaik untuk membangun aplikasi yang dapat diuji adalah mendekati setiap metode panggilan baru dengan pertanyaan, "Bagaimana saya bisa menguji ini?" Meskipun ada trik untuk mengatasi hard-coding ini, melakukannya secara luas dianggap sebagai praktik yang buruk. Sebaliknya, selalu suntikkan dependensi kelas melalui konstruktor, atau melalui setter injection.

Setter injection lebih atau kurang identik dengan injeksi konstruktor. Prinsipnya persis sama; satu-satunya perbedaan adalah bahwa, agak menyuntikkan dependensi kelas melalui metode konstruktor, mereka malah melakukannya melalui metode setter, seperti:

Kritik umum injeksi ketergantungan adalah bahwa ia memperkenalkan kerumitan tambahan ke dalam aplikasi, semua demi membuatnya lebih dapat diuji. Meskipun argumen kompleksitas masih bisa diperdebatkan dalam pendapat penulis ini, jika Anda lebih suka, Anda dapat mengizinkan injeksi ketergantungan, sambil tetap menentukan default fallback. Ini contohnya:

Sekarang, jika instance File dilewatkan ke konstruktor, objek itu akan digunakan di kelas. Di sisi lain, jika tidak ada yang terlewatkan, Generator akan turun kembali ke manual instantiating kelas yang berlaku. Ini memungkinkan variasi seperti:

Selanjutnya, untuk keperluan tutorial ini, kelas File tidak lebih dari pembungkus sederhana di sekitar fungsi file_put_contents PHP.

Agak sederhana, ya? Mari menulis tes untuk melihat, langsung, apa masalahnya.

Harap perhatikan bahwa contoh-contoh ini mengasumsikan bahwa kelas yang diperlukan sedang dimuat secara otomatis dengan Composer. File composer.json Anda secara opsional menerima objek autoload, di mana Anda dapat menentukan direktori atau kelas mana yang akan di-autoload. Tidak perlu lagi require yang berantakan!

Jika bekerja, jalankan phpunit akan kembali:

Itu hijau; itu berarti kita dapat melanjutkan ke tugas berikutnya, bukan? Yah, tidak persis. Meskipun benar bahwa kode memang bekerja, setiap kali tes ini dijalankan, file foo.txt akan dibuat pada sistem file. Bagaimana kalau Anda sudah menulis lusinan tes lagi? Seperti yang Anda bayangkan, dengan sangat cepat, kecepatan eksekusi tes Anda akan gagap.

Meskipun tes lulus, mereka salah menyentuh sistem file.

Masih belum yakin? Jika kecepatan penurunan pengujian tidak bergoyang Anda, kemudian mempertimbangkan akal. Pikirkan tentang hal ini: kita sedang menguji kelas Generator; mengapa kita tertarik untuk mengeksekusi kode dari kelas File? Itu harus memiliki tes sendiri! Kenapa sih harus digandakan?


Solusi

Semoga bagian sebelumnya memberikan ilustrasi yang sempurna untuk mengapa ejekan itu penting. Seperti yang telah disebutkan sebelumnya, meskipun kita dapat menggunakan API asli PHPUnit untuk melayani persyaratan mengejek, itu tidak terlalu menyenangkan untuk dikerjakan. Untuk mengilustrasikan kebenaran ini, berikut ini contoh untuk menegaskan bahwa objek yang diejek harus menerima metode, getName, dan mengembalikan John Doe.

Sementara itu mendapat pekerjaan yang dilakukan - menegaskan bahwa metode getName dipanggil sekali, dan mengembalikan John Doe - implementasi PHPUnit membingungkan dan verbose. Dengan Mockery, kita dapat meningkatkan pembacaannya secara drastis.

Perhatikan bagaimana contoh terakhir membaca (dan berbicara) lebih baik.

Dilanjutkan dengan contoh dari bagian "Dilema sebelumnya", kali ini, dalam kelas GeneratorTest, mari kita lakukan sebagai gurauan - atau simulasikan perilaku - kelas File dengan Mockery. Ini kode yang diperbarui:

Bingung dengan referensi Mockery::close() dalam metode teardown? Panggilan statis ini membersihkan wadah Mockery yang digunakan oleh pengujian saat ini, dan menjalankan semua tugas verifikasi yang diperlukan untuk harapan Anda.

Kelas dapat diejek menggunakan metode Mockery::mock() yang dapat dibaca. Selanjutnya, Anda biasanya perlu menentukan metode mana pada objek tiruan yang Anda harapkan untuk dipanggil, bersama dengan argumen yang berlaku. Ini dapat dicapai, melalui metode shouldReceive(METHOD) dan with(ARG).

Dalam hal ini, ketika kita memanggil $generate-> fire(), kita menyatakan bahwa ia harus put metode put pada instance File, dan mengirimnya path, foo.txt, dan data, foo bar.

Karena kita menggunakan injeksi ketergantungan, sekarang mudah untuk menyuntikkan objek File yang diejek.

Jika kita menjalankan tes lagi, mereka masih akan kembali hijau, namun, kelas File - dan, akibatnya, sistem file - tidak akan pernah tersentuh! Sekali lagi, tidak perlu menyentuh File. Itu harus memiliki tes sendiri! Mocking untuk kemenangan!

Objek Mock Sederhana

Benda-benda tiruan tidak perlu selalu merujuk kelas. Jika Anda hanya membutuhkan objek sederhana, mungkin untuk pengguna, Anda mungkin mengirimkan array ke metode mock - di mana, untuk setiap item, kunci dan nilai sesuai dengan nama metode dan nilai pengembalian, masing-masing.

Kembali Nilai Dari Metode Mengejek

Pasti ada saatnya, ketika metode kelas yang diejek perlu mengembalikan nilai. Melanjutkan dengan contoh Generator/File kita, bagaimana jika kita perlu memastikan bahwa, jika file sudah ada, seharusnya tidak ditimpa? Bagaimana kita bisa mencapainya?

Kuncinya adalah menggunakan metode andReturn() pada objek yang diejek untuk mensimulasikan status yang berbeda. Berikut contoh yang diperbarui:

Kode yang diperbarui ini sekarang menegaskan bahwa metode exists harus dipicu pada kelas File yang diejek, dan seharusnya, untuk tujuan jalur tes ini, mengembalikan true, menandakan bahwa file sudah ada dan tidak seharusnya ditimpa. Kami selanjutnya memastikan bahwa, dalam situasi seperti ini, metode put pada kelas File tidak pernah dipicu. Dengan Mockery, ini mudah, berkat harapan never().

Haruskah kita menjalankan tes lagi, kesalahan akan dikembalikan:

Aha; jadi tes ini diharapkan bahwa $this->file->exists() harus dipanggil, tetapi itu tidak pernah terjadi. Dengan demikian, gagal. Mari perbaiki!

Hanya itu saja! Kita tidak hanya mengikuti siklus TDD (pengembangan uji coba), tetapi tes kembali ke hijau!

Penting untuk diingat bahwa gaya pengujian ini hanya efektif jika Anda benar-benar menguji dependensi kelas Anda juga! Jika tidak, meskipun tes mungkin menunjukkan hijau, untuk produksi, kode akan rusak. Demo kita sejauh ini hanya memastikan Generator bekerja seperti yang diharapkan. Jangan lupa untuk menguji File juga!


Ekspektasi

Mari kita gali sedikit lebih jauh ke dalam deklarasi pengharapan Mockery. Anda sudah akrab dengan shouldReceive. Hati-hati dengan ini, meskipun; namanya agak menyesatkan. Ketika dibiarkan sendiri, itu tidak mengharuskan metode tersebut harus dipicu; default adalah nol atau lebih kali (zeroOrMoreTimes()). Untuk menegaskan bahwa Anda memerlukan metode yang akan dipanggil satu kali, atau berpotensi lebih banyak waktu, sejumlah opsi tersedia:

Akan ada saat-saat ketika kendala tambahan diperlukan. Seperti yang ditunjukkan sebelumnya, ini dapat sangat membantu ketika Anda perlu memastikan bahwa metode tertentu dipicu oleh argumen yang diperlukan. Penting untuk diingat bahwa harapan hanya akan berlaku jika metode dipanggil dengan argumen yang tepat ini.

Inilah beberapa contoh.

Ini dapat diperpanjang lebih jauh lagi untuk memungkinkan nilai argumen menjadi dinamis, selama mereka memenuhi kriteria tertentu. Mungkin kita hanya ingin memastikan bahwa string dilewatkan ke metode:

Atau, mungkin argumennya harus sesuai dengan ekspresi reguler. Mari kita menegaskan bahwa setiap nama file yang diakhiri dengan .txt harus dicocokkan.

Dan sebagai contoh akhir (tetapi tidak terbatas pada), mari kita izinkan untuk array dari nilai yang dapat diterima, menggunakan penunjuk anyOf apa pun.

Dengan kode ini, harapan hanya akan berlaku jika argumen pertama untuk metode get adalah log.txt atau cache.txt. Jika tidak, pengecualian Mockery akan dibuang ketika tes dijalankan.

Tip: Jangan lupa, Anda selalu dapat alias Mockery sebagai m di bagian atas kelas Anda untuk membuat hal-hal menjadi sedikit lebih ringkas: gunakan Mockery as m;. Ini memungkinkan untuk lebih ringkas, m: :mock().

Terakhir, kita memiliki berbagai opsi untuk menentukan apa yang harus dilakukan atau dikembalikan oleh metode yang ditiru. Mungkin kita hanya perlu mengembalikan boolean. Mudah:


Mock Parsial

Anda mungkin menemukan bahwa ada situasi ketika Anda hanya perlu tiruan satu metode, daripada seluruh objek. Mari kita bayangkan, untuk keperluan contoh ini, bahwa di kelas metode Anda mereferensikan fungsi global khusus (gasp) untuk mengambil nilai dari file konfigurasi.

Meskipun ada beberapa teknik berbeda untuk meniru fungsi global. Meskipun demikian, sebaiknya menghindari metode ini memanggil semua bersama-sama. Ini tepatnya ketika sebagian tiruan ikut bermain.

Perhatikan bagaimana kita menempatkan metode untuk meniru di dalam kurung. Jika Anda memiliki beberapa metode, cukup pisahkan dengan koma, seperti:

Dengan teknik ini, sisa metode pada objek akan memicu dan berperilaku seperti biasanya. Perlu diingat bahwa Anda harus selalu mendeklarasikan perilaku metode yang ditiru, seperti yang dilakukan di atas. Dalam hal ini, ketika getOption dipanggil, daripada mengeksekusi kode di dalamnya, kita cukup mengembalikan 10000.

Pilihan alternatif adalah memanfaatkan penggunaan pengandaian parsial parsial, yang dapat Anda anggap sebagai pengaturan status default untuk objek tiruan: semua metode tunduk pada kelas induk utama, kecuali ada harapan yang ditentukan.

Potongan kode sebelumnya dapat ditulis ulang sebagai:

Dalam contoh ini, semua metode pada MyClass akan berperilaku seperti yang mereka biasanya akan, termasuk getOption, yang akan mengejek dan kembali 10000'.


Hamcrest

Pustaka Hamcrest menyediakan seperangkat penemunya tambahan untuk mendefinisikan harapan.

Setelah Anda membiasakan diri dengan API Mockery, disarankan agar Anda juga memanfaatkan perpustakaan Hamcrest, yang menyediakan seperangkat penemunya tambahan untuk mendefinisikan harapan yang dapat dibaca. Seperti Mockery, itu dapat dipasang melalui Composer.

Setelah terinstal, Anda dapat menggunakan notasi yang lebih mudah dibaca manusia untuk menentukan tes Anda. Berikut ini beberapa contoh, termasuk sedikit variasi yang mencapai hasil akhir yang sama.

Perhatikan bagaimana Hamcrest memungkinkan Anda menulis pernyataan Anda sebagai sesuatu yang mudah dibaca atau terselip seperti yang Anda inginkan. Penggunaan fungsi is() tidak lebih dari gula sintaksis untuk membantu keterbacaan.

Anda akan merasa bahwa Mockery sangat cocok dengan Hamcrest. Misalnya, dengan Mockery sendiri, untuk menetapkan bahwa metode yang diejek harus dipanggil dengan satu argumen tipe, string, Anda dapat menulis:

Jika menggunakan Hamcrest, Mockery::type dapat diganti dengan stringValue(), seperti:

Hamcrest mengikuti konvensi penamaan resourceValue untuk mencocokkan jenis nilai.

  • nullValue
  • integerValue
  • arrayValue
  • rinse and repeat

Atau, untuk mencocokkan argumen apa pun, Mockery::any() dapat menjadi anything().


Ringkasan

Hambatan terbesar untuk menggunakan Mockery adalah, ironisnya, bukan API, itu sendiri.

Rintangan terbesar untuk menggunakan Mockery adalah, ironisnya, bukan API, itu sendiri, tetapi memahami mengapa dan kapan menggunakan tiruan dalam pengujian Anda.

Kuncinya adalah belajar dan menghormati prinsip tanggung jawab tunggal dalam alur kerja pengkodean Anda. Diciptakan oleh Bob Martin, SRP mendikte bahwa kelas "should have one, and only one, reason to change." Dengan kata lain, kelas tidak perlu diperbarui untuk menanggapi beberapa perubahan yang tidak terkait dengan aplikasi Anda, seperti memodifikasi logika bisnis, atau bagaimana output diformat, atau bagaimana data dapat bertahan. Dalam bentuknya yang paling sederhana, sama seperti metode, kelas harus melakukan satu hal.

Kelas File mengelola interaksi sistem file. Sebuah repositori MysqlDb tetap ada data. Kelas Email mempersiapkan dan mengirim email. Perhatikan bagaimana, tidak satu pun dari contoh ini adalah kata, dan, digunakan.

Setelah ini dipahami, pengujian menjadi jauh lebih mudah. Ketergantungan injeksi harus digunakan untuk semua operasi yang tidak jatuh di bawah umbrella kelas. Saat menguji, fokus pada satu kelas pada satu waktu, dan tirukan semua dependensinya. Anda tidak tertarik untuk menguji mereka; mereka punya tes sendiri!

Meskipun tidak ada yang menghalangi Anda untuk menggunakan implementasi mengejek asli PHPUnit, mengapa repot-repot ketika Mockery meningkatkan keterbacaan hanya composer update?

Advertisement
Advertisement
Looking for something to help kick start your next project?
Envato Market has a range of items for sale to help get you started.