Menentukan apa yang merupakan unit test yang berguna

47

Saya telah membaca dokumen phpunit dan menemukan kutipan berikut:

Anda selalu dapat menulis lebih banyak tes. Namun, Anda akan segera menemukan bahwa hanya sebagian kecil dari tes yang dapat Anda bayangkan yang benar-benar bermanfaat. Yang Anda inginkan adalah menulis tes yang gagal walaupun Anda pikir itu harus berhasil, atau tes yang berhasil meskipun Anda pikir itu harus gagal. Cara lain untuk memikirkannya adalah dari segi biaya / manfaat. Anda ingin menulis tes yang akan membayar Anda kembali dengan informasi. --Erich Gamma

Itu membuat saya bertanya-tanya. Bagaimana Anda menentukan apa yang membuat tes unit lebih bermanfaat daripada yang lain, selain dari apa yang dinyatakan dalam kutipan tentang biaya / manfaat. Bagaimana cara Anda memutuskan bagian kode mana yang Anda buat untuk pengujian unit? Saya meminta itu karena salah satu dari kutipan itu juga mengatakan:

Jadi jika ini bukan tentang pengujian, tentang apa? Ini tentang mencari tahu apa yang Anda coba lakukan sebelum Anda lari setengah matang untuk mencoba melakukannya. Anda menulis spesifikasi yang memakukan aspek kecil perilaku dalam bentuk yang ringkas, tidak ambigu, dan dapat dieksekusi. Sesederhana itu. Apakah itu berarti Anda menulis tes? Tidak. Itu berarti Anda menulis spesifikasi tentang apa yang harus dilakukan kode Anda. Itu berarti Anda menentukan perilaku kode Anda sebelumnya. Tapi tidak jauh sebelumnya. Sebenarnya, tepat sebelum Anda menulis kode adalah yang terbaik karena pada saat itulah Anda memiliki sebanyak mungkin informasi yang Anda miliki hingga saat itu. Seperti TDD yang dilakukan dengan baik, Anda bekerja dengan sedikit tambahan ... menentukan satu aspek kecil perilaku sekaligus, kemudian menerapkannya. Ketika Anda menyadari bahwa ini semua tentang menentukan perilaku dan bukan menulis tes, sudut pandang Anda bergeser. Tiba-tiba ide memiliki kelas Uji untuk masing-masing kelas produksi Anda sangat terbatas. Dan pemikiran menguji setiap metode Anda dengan metode pengujian sendiri (dalam hubungan 1-1) akan menggelikan. --Memiliki Astels

Bagian penting dari itu adalah

* Dan pemikiran menguji setiap metode Anda dengan metode pengujiannya sendiri (dalam hubungan 1-1) akan menggelikan. *

Jadi, jika membuat tes untuk setiap metode adalah 'laughable', bagaimana / kapan Anda memilih untuk apa Anda menulis tes?

zcourts
sumber
5
Ini pertanyaan yang bagus, tapi saya rasa ini adalah pertanyaan independen dalam bahasa pemrograman - mengapa Anda memberi tag php?
Berikut adalah beberapa artikel yang sangat bagus yang memberi saya arahan yang baik pada tes unit apa yang harus saya tulis dan bidang mana yang penting untuk menyediakan tes otomatis. - blog.stevensanderson.com/2009/11/04/… - blog.stevensanderson.com/2009/08/24/… - ayende.com/blog/4218/scenario-driven-tests
Kane

Jawaban:

27

Berapa banyak tes per metode?

Yah maksimum teoritis dan sangat tidak praktis adalah kompleksitas N-Path (anggap semua tes mencakup cara yang berbeda melalui kode;)). Minimal adalah SATU! Per metode publik yaitu, ia tidak menguji detail implementasi, hanya perilaku eksternal suatu kelas (mengembalikan nilai & memanggil objek lain).

Anda mengutip:

* Dan pemikiran menguji setiap metode Anda dengan metode pengujiannya sendiri (dalam hubungan 1-1) akan menggelikan. *

dan kemudian bertanya:

Jadi, jika membuat tes untuk setiap metode adalah 'laughable', bagaimana / kapan Anda memilih untuk apa Anda menulis tes?

Tapi saya pikir Anda salah paham tentang penulis di sini:

Gagasan memiliki one test methodper one method in the class to testadalah apa yang penulis sebut "menggelikan".

(Setidaknya bagi saya) Ini bukan tentang 'kurang' ini tentang 'lebih banyak'

Jadi izinkan saya ulangi seperti saya memahaminya:

Dan pemikiran menguji setiap metode Anda dengan HANYA SATU METODE (metode pengujian sendiri dalam hubungan 1-1) akan menggelikan.

Mengutip penawaran Anda lagi:

Ketika Anda menyadari bahwa ini semua tentang menentukan perilaku dan bukan menulis tes, sudut pandang Anda bergeser.


Ketika Anda berlatih TDD Anda tidak berpikir :

Saya punya metode calculateX($a, $b);dan perlu tes testCalculcateXyang menguji SEMUA tentang metode ini.

Apa yang dikatakan TDD kepada Anda adalah untuk berpikir tentang apa kode Anda HARUS LAKUKAN :

Saya perlu menghitung lebih besar dari dua nilai ( test case pertama! ) Tetapi jika $ a lebih kecil dari nol maka ia harus menghasilkan kesalahan ( case test kedua! ) Dan jika $ b lebih kecil dari nol seharusnya .... ( test case ketiga! ) dan seterusnya.


Anda ingin menguji perilaku, bukan hanya metode tunggal tanpa konteks.

Dengan begitu Anda mendapatkan test suite yang merupakan dokumentasi untuk kode Anda dan BENAR-BENAR menjelaskan apa yang diharapkan untuk dilakukan, mungkin bahkan mengapa :)


Bagaimana cara Anda memutuskan bagian kode mana yang Anda buat untuk pengujian unit?

Ya, segala sesuatu yang berakhir di repositori atau di dekat produksi memerlukan tes. Saya tidak berpikir penulis kutipan Anda tidak akan setuju dengan itu ketika saya mencoba untuk menyatakan di atas.

Jika Anda tidak memiliki tes untuk itu akan lebih sulit (lebih mahal) untuk mengubah kode, terutama jika bukan Anda yang membuat perubahan.

TDD adalah cara untuk memastikan bahwa Anda memiliki tes untuk SEMUA tetapi selama Anda MENULIS tes itu baik-baik saja. Biasanya menulisnya pada hari yang sama membantu karena Anda tidak akan melakukannya nanti, bukan? :)



Tanggapan terhadap komentar:

sejumlah metode yang layak tidak dapat diuji dalam konteks tertentu karena mereka tergantung atau tergantung pada metode lain

Ada tiga hal yang bisa disebut metode ini:

Metode publik dari kelas lain

Kita dapat mengejek kelas lain sehingga kita telah menetapkan status di sana. Kami mengendalikan konteks sehingga tidak masalah di sana.

* Metode yang dilindungi atau pribadi pada yang sama *

Apa pun yang bukan bagian dari API umum suatu kelas tidak diuji secara langsung, biasanya.

Anda ingin menguji perilaku dan bukan implementasi dan jika kelas melakukan semua itu berfungsi dalam satu metode publik besar atau dalam banyak metode yang lebih kecil yang dilindungi disebut implementasi . Anda ingin dapat MENGUBAH metode yang dilindungi tersebut tanpa menyentuh tes Anda. Karena tes Anda akan rusak jika kode Anda berubah perilaku! Untuk itulah tes Anda, untuk memberi tahu Anda ketika Anda memecahkan sesuatu :)

Metode publik di kelas yang sama

Itu tidak sering terjadi bukan? Dan jika memang suka dalam contoh berikut ada beberapa cara untuk menangani ini:

$stuff = new Stuff();
$stuff->setBla(12);
$stuff->setFoo(14);
$stuff->execute(); 

Bahwa setter ada dan bukan bagian dari metode eksekusi tanda tangan adalah topik lain;)

Apa yang bisa kita uji di sini adalah jika eksekusi tidak meledak ketika kita menetapkan nilai yang salah. Itu setBlamelempar pengecualian ketika Anda melewatkan string dapat diuji secara terpisah, tetapi jika kami ingin menguji bahwa kedua nilai yang diizinkan (12 & 14) tidak bekerja BERSAMA (untuk alasan apa pun) daripada satu kasus uji.

Jika Anda menginginkan test suite "baik", Anda dapat, di php, mungkin (!) Menambahkan @covers Stuff::executeanotasi untuk memastikan Anda hanya menghasilkan cakupan kode untuk metode ini dan hal-hal lain yang baru saja setup perlu diuji secara terpisah (sekali lagi, jika kamu menginginkan itu).

Jadi intinya adalah: Mungkin Anda harus membuat beberapa dunia di sekitarnya terlebih dahulu tetapi Anda harus dapat menulis kasus uji yang bermakna yang biasanya hanya menjangkau satu atau mungkin dua fungsi nyata (setter tidak dihitung di sini). Sisanya dapat diejek atau di tes terlebih dahulu dan kemudian diandalkan (lihat @depends)


* Catatan: Pertanyaannya dimigrasikan dari SO dan awalnya tentang PHP / PHPUnit, itu sebabnya contoh kode dan referensi dari dunia php, saya pikir ini juga berlaku untuk bahasa lain karena phpunit tidak berbeda jauh dari yang lain xUnit kerangka kerja pengujian.

pendidik
sumber
Sangat terperinci dan informatif ... Anda berkata, "Anda ingin menguji perilaku, bukan hanya metode tunggal tanpa konteks." , oleh karena itu kondisi pengujian yang berguna hanya akan kontekstual jika tanggungan sedang diuji juga? Atau saya salah menafsirkan apa yang Anda maksud>
zcourts
@ robinsonc494 Saya akan mengedit dalam contoh yang mungkin menjelaskan sedikit lebih baik di mana saya akan pergi dengan ini
edorian
terima kasih untuk suntingan dan contohnya, ini pasti membantu. Saya pikir kebingungan saya (jika Anda bisa menyebutnya begitu) adalah bahwa meskipun saya membaca tentang pengujian untuk "perilaku", entah bagaimana saya secara inheren (mungkin?) Memikirkan kasus uji yang berfokus pada implementasi.
zcourts
@ robinsonc494 Mungkin berpikir seperti ini: Jika Anda memukul seseorang yang ia eter balas, panggil polisi atau lari. Perilaku itu. Itu yang dilakukan Orang. Fakta bahwa ia menggunakan kerang yang dipicu oleh sedikit muatan listrik dari otaknya adalah implementasi. Jika Anda ingin menguji reaksi seseorang, Anda memukulnya dan melihat apakah dia bertindak seperti yang Anda harapkan. Anda tidak menempatkannya di pemindai otak dan melihat apakah impuls dikirim ke kerang. Beberapa masuk untuk kelas cukup banyak;)
edorian
3
Saya pikir contoh terbaik yang saya lihat dari TDD yang benar-benar membantunya mengklik cara menulis kasus uji adalah Game Bowling Kata oleh Paman Bob Martin. slideshare.net/lalitkale/bowling-game-kata-by-robert-c-martin
Amy Anuszewski
2

Pengujian dan Pengujian Unit bukan hal yang sama. Unit Testing adalah bagian yang sangat penting dan menarik dari Pengujian secara keseluruhan. Saya akan mengklaim bahwa fokus Unit Testing membuat kami berpikir tentang pengujian semacam ini dengan cara yang agak bertentangan dengan kutipan di atas.

Pertama, jika kita mengikuti TDD atau bahkan DTH (yaitu mengembangkan dan menguji dengan harmonis) kita menggunakan tes yang kita tulis sebagai fokus untuk memperbaiki desain kita. Dengan memikirkan kasus sudut dan menulis tes sesuai dengan itu kami menghindari bug masuk di tempat pertama, jadi sebenarnya kami menulis tes yang kami harapkan lulus (OK tepat pada awal TDD mereka gagal, tapi itu hanya artefak pemesanan, ketika kode selesai kami harapkan mereka lulus, dan sebagian besar melakukannya, karena Anda sudah memikirkan kode tersebut.

Kedua, Tes Unit benar-benar menjadi milik mereka saat kami melakukan refactor. Kami mengubah implementasi kami tetapi mengharapkan jawaban tetap sama - Uji Unit adalah perlindungan kami terhadap melanggar kontrak antarmuka kami. Jadi satu lagi kami berharap tes lulus.

Ini menyiratkan bahwa untuk antarmuka publik kami, yang mungkin stabil, kami membutuhkan keterlacakan yang jelas sehingga kami dapat melihat bahwa setiap metode publik diuji.

Untuk menjawab pertanyaan eksplisit Anda: Tes Unit untuk antarmuka publik memiliki nilai.

diedit dalam komentar tanggapan:

Menguji metode pribadi? Ya, kita harus, tetapi jika kita tidak harus menguji sesuatu maka di situlah saya berkompromi. Lagipula jika metode publik berfungsi maka bisakah bug-bug itu menjadi barang pribadi begitu penting? Secara pragmatis, churn cenderung terjadi pada hal-hal pribadi, Anda bekerja keras untuk mempertahankan antarmuka publik Anda, tetapi jika hal-hal yang Anda bergantung pada perubahan hal-hal pribadi dapat berubah. Pada titik tertentu kita mungkin menemukan mempertahankan tes internal adalah banyak usaha. Apakah upaya itu dihabiskan dengan baik?

djna
sumber
Jadi hanya untuk memastikan saya mengerti apa yang Anda katakan: Saat menguji, fokus pada pengujian antarmuka publik, kan? Dengan anggapan itu benar ... tidakkah ada kemungkinan yang lebih besar untuk meninggalkan bug dalam metode / antarmuka pribadi yang tidak Anda lakukan pengujian unit, beberapa bug rumit di antarmuka "pribadi" yang belum diuji mungkin dapat menyebabkan kelulusan uji ketika seharusnya benar-benar gagal. Apakah saya salah dalam berpikir demikian?
zcourts
Menggunakan cakupan kode, Anda dapat mengetahui kapan kode dalam metode pribadi Anda tidak dijalankan saat menguji metode publik Anda. Jika metode publik Anda sepenuhnya dicakup, metode pribadi yang terbuka jelas tidak digunakan dan dapat dihapus. Jika tidak, Anda perlu lebih banyak tes untuk metode publik Anda.
David Harkness
2

Tes unit harus menjadi bagian dari strategi pengujian yang lebih besar. Saya mengikuti prinsip-prinsip ini dalam memilih jenis tes apa yang akan ditulis dan kapan:

  • Fokus pada penulisan tes ujung ke ujung. Anda mencakup lebih banyak kode per tes daripada dengan unit test dan karenanya mendapatkan lebih banyak pengujian untuk hasil Jadikan ini validasi otomatis roti dan mentega sistem Anda secara keseluruhan.

  • Drop down untuk menulis tes unit di sekitar nugget logika yang rumit. Tes unit bernilai berat dalam situasi di mana tes ujung ke ujung akan sulit untuk di-debug atau sulit untuk ditulis untuk cakupan kode yang memadai.

  • Tunggu hingga API yang Anda uji stabil untuk menulis kedua jenis tes. Anda ingin menghindari refactor implementasi dan pengujian Anda.

Rob Ashton memiliki artikel bagus tentang topik ini, yang saya banyak manfaatkan untuk mengartikulasikan prinsip-prinsip di atas.

Edward Brey
sumber
+1 - Saya tidak harus setuju dengan semua poin Anda (atau poin artikel) tetapi jika saya setuju adalah bahwa sebagian besar unit test tidak berguna jika dilakukan secara membabi buta (pendekatan TDD). Namun, mereka sangat berharga jika Anda cerdas dalam memutuskan apa yang layak menghabiskan waktu melakukan tes unit. Saya setuju sepenuhnya bahwa Anda mendapatkan jauh, jauh lebih baik untuk secara tertulis tes tingkat yang lebih tinggi, khususnya pengujian tingkat subsistem otomatis. Masalah dengan tes otomatis end-to-end adalah bahwa mereka akan sulit, jika tidak sepenuhnya tidak praktis, untuk suatu sistem jika memiliki ukuran / kompleksitas.
Dunk
0

Saya cenderung mengikuti pendekatan berbeda untuk pengujian unit yang tampaknya bekerja dengan baik. Alih-alih menganggap unit test sebagai "Menguji beberapa perilaku" saya lebih menganggapnya sebagai "spesifikasi yang harus diikuti oleh kode saya". Dengan cara ini, Anda pada dasarnya dapat menyatakan bahwa suatu objek harus berperilaku dengan cara tertentu, dan mengingat bahwa Anda berasumsi bahwa di tempat lain dalam program Anda, Anda dapat yakin itu relatif bebas bug.

Jika Anda menulis API publik, ini sangat berharga. Namun, Anda akan selalu memerlukan dosis yang baik dari tes integrasi end-to-end juga karena mendekati cakupan tes unit 100% biasanya tidak layak dan akan kehilangan hal-hal yang kebanyakan akan dianggap "tidak dapat diuji" oleh metode pengujian unit (mengejek, dll)

Earlz
sumber