Bagaimana seharusnya C ++ Kode Uji Unit disusun untuk efisiensi Unit Uji maksimum?

47
  • Pertanyaan ini bukan tentang Kerangka Pengujian Unit.
  • Pertanyaan ini bukan tentang menulis Tes Unit.
  • Pertanyaan ini adalah tentang di mana harus menulis kode UT dan bagaimana / kapan / di mana mengkompilasi dan menjalankannya.

Dalam Bekerja Efektif dengan Legacy Code , Michael Feathers menegaskan hal itu

tes unit yang bagus ... berjalan cepat

dan itu

Tes unit yang membutuhkan 1/10 detik untuk dijalankan adalah tes unit lambat.

Saya pikir definisi ini masuk akal. Saya juga berpikir bahwa mereka menyiratkan bahwa Anda harus menyimpan satu set Tes Unit dan satu set Tes Kode Itu yang Dibutuhkan Lebih Lama secara terpisah, tapi saya kira itu harga yang Anda bayar untuk hanya memanggil sesuatu Tes Unit jika berjalan (sangat) cepat .

Jelas masalah di C ++ adalah bahwa untuk "lari" Anda Unit Uji ( s ), Anda harus:

  1. Edit kode Anda (produksi atau Tes Unit, tergantung pada "siklus" Anda berada di mana)
  2. Menyusun
  3. Tautan
  4. Mulai Unit Uji Executable ( s )

Sunting (setelah pemilihan umum yang aneh) : Sebelum masuk ke detail, saya akan mencoba meringkas titik di sini:

Bagaimana kode C ++ Unit Test dapat diatur secara efektif, sehingga efisien untuk mengedit kode (test) dan menjalankan kode tes?


Masalah pertama adalah memutuskan di mana harus meletakkan kode Uji Unit sehingga:

  • itu "alami" untuk mengedit dan melihatnya dalam kombinasi dengan kode produksi terkait.
  • mudah / cepat untuk memulai siklus kompilasi untuk unit yang sedang Anda ubah

Masalah kedua , terkait, adalah apa yang harus dikompilasi sehingga umpan baliknya instan.

Opsi ekstrem:

  • Setiap Unit-Tes-Tes-Unit hidup dalam file cpp yang terpisah dan file cpp ini dikompilasi + ditautkan secara terpisah (bersama dengan kode sumber file unit yang diuji) ke satu executable yang kemudian menjalankan Tes Unit yang satu ini.
    • (+) Ini meminimalkan waktu startup (kompilasi + tautan!) Untuk Unit Uji tunggal.
    • (+) Tes berjalan sangat cepat, karena hanya menguji satu unit.
    • (-) Mengeksekusi seluruh rangkaian harus memulai banyak proses. Bisa jadi masalah untuk dikelola.
    • (-) overhead proses mulai akan terlihat
  • Sisi lain akan memiliki - masih - satu file cpp per tes, tetapi semua file cpp tes (bersama dengan kode yang mereka uji!) Dihubungkan ke satu yang dapat dieksekusi (per modul / per proyek / pilih pilihan Anda).
    • (+) Waktu kompilasi masih akan OK, karena hanya kode yang diubah yang akan dikompilasi.
    • (+) Menjalankan seluruh suite itu mudah, karena hanya ada satu exe yang harus dijalankan.
    • (-) Suite akan membutuhkan waktu lama untuk ditautkan, karena setiap kompilasi ulang terhadap objek apa pun akan memicu tautan ulang.
    • (-) (?) Gugatan akan memakan waktu lebih lama untuk dijalankan, meskipun jika semua Tes Unit cepat, waktunya harus OK.

Jadi, bagaimana dunia nyata Unit C ++ Tes ditangani? Jika saya hanya menjalankan hal-hal itu setiap malam / jam, bagian kedua tidak terlalu penting, tetapi bagian pertama, yaitu bagaimana "memasangkan" kode UT ke kode produksi, sehingga "wajar" bagi pengembang untuk menjaga keduanya tetap dalam fokus selalu penting menurut saya. (Dan jika pengembang memiliki kode UT dalam fokus, mereka ingin menjalankannya, yang membawa kita kembali ke bagian dua.)

Kisah dan pengalaman dunia nyata dihargai!

Catatan:

  • Pertanyaan ini sengaja meninggalkan platform yang tidak ditentukan dan sistem pembuatan / proyek.
  • Pertanyaan yang Ditandai UT & C ++ adalah tempat yang bagus untuk memulai, tetapi sayangnya terlalu banyak pertanyaan, dan terutama jawaban, terlalu terfokus pada detail atau kerangka kerja tertentu.
  • Beberapa waktu yang lalu, saya menjawab pertanyaan serupa tentang struktur untuk meningkatkan unit test. Saya menemukan struktur ini kurang untuk "nyata", Tes Unit cepat. Dan saya menemukan pertanyaan lain terlalu sempit, maka pertanyaan baru ini.
Martin Ba
sumber
6
Komplikasi lebih lanjut diperkenalkan oleh idiom C ++ tentang memindahkan sebanyak mungkin kesalahan untuk mengkompilasi waktu - serangkaian unit test yang baik seringkali perlu untuk dapat menguji bahwa penggunaan tertentu gagal dikompilasi.
3
@Closers: Bisakah Anda memberikan argumen yang membuat Anda menutup pertanyaan ini?
Luc Touraille
Saya tidak mengerti mengapa ini harus ditutup. :-(Di mana Anda seharusnya mencari jawaban untuk pertanyaan seperti itu jika tidak ada di forum ini?
2
@ Jo: Kenapa saya perlu tes unit untuk menemukan jika ada sesuatu yang dikompilasi ketika kompiler akan memberitahu saya itu?
David Thornley
2
@ David: Karena Anda ingin memastikan bahwa itu tidak dikompilasi . Contoh cepat akan menjadi objek pipa yang menerima input dan dan menghasilkan output Pipeline<A,B>.connect(Pipeline<B,C>)harus dikompilasi sedangkan Pipeline<A,B>.connect(Pipeline<C,D>)seharusnya tidak mengkompilasi: Jenis output dari tahap pertama tidak sesuai dengan jenis input dari tahap kedua.
sebastiangeiger

Jawaban:

6

Kami memiliki semua tes unit (untuk modul) dalam satu yang dapat dieksekusi. Tes dimasukkan ke dalam kelompok. Saya dapat menjalankan tes tunggal (atau beberapa tes) atau sekelompok tes dengan menentukan nama (tes / grup) pada baris perintah pelari tes. Sistem build dapat menjalankan grup "Build", departemen uji dapat menjalankan "All". Pengembang dapat memasukkan beberapa tes ke dalam grup seperti "BUG1234" dengan 1234 menjadi nomor pelacak masalah dari kasus yang sedang dikerjakannya.

kamu.
sumber
6

Pertama, saya tidak setuju dengan "1) Edit kode (produksi) Anda dan Uji Unit Anda". Anda harus memodifikasi hanya satu per satu, jika tidak, jika hasilnya berubah, Anda tidak akan tahu yang mana yang menyebabkannya.

Saya suka meletakkan unit test di pohon direktori yang membayangi pohon utama. Jika saya punya /sources/componentA/alpha/foo.ccdan /objects/componentA/beta/foo.o, maka saya ingin sesuatu seperti /UTest_sources/componentA/alpha/test_foo.ccdan /UTest_objects/componentA/beta/test_foo.o. Saya menggunakan pohon bayangan yang sama untuk benda rintisan / tiruan dan sumber apa pun yang dibutuhkan tes. Akan ada beberapa kasus tepi, tetapi skema ini menyederhanakan banyak hal. Makro editor yang baik dapat menarik sumber tes bersama sumber subjek dengan mudah. Sistem build yang baik (mis. GNUMake) dapat mengkompilasi keduanya dan menjalankan tes dengan satu perintah (mis. make test_foo), Dan dapat mengelola bazillion proses semacam itu - hanya yang sumbernya telah berubah sejak terakhir kali diuji - cukup mudah (saya punya tidak pernah menemukan overhead memulai proses ini menjadi masalah, itu O (N)).

Dalam kerangka kerja yang sama, Anda dapat memiliki tes skala lebih besar (tidak lagi tes unit) yang menghubungkan banyak objek dan menjalankan banyak tes. Triknya adalah dengan mengurutkan tes-tes ini berdasarkan berapa lama waktu yang dibutuhkan untuk membangun / berlari, dan mengerjakannya sesuai dengan jadwal harian Anda. Jalankan tes satu detik atau kurang setiap kali Anda menginginkannya; mulailah tes sepuluh detik dan lakukan peregangan; tes lima menit dan istirahat; tes setengah jam dan pergi makan siang; tes enam jam dan pulang. Jika ternyata Anda membuang banyak waktu, mis. Menautkan ulang pengujian besar setelah mengubah hanya satu file kecil, Anda salah melakukannya - bahkan jika penautannya seketika, Anda masih akan menjalankan tes panjang ketika tidak dipanggil.

Beta
sumber
iklan (1) - ya itu diungkapkan dengan sembarangan
Martin Ba