Pengujian unit C ++: Apa yang harus diuji?

20

TL; DR

Menulis tes yang baik dan bermanfaat sulit, dan memiliki biaya tinggi dalam C ++. Dapatkan Anda pengembang yang berpengalaman membagikan alasan Anda tentang apa dan kapan menguji?

Cerita panjang

Saya biasa melakukan pengembangan yang digerakkan oleh tes, seluruh tim saya sebenarnya, tetapi itu tidak berhasil bagi kami. Kami memiliki banyak tes, tetapi tampaknya tidak pernah mencakup kasus di mana kami memiliki bug dan regresi sebenarnya - yang biasanya terjadi ketika unit berinteraksi, bukan dari perilaku mereka yang terisolasi.

Hal ini seringkali sangat sulit untuk diuji pada tingkat unit sehingga kami berhenti melakukan TDD (kecuali untuk komponen yang benar-benar mempercepat pengembangan), dan sebagai gantinya menginvestasikan lebih banyak waktu untuk meningkatkan cakupan uji integrasi. Sementara tes unit kecil tidak pernah menangkap bug nyata dan pada dasarnya hanya pemeliharaan overhead, tes integrasi benar-benar sepadan dengan usaha.

Sekarang saya telah mewarisi proyek baru, dan saya bertanya-tanya bagaimana cara mengujinya. Ini adalah aplikasi C ++ / OpenGL asli, jadi tes integrasi sebenarnya bukan pilihan. Tetapi pengujian unit dalam C ++ sedikit lebih sulit daripada di Jawa (Anda harus membuat barang secara eksplisit virtual), dan program ini tidak terlalu berorientasi objek, jadi saya tidak bisa mengejek / mematikan beberapa hal.

Saya tidak ingin mencabik-cabik dan OO-ize semuanya hanya untuk menulis beberapa tes demi tes menulis. Jadi saya bertanya kepada Anda: Untuk apa saya harus menulis tes? misalnya:

  • Fungsi / Kelas yang saya harapkan akan sering berubah?
  • Fungsi / Kelas yang lebih sulit untuk diuji secara manual?
  • Fungsi / Kelas yang sudah mudah diuji?

Saya mulai menyelidiki beberapa basis kode C ++ yang terhormat untuk melihat bagaimana mereka melakukan pengujian. Saat ini saya sedang mencari kode sumber Chromium, tetapi saya merasa sulit untuk mengekstrak alasan pengujian dari kode tersebut. Jika ada yang punya contoh atau posting yang baik tentang seberapa populer pengguna C ++ (orang-orang dari komite, penulis buku, Google, Facebook, Microsoft, ...) mendekati ini, itu akan sangat membantu.

Memperbarui

Saya telah mencari di situs ini dan di web sejak menulis ini. Menemukan beberapa barang bagus:

Sayangnya, semua ini agak Java / C # centric. Menulis banyak tes di Java / C # bukan masalah besar, jadi manfaatnya biasanya melebihi biaya.

Tapi seperti yang saya tulis di atas, lebih sulit di C ++. Terutama jika basis kode Anda tidak begitu-OO, Anda harus mengacaukan segalanya untuk mendapatkan cakupan tes unit yang baik. Sebagai contoh: Aplikasi yang saya warisi memiliki Graphicsruang nama yang merupakan lapisan tipis di atas OpenGL. Untuk menguji entitas mana pun - yang semuanya menggunakan fungsinya secara langsung - saya harus mengubahnya menjadi antarmuka dan kelas dan menyuntikkannya ke semua entitas. Itu hanya satu contoh.

Jadi ketika menjawab pertanyaan ini, harap diingat bahwa saya harus melakukan investasi yang agak besar untuk tes menulis.

futlib
sumber
3
+1 untuk kesulitan dalam pengujian unit C ++. Jika tes unit Anda mengharuskan Anda untuk mengubah kode, jangan.
DPD
2
@DPD: Saya tidak begitu yakin, bagaimana jika ada sesuatu yang benar-benar layak untuk diuji? Pada basis kode saat ini, saya hampir tidak dapat menguji apa pun dalam kode simulasi karena semuanya memanggil fungsi grafik secara langsung, dan saya tidak dapat mengejek / mematikannya. Yang bisa saya uji sekarang adalah fungsi utilitas. Tapi saya setuju, mengubah kode untuk membuatnya "dapat diuji" terasa ... salah. Para pendukung TDD sering mengatakan bahwa ini akan membuat semua kode Anda lebih baik dalam semua cara yang bisa dibayangkan, tapi saya dengan rendah hati tidak setuju. Tidak semuanya membutuhkan antarmuka dan beberapa implementasi.
futlib
Biarkan saya memberi Anda contoh baru-baru ini: Saya menghabiskan satu hari penuh mencoba untuk menguji satu fungsi menghanguskan (ditulis dalam C ++ / CLI) dan alat uji MS Test akan selalu crash untuk tes ini. Tampaknya memiliki beberapa masalah dengan referensi CPP biasa. Alih-alih, saya hanya menguji oput dari fungsi pemanggilannya dan itu berfungsi dengan baik. Saya menghabiskan satu hari penuh untuk satu fungsi UT. Itu adalah kehilangan waktu yang berharga. Juga saya tidak bisa mendapatkan alat stubbing yang sesuai dengan kebutuhan saya. Saya melakukan stubbing manual sedapat mungkin.
DPD
Itu hanya hal-hal yang ingin saya hindari: DI kira kita C ++ devs harus sangat pragmatis tentang pengujian. Anda akhirnya mengujinya, jadi saya rasa tidak apa-apa.
futlib
@DPD: Saya sudah memikirkan lebih lanjut tentang ini, dan saya pikir Anda benar, pertanyaannya adalah pengorbanan seperti apa yang ingin saya buat. Apakah layak refactoring seluruh sistem grafis untuk menguji beberapa entitas? Tidak ada bug di sana saya tahu, jadi mungkin: Tidak. Jika mulai merasa buggy, saya akan menulis tes. Sayang sekali saya tidak dapat menerima jawaban Anda karena ini adalah komentar :)
futlib

Jawaban:

5

Nah, Unit Testing hanya satu bagian. Tes integrasi membantu Anda dengan masalah tim Anda. Tes Integrasi dapat ditulis untuk semua jenis aplikasi, juga untuk aplikasi asli dan OpenGL. Anda harus memeriksa "Growing Object Oriented Software Dipandu oleh Tes" oleh Steve Freemann dan Nat Pryce (mis. Http://www.amazon.com/Growing-Object-Oriented-Software-Guided-Signature/dp/0321503627 ). Ini menuntun Anda langkah demi langkah melalui pengembangan aplikasi dengan GUI dan komunikasi jaringan.

Pengujian Perangkat Lunak yang tidak digerakkan oleh tes adalah cerita lain. Lihat Michael Feathers "Bekerja Secara Efektif dengan Kode Warisan" (http://www.amazon.com/Working-Effectively-Legacy-Michael-Feathers/dp/0131177052).

EricSchaefer
sumber
Saya tahu kedua buku itu. Masalahnya adalah: 1. Kami tidak ingin menggunakan TDD, karena itu tidak bekerja dengan baik dengan kami. Kami memang ingin tes, tetapi tidak secara agama. 2. Saya yakin tes integrasi untuk aplikasi OpenGL bisa dilakukan, tapi butuh banyak usaha. Saya ingin terus meningkatkan aplikasi, bukan memulai proyek penelitian.
futlib
Ketahuilah bahwa pengujian unit "setelah fakta" selalu lebih sulit daripada "tes pertama", karena kode tidak dirancang untuk dapat diuji (dapat digunakan kembali, dapat dipelihara, dll.). Jika Anda tetap ingin melakukannya, cobalah untuk tetap menggunakan trik Michael Feathers (misalnya jahitan) bahkan jika Anda menghindari TDD. Misal, jika Anda ingin memperpanjang suatu fungsi coba sesuatu seperti "metode sprout" dan cobalah untuk tetap menggunakan metode baru ini. Hal ini dimungkinkan untuk dilakukan, tetapi IMHO lebih sulit.
EricSchaefer
Saya setuju bahwa kode non TDD tidak dirancang untuk dapat diuji, tetapi saya tidak akan mengatakan itu tidak dapat dipertahankan atau digunakan kembali per se - seperti yang saya komentari di atas, beberapa hal hanya tidak memerlukan antarmuka dan beberapa implementasi. Tidak masalah sama sekali dengan Mockito, tetapi di C ++, saya harus membuat semua fungsi yang saya ingin rintisan / mock virtual. Bagaimanapun, kode yang tidak dapat diuji adalah masalah terbesar saya saat ini: Saya harus mengubah beberapa hal yang sangat mendasar untuk membuat beberapa bagian dapat diuji, dan oleh karena itu saya ingin alasan yang bagus tentang apa yang harus diuji, untuk memastikan itu layak.
futlib
Anda benar tentu saja, saya akan berhati-hati untuk membuat kode baru yang saya tulis dapat diuji. Tapi itu tidak akan mudah, dengan cara kerja di basis kode ini sekarang.
futlib
Ketika Anda menambahkan fitur / fungsi, pikirkan saja bagaimana Anda bisa mengujinya. Bisakah Anda menyuntikkan dependensi jelek? Bagaimana Anda tahu bahwa fungsi melakukan apa yang seharusnya dilakukan. Bisakah Anda mengamati perilaku apa pun? Apakah ada hasil yang bisa Anda periksa kebenarannya? Apakah ada invarian yang bisa Anda periksa?
EricSchaefer
2

Sayang TDD "tidak bekerja dengan baik untuk Anda." Saya pikir itulah kunci untuk memahami ke mana harus berpaling. Tinjau kembali dan pahami bagaimana TDD tidak berfungsi, apa yang bisa Anda lakukan dengan lebih baik, mengapa ada kesulitan.

Jadi, tentu saja unit test Anda tidak menangkap bug yang Anda temukan. Itu intinya. :-) Anda tidak menemukan bug itu karena Anda mencegahnya terjadi sejak awal dengan memikirkan bagaimana antarmuka harus bekerja dan bagaimana memastikan mereka diuji dengan benar.

Untuk menjawab, Anda mempertanyakan, seperti yang telah Anda simpulkan, kode pengujian unit yang tidak dirancang untuk diuji sulit. Untuk kode yang ada, mungkin lebih efektif untuk menggunakan lingkungan uji fungsional atau integrasi daripada lingkungan uji unit. Uji keseluruhan sistem dengan fokus pada area tertentu.

Tentu saja pengembangan baru akan mendapat manfaat dari TDD. Ketika fitur baru ditambahkan, refactoring untuk TDD mungkin membantu untuk menguji pengembangan baru, sementara juga memungkinkan pengembangan unit test baru untuk fungsi-fungsi warisan.

Bill Door
sumber
4
Kami melakukan TDD selama sekitar satu setengah tahun, semua sangat bersemangat tentang hal itu. Namun membandingkan proyek TDD dengan yang sebelumnya dilakukan tanpa TDD (tapi bukan tanpa tes), saya tidak akan mengatakan bahwa mereka sebenarnya lebih stabil, atau memiliki kode yang dirancang lebih baik. Mungkin itu tim kami: Kami banyak berpasangan dan mengulas, kualitas kode kami selalu cukup baik.
futlib
1
Semakin saya memikirkannya, semakin saya berpikir bahwa TDD tidak cocok dengan teknologi proyek tertentu itu dengan sangat baik: Flex / Swiz. Ada banyak peristiwa dan ikatan dan injeksi yang terjadi yang membuat interaksi antara objek menjadi rumit dan hampir tidak mungkin untuk unit test. Memisahkan objek-objek itu tidak membuatnya lebih baik, karena mereka bekerja dengan benar sejak awal.
futlib
2

Saya belum melakukan TDD di C ++ jadi saya tidak bisa mengomentari itu, tetapi Anda seharusnya menguji perilaku yang diharapkan dari kode Anda. Sementara implementasinya dapat berubah, perilaku seharusnya (biasanya?) Tetap sama. Di dunia Java \ C # centric, itu berarti Anda hanya menguji metode publik, menulis tes untuk perilaku yang diharapkan dan melakukan itu sebelum implementasi (yang biasanya lebih baik dikatakan daripada dilakukan :)).

Dante
sumber