Merancang tes unit untuk sistem stateful

20

Latar Belakang

Pengembangan Test Driven dipopulerkan setelah saya selesai sekolah dan di industri. Saya mencoba mempelajarinya, tetapi beberapa hal utama masih luput dari saya. Para pendukung TDD mengatakan banyak hal seperti (selanjutnya disebut sebagai "prinsip pernyataan tunggal" atau SAP ):

Untuk beberapa waktu saya telah memikirkan tentang bagaimana tes TDD dapat sesederhana, seekspresif, dan seanggun mungkin. Artikel ini mengeksplorasi sedikit tentang bagaimana rasanya membuat tes sesederhana dan diurai sebanyak mungkin: bertujuan untuk pernyataan tunggal dalam setiap tes.

Sumber: http://www.artima.com/weblogs/viewpost.jsp?thread=35578

Mereka juga mengatakan hal-hal seperti ini (selanjutnya disebut sebagai "prinsip metode pribadi" atau PMP ):

Anda biasanya tidak menguji metode pribadi secara langsung. Karena bersifat pribadi, anggap mereka sebagai detail implementasi. Tidak ada yang akan memanggil salah satu dari mereka dan mengharapkannya bekerja dengan cara tertentu.

Anda sebaiknya menguji antarmuka publik Anda. Jika metode yang memanggil metode pribadi Anda berfungsi seperti yang Anda harapkan, Anda kemudian mengasumsikan dengan ekstensi bahwa metode pribadi Anda berfungsi dengan benar.

Sumber: Bagaimana Anda menguji metode pribadi?

Situasi

Saya mencoba menguji sistem pemrosesan data stateful. Sistem dapat melakukan hal-hal yang berbeda untuk bagian data yang sama persis mengingat statusnya sebelum menerima data tersebut. Pertimbangkan uji langsung yang membangun status dalam sistem, lalu uji perilaku yang ingin diuji oleh metode yang diberikan.

  • SAP menyarankan agar saya tidak menguji "prosedur build up state", saya harus berasumsi bahwa state adalah apa yang saya harapkan dari kode build up dan kemudian menguji satu perubahan state yang saya coba uji

  • PMP menyarankan agar saya tidak dapat melewati langkah "peningkatan keadaan" ini dan hanya menguji metode yang mengatur fungsi itu secara mandiri.

Hasil dari kode saya yang sebenarnya adalah tes yang membengkak, rumit, panjang, dan sulit untuk ditulis. Dan jika transisi negara berubah, tes harus diubah ... yang akan baik-baik saja dengan tes kecil, efisien tetapi sangat memakan waktu dan membingungkan dengan tes lama yang membengkak ini. Bagaimana ini biasanya dilakukan?

durron597
sumber
2
Saya tidak berpikir Anda akan menemukan solusi yang elegan untuk ini. Pendekatan umum bukan untuk membuat sistem stateful untuk memulai, yang tidak membantu Anda saat menguji sesuatu yang sudah dibangun. Refactoring menjadi tanpa kewarganegaraan mungkin juga tidak sebanding dengan biayanya.
Doval
@Doval: Tolong jelaskan bagaimana membuat sesuatu seperti telepon (SIP UserAgent) non-statefull. Perilaku yang diharapkan dari unit ini ditentukan dalam RFC menggunakan diagram transisi keadaan.
Bart van Ingen Schenau
Apakah Anda menyalin / menempel / mengedit tes Anda atau Anda menulis metode utilitas untuk berbagi pengaturan / teardown / fungsionalitas yang umum? Meskipun beberapa kasus uji bisa berlangsung lama dan membengkak, ini seharusnya tidak terlalu umum. Dalam sistem stateful, saya mengharapkan rutin pengaturan umum di mana status akhir adalah parameter dan rutin ini membawa Anda ke kondisi yang ingin Anda uji. Selain itu pada akhir setiap tes saya akan memiliki metode teardown yang membawa Anda kembali ke keadaan awal yang diketahui (jika itu diperlukan) sehingga metode pengaturan Anda akan bekerja dengan baik ketika tes berikutnya dimulai.
Dunk
Pada garis singgung tapi saya juga akan menambahkan bahwa diagram Negara adalah alat komunikasi dan bukan keputusan implementasi bahkan jika itu dalam RFC. Selama Anda memenuhi fungsi yang dijelaskan, Anda memenuhi standar. Saya telah memiliki beberapa kesempatan di mana saya mengubah implementasi transisi keadaan yang sangat rumit (sebagaimana didefinisikan dalam RFC) menjadi fungsi pemrosesan umum yang sangat sederhana. Satu kasus saya ingat menyingkirkan beberapa ribu baris kode begitu saya menyadari bahwa selain beberapa flag sekitar 5 negara melakukan hal yang persis sama setelah Anda mengganti nama elemen umum "tersembunyi".
Dunk

Jawaban:

15

Perspektif:

Jadi mari kita mundur dan tanyakan apa yang TDD coba bantu. TDD berusaha membantu kami menentukan apakah kode kami benar atau tidak. Dan yang benar, maksud saya "apakah kode memenuhi persyaratan bisnis?" Poin jualnya adalah bahwa kita tahu perubahan akan diperlukan di masa mendatang, dan kami ingin memastikan bahwa kode kami tetap benar setelah kami melakukan perubahan itu.

Saya membawa perspektif itu ke atas karena saya pikir mudah untuk tersesat dalam detail dan melupakan apa yang kita coba capai.

Prinsip - SAP:

Meskipun saya bukan seorang ahli dalam TDD, saya pikir Anda kehilangan bagian dari apa yang sedang diajarkan oleh Prinsip Pernyataan Tunggal (SAP). SAP dapat dinyatakan kembali sebagai "uji satu per satu." Tetapi TOTAT tidak menggulung lidah semudah SAP.

Menguji satu hal pada satu waktu berarti Anda fokus pada satu kasus; satu jalan; satu syarat batas; satu kasus kesalahan; satu apa pun per tes. Dan ide mengemudi di balik itu adalah Anda perlu tahu apa yang rusak ketika test case gagal, sehingga Anda dapat menyelesaikan masalah lebih cepat. Jika Anda menguji beberapa kondisi (mis. Lebih dari satu hal) dalam suatu tes dan tes gagal, maka Anda memiliki lebih banyak pekerjaan di tangan Anda. Pertama Anda harus mengidentifikasi mana dari beberapa kasus gagal dan kemudian mencari tahu mengapa itu terjadi gagal.

Jika Anda menguji satu hal pada satu waktu, cakupan pencarian Anda jauh lebih kecil dan cacat diidentifikasi lebih cepat. Perlu diingat bahwa "menguji satu hal pada suatu waktu" tidak serta merta mengecualikan Anda dari melihat lebih dari satu output proses pada suatu waktu. Misalnya, ketika menguji "jalur yang dikenal baik", saya mungkin berharap untuk melihat nilai spesifik yang dihasilkan fooserta nilai lain di dalamnya bardan saya dapat memverifikasi itu foo != barsebagai bagian dari pengujian saya. Kuncinya adalah secara logis mengelompokkan cek output berdasarkan kasus yang sedang diuji.

Prinsip - PMP:

Demikian juga, saya pikir Anda sedikit kehilangan tentang apa yang harus diajarkan oleh Prinsip Metode Pribadi (PMP) kepada kami. PMP mendorong kita untuk memperlakukan sistem seperti kotak hitam. Untuk input yang diberikan, Anda harus mendapatkan output yang diberikan. Anda tidak peduli bagaimana kotak hitam menghasilkan output. Anda hanya peduli bahwa output Anda selaras dengan input Anda.

PMP adalah perspektif yang sangat baik untuk melihat aspek API dari kode Anda. Ini juga dapat membantu Anda menentukan apa yang harus Anda uji. Identifikasi poin antarmuka Anda dan verifikasi bahwa mereka memenuhi persyaratan kontrak mereka. Anda tidak perlu peduli tentang cara metode di belakang antarmuka (alias pribadi) melakukan tugasnya. Anda hanya perlu memverifikasi bahwa mereka melakukan apa yang seharusnya mereka lakukan.


TDD terapan ( untuk Anda )

Jadi situasi Anda menghadirkan sedikit kerutan di luar aplikasi biasa. Metode aplikasi Anda stateful, jadi outputnya bergantung tidak hanya pada input tetapi juga apa yang telah dilakukan sebelumnya. Saya yakin saya harus di <insert some lecture>sini tentang keadaan mengerikan dan bla bla bla, tapi itu benar-benar tidak membantu menyelesaikan masalah Anda.

Saya akan menganggap Anda memiliki semacam tabel diagram keadaan yang menunjukkan berbagai keadaan potensial dan apa yang perlu dilakukan untuk memicu transisi. Jika tidak, Anda akan memerlukannya karena akan membantu mengekspresikan persyaratan bisnis untuk sistem ini.

Tes: Pertama, Anda akan berakhir dengan serangkaian tes yang memberlakukan perubahan status. Idealnya, Anda akan memiliki tes yang menjalankan berbagai perubahan status yang dapat terjadi, tetapi saya dapat melihat beberapa skenario di mana Anda mungkin tidak perlu pergi sejauh itu.

Selanjutnya, Anda perlu membuat tes untuk memvalidasi pemrosesan data. Beberapa tes negara akan digunakan kembali ketika Anda membuat tes pemrosesan data. Misalnya, anggap Anda memiliki metode Foo()yang memiliki keluaran berbeda berdasarkan pada Initdan State1menyatakan. Anda akan ingin menggunakan ChangeFooToState1tes Anda sebagai langkah pengaturan untuk menguji output ketika " Foo()ada State1".

Ada beberapa implikasi di balik pendekatan yang ingin saya sebutkan. Spoiler, di sinilah aku akan membuat marah para puritan

Pertama, Anda harus menerima bahwa Anda menggunakan sesuatu sebagai ujian dalam satu situasi dan pengaturan dalam situasi lain. Di satu sisi, ini tampaknya merupakan pelanggaran langsung terhadap SAP. Tetapi jika Anda secara logis membingkai ChangeFooToState1memiliki dua tujuan maka Anda masih menemukan semangat dari apa yang SAP ajarkan kepada kami. Saat Anda perlu memastikan Foo()status perubahan, maka Anda gunakan ChangeFooToState1sebagai tes. Dan ketika perlu memvalidasi " Foo()keluaran ketika di State1" maka Anda menggunakan ChangeFooToState1sebagai pengaturan.

Butir kedua adalah dari sudut pandang praktis, Anda tidak ingin pengujian unit sepenuhnya acak untuk sistem Anda. Anda harus menjalankan semua tes perubahan status sebelum menjalankan tes validasi output. SAP adalah semacam prinsip panduan di balik pemesanan itu. Untuk menyatakan apa yang seharusnya jelas - Anda tidak dapat menggunakan sesuatu sebagai pengaturan jika gagal sebagai tes.

Menyatukannya:

Dengan menggunakan diagram keadaan Anda, Anda akan membuat tes untuk mencakup transisi. Sekali lagi, menggunakan diagram Anda, Anda menghasilkan tes untuk mencakup semua kasus pemrosesan data input / output didorong oleh negara.

Jika Anda mengikuti pendekatan itu, bloated, complicated, long, and difficult to writetes akan menjadi sedikit lebih mudah untuk dikelola. Secara umum, mereka harus berakhir lebih kecil dan mereka harus lebih ringkas (yaitu kurang rumit). Anda harus memperhatikan bahwa tes lebih dipisahkan atau modular juga.

Sekarang, saya tidak mengatakan prosesnya akan benar-benar bebas dari rasa sakit karena menulis tes yang baik memang membutuhkan usaha. Dan beberapa di antaranya masih akan sulit karena Anda memetakan parameter kedua (keadaan) pada beberapa kasus Anda. Dan sebagai tambahan, itu akan menjadi sedikit lebih jelas mengapa sistem stateless lebih mudah untuk membangun tes. Tetapi jika Anda mengadaptasi pendekatan ini untuk aplikasi Anda, Anda harus menemukan bahwa Anda dapat membuktikan aplikasi Anda berfungsi dengan benar.


sumber
11

Anda biasanya akan mengabstraksi detail pengaturan menjadi fungsi sehingga Anda tidak perlu mengulangi sendiri. Dengan begitu Anda hanya perlu mengubahnya di satu tempat dalam pengujian jika fungsi berubah.

Namun, Anda biasanya tidak ingin mendeskripsikan bahkan fungsi pengaturan Anda sebagai kembung, rumit, atau panjang. Itu pertanda bahwa antarmuka Anda perlu refactoring, karena jika tes Anda sulit digunakan, sulit juga untuk kode asli Anda untuk digunakan.

Itu sering merupakan tanda terlalu banyak dalam satu kelas. Jika Anda memiliki persyaratan stateful, Anda memerlukan kelas yang mengelola negara dan tidak ada yang lain. Kelas-kelas yang mendukungnya harus stateless. Sebagai contoh SIP Anda, penguraian paket harus benar-benar tanpa kewarganegaraan. Anda dapat memiliki kelas yang mem-parsing sebuah paket kemudian memanggil sesuatu seperti sipStateController.receiveInvite()untuk mengelola transisi keadaan, yang dengan sendirinya memanggil kelas tanpa negara lain untuk melakukan hal-hal seperti menelepon telepon.

Ini membuat pengaturan pengujian unit untuk kelas mesin negara menjadi masalah sederhana dari beberapa pemanggilan metode. Jika pengaturan Anda untuk pengujian unit mesin negara bagian membutuhkan paket kerajinan, Anda telah memasukkan terlalu banyak ke kelas itu. Demikian juga, kelas parser paket Anda harus relatif sederhana untuk membuat kode pengaturan, menggunakan tiruan untuk kelas mesin negara.

Dengan kata lain, Anda tidak dapat menghindari status sama sekali, tetapi Anda dapat meminimalkan dan mengisolasinya.

Karl Bielefeldt
sumber
Sebagai catatan, contoh SIP adalah milik saya, bukan dari OP. Dan beberapa mesin negara mungkin membutuhkan lebih dari beberapa pemanggilan metode untuk mendapatkannya dalam kondisi yang tepat untuk pengujian tertentu.
Bart van Ingen Schenau
Memberi +1 untuk "Anda tidak dapat menghindari status sama sekali, tetapi Anda dapat meminimalkan dan mengisolasinya." Saya tidak bisa setuju. Negara adalah kejahatan yang diperlukan dalam perangkat lunak.
Brandon
0

Ide inti dari TDD adalah bahwa, dengan menulis tes terlebih dahulu, Anda berakhir dengan sistem yang, setidaknya, mudah untuk diuji. Semoga itu berhasil, dapat dirawat, didokumentasikan dengan baik dan seterusnya, tetapi jika tidak, setidaknya itu masih mudah untuk diuji.

Jadi, jika Anda TDD dan berakhir dengan sistem yang sulit untuk diuji, ada yang salah. Mungkin beberapa hal yang bersifat pribadi harus bersifat publik, karena Anda memerlukannya untuk pengujian. Mungkin Anda tidak bekerja pada tingkat abstraksi yang tepat; sesuatu yang sederhana seperti daftar adalah stateful pada satu level, tetapi nilai di level lain. Atau mungkin Anda memberi terlalu banyak untuk nasihat yang tidak berlaku dalam konteks Anda, atau masalah Anda hanya sulit. Atau, tentu saja, mungkin desain Anda buruk.

Apa pun penyebabnya, Anda mungkin tidak akan kembali dan menulis sistem Anda lagi untuk membuatnya lebih dapat diuji dengan kode tes sederhana. Jadi, kemungkinan rencana terbaik adalah menggunakan beberapa teknik tes yang sedikit lebih mewah, seperti:

soru
sumber