Apakah ada alasan bahwa tes tidak ditulis sesuai dengan kode yang mereka uji?

91

Saya telah membaca sedikit tentang Pemrograman Literate baru-baru ini, dan itu membuat saya berpikir ... Tes yang ditulis dengan baik, terutama spesifikasi gaya BDD dapat melakukan pekerjaan yang lebih baik dalam menjelaskan kode apa yang dilakukan daripada prosa, dan memiliki keuntungan besar dari memverifikasi akurasi mereka sendiri.

Saya belum pernah melihat tes yang ditulis sesuai dengan kode yang mereka uji. Apakah ini hanya karena bahasa tidak cenderung membuatnya mudah untuk memisahkan aplikasi dan kode uji ketika ditulis dalam file sumber yang sama (dan tidak ada yang membuatnya mudah), atau adakah alasan yang lebih berprinsip bahwa orang memisahkan kode uji dari kode aplikasi?

Chris Devereux
sumber
33
Beberapa bahasa pemrograman seperti python dengan doctest memungkinkan Anda melakukan itu.
Simon Bergot
2
Anda mungkin merasa spesifikasi gaya BDD lebih baik daripada prosa dalam menjelaskan kode, tetapi itu tidak berarti kombinasi keduanya tidak lebih baik.
JeffO
5
Setengah dari argumen di sini juga berlaku untuk dokumentasi inline.
CodesInChaos
3
@Simon doctests terlalu sederhana untuk pengujian serius, sebagian besar karena mereka tidak dirancang untuk itu. Mereka dimaksudkan untuk, dan unggul di, memiliki contoh kode dalam dokumentasi yang dapat diverifikasi secara otomatis. Sekarang, beberapa orang menggunakannya untuk pengujian unit juga, tetapi akhir-akhir ini (seperti pada, selama beberapa tahun terakhir) ini membutuhkan banyak kritik, karena cenderung berakhir dengan kekacauan yang rapuh, "dokumentasi" yang terlalu bertele-tele, dan kekacauan lainnya.
7
Desain oleh Kontrak memungkinkan untuk spesifikasi inline yang membuat pengujian langsung.
Fuhrmanator

Jawaban:

89

Satu-satunya keuntungan yang dapat saya pikirkan untuk tes inline adalah mengurangi jumlah file yang akan ditulis. Dengan IDE modern ini sebenarnya bukan masalah besar.

Namun, ada beberapa kelemahan yang jelas untuk pengujian inline:

  • Itu melanggar pemisahan keprihatinan . Ini mungkin bisa diperdebatkan, tetapi bagi saya pengujian fungsionalitas adalah tanggung jawab yang berbeda dari mengimplementasikannya.
  • Anda harus memperkenalkan fitur bahasa baru untuk membedakan antara tes / implementasi, atau Anda akan berisiko mengaburkan garis di antara keduanya.
  • File sumber yang lebih besar lebih sulit untuk dikerjakan: lebih sulit untuk dibaca, lebih sulit untuk dipahami, Anda lebih mungkin harus berurusan dengan konflik kontrol sumber.
  • Saya pikir itu akan membuat lebih sulit untuk memakai topi "penguji" Anda, sehingga untuk berbicara. Jika Anda melihat detail implementasi, Anda akan lebih tergoda untuk melewatkan pelaksanaan tes tertentu.
vaughandroid
sumber
9
Itu menarik. Saya kira keuntungan yang bisa saya lihat adalah ketika Anda menggunakan "coder" Anda, Anda ingin memikirkan tes, tetapi itu adalah poin yang baik bahwa kebalikannya tidak benar.
Chris Devereux
2
Sejalan dengan ini, dimungkinkan (dan mungkin diinginkan) untuk memiliki satu orang membuat tes dan yang kedua benar-benar menerapkan kode. Menempatkan tes sebaris membuat ini lebih sulit.
Jim Nutt
6
akan downvote jika saya bisa. Bagaimana ini merupakan jawaban sama sekali? Pelaksana tidak menulis tes? Orang melewatkan tes jika mereka melihat detail implementasi? "Terlalu sulit" Konflik pada file besar ?? Dan bagaimana dengan cara apa pun tes dapat dikacaukan dengan detail implementasi ???
bharal
5
@harhar Juga, wrt ke "Terlalu keras", masokisme adalah kebajikan bodoh. Saya ingin semuanya mudah kecuali untuk masalah yang sebenarnya saya coba selesaikan.
deworde
3
Tes unit dapat dianggap sebagai dokumentasi. Itu menunjukkan unit test harus dimasukkan dalam kode untuk alasan yang sama seperti komentar - untuk meningkatkan keterbacaan. Namun, masalah dengan hal itu adalah cenderung ada banyak unit test, dan banyak overhead implementasi-tes yang tidak menentukan hasil yang diharapkan. Bahkan komentar di dalam kode harus dijaga tetap ringkas, dengan penjelasan yang lebih besar dipindahkan - ke blok komentar di luar fungsi, ke file terpisah, atau mungkin ke dokumen desain. Tes unit adalah IMO jarang jika pernah cukup singkat untuk disimpan dalam kode yang diuji seperti komentar.
Steve314
36

Saya dapat memikirkan beberapa hal:

  • Keterbacaan. Menyelingi kode "nyata" dan tes akan membuatnya lebih sulit untuk membaca kode nyata.

  • Kode mengasapi. Mencampur kode "nyata" dan kode uji ke dalam file / kelas yang sama / apa pun yang mungkin menghasilkan file yang dikompilasi lebih besar, dll. Ini sangat penting untuk bahasa dengan ikatan yang terlambat.

  • Anda mungkin tidak ingin pelanggan / klien Anda melihat kode pengujian Anda. (Saya tidak suka alasan ini ... tetapi jika Anda bekerja pada proyek sumber tertutup, kode uji kemungkinan tidak akan membantu pelanggan.)

Sekarang ada solusi yang mungkin untuk masing-masing masalah ini. Tapi IMO, lebih mudah untuk tidak pergi ke sana sejak awal.


Patut diperhatikan bahwa pada masa-masa awal, programmer Java biasa melakukan hal semacam ini; misalnya memasukkan main(...)metode dalam kelas untuk memfasilitasi pengujian. Gagasan ini hampir sepenuhnya menghilang. Ini adalah praktik industri untuk mengimplementasikan tes secara terpisah menggunakan semacam kerangka uji.

Perlu juga diperhatikan bahwa Pemrograman Literate (seperti yang dikandung oleh Knuth) tidak pernah tertangkap dalam industri rekayasa perangkat lunak.

Stephen C
sumber
4
+1 Masalah keterbacaan - kode uji mungkin lebih besar secara proporsional dengan kode implementasi, terutama dalam desain OO.
Fuhrmanator
2
+1 untuk menunjukkan menggunakan kerangka kerja pengujian. Saya tidak bisa membayangkan menggunakan kerangka kerja pengujian yang baik bersamaan dengan kode produksi.
joshin4colours
1
RE: Anda mungkin tidak ingin pelanggan / klien Anda melihat kode pengujian Anda. (Saya tidak suka alasan ini ... tetapi jika Anda bekerja pada proyek sumber tertutup, kode uji kemungkinan besar tidak akan membantu pelanggan.) - Mungkin diinginkan untuk menjalankan tes pada mesin klien. Menjalankan tes dapat membantu mengidentifikasi dengan cepat apa masalahnya dan untuk mengungkap perbedaan dalam klien env ..
sixtyfootersdude
1
@sixtyfootersdude - itu adalah situasi yang sangat tidak biasa. Dan dengan asumsi bahwa Anda sedang mengembangkan sumber tertutup, Anda tidak akan ingin memasukkan tes Anda dalam distro biner standar Anda untuk berjaga-jaga. (Anda akan membuat bundel terpisah yang berisi tes yang Anda ingin pelanggan jalankan.)
Stephen C
1
1) Apakah Anda melewatkan bagian pertama dari jawaban saya di mana saya memberikan tiga alasan sebenarnya? Ada beberapa "pemikiran kritis" yang terlibat di sana .... 2) Apakah Anda melewatkan bagian kedua di mana saya mengatakan bahwa programmer Java dulu melakukan ini, tetapi mereka tidak sekarang? Dan implikasi yang jelas bahwa programmer berhenti melakukan ini ... untuk alasan yang bagus?
Stephen C
14

Sebenarnya, Anda dapat menganggap Desain Dengan Kontrak sebagai melakukan ini. Masalahnya adalah sebagian besar bahasa pemrograman tidak membiarkan Anda menulis kode seperti ini :( Sangat mudah untuk menguji prasyarat dengan tangan, tetapi kondisi posting adalah tantangan nyata tanpa mengubah cara Anda menulis kode (IMO negatif besar).

Michael Feathers memiliki presentasi tentang ini dan ini adalah salah satu dari banyak cara dia menyebutkan Anda dapat meningkatkan kualitas kode.

Daniel Kaplan
sumber
13

Untuk banyak alasan yang sama ketika Anda mencoba untuk menghindari kopling ketat antara kelas-kelas dalam kode Anda, juga merupakan ide yang baik untuk menghindari kopling yang tidak perlu antara tes dan kode.

Penciptaan: Tes dan kode dapat ditulis pada waktu yang berbeda, oleh orang yang berbeda.

Kontrol: Jika tes digunakan untuk menentukan persyaratan, Anda tentu menginginkannya tunduk pada aturan yang berbeda tentang siapa yang dapat mengubahnya dan kapan dibandingkan dengan kode yang sebenarnya.

Dapat digunakan kembali: Jika Anda menempatkan tes sebaris, Anda tidak dapat menggunakannya dengan potongan kode lain.

Bayangkan Anda memiliki banyak kode yang melakukan pekerjaan dengan benar, tetapi masih banyak yang diinginkan dalam hal kinerja, pemeliharaan, apa pun. Anda memutuskan untuk mengganti kode itu dengan kode baru dan lebih baik. Menggunakan serangkaian tes yang sama dapat membantu Anda memverifikasi bahwa kode baru menghasilkan hasil yang sama dengan kode lama.

Kemampuan pilih : Menjaga tes terpisah dari kode membuatnya lebih mudah untuk memilih tes mana yang ingin Anda jalankan.

Misalnya, Anda mungkin memiliki serangkaian kecil tes yang hanya terkait dengan kode yang sedang Anda kerjakan, dan suite yang lebih besar yang menguji seluruh proyek.

Caleb
sumber
Saya bingung tentang alasan Anda: TDD sudah mengatakan pembuatan tes terjadi sebelum (atau pada saat yang sama) sebagai kode produksi, dan harus dilakukan oleh pembuat kode yang sama! Mereka juga mengisyaratkan bahwa tes seperti persyaratan. Tentu saja, keberatan ini tidak berlaku jika Anda tidak berlangganan dogma TDD (yang akan diterima, tetapi Anda harus menjelaskannya!). Juga, apa sebenarnya tes yang "dapat digunakan kembali"? Bukankah tes, menurut definisi, khusus untuk kode yang mereka uji?
Andres F.
1
@AndresF. Tidak, tes tidak khusus untuk kode yang mereka uji; mereka spesifik untuk perilaku yang mereka uji. Jadi, katakan Anda memiliki modul Widget lengkap dengan serangkaian tes yang memverifikasi bahwa Widget berperilaku benar. Kolega Anda datang dengan BetterWidget, yang bermaksud melakukan hal yang sama seperti Widget tetapi tiga kali lebih cepat. Jika tes untuk Widget disematkan dalam kode sumber Widget dengan cara yang sama seperti Literate Programming menyematkan dokumentasi dalam kode sumber, Anda tidak dapat menerapkan tes tersebut dengan lebih baik ke BetterWidget untuk memverifikasi bahwa perilakunya sama dengan Widget.
Caleb
@AndresF. tidak perlu menentukan Anda tidak mengikuti TDD. itu bukan standar kosmik. Adapun titik reuse. Saat menguji suatu sistem Anda peduli tentang input dan output, bukan internal. Ketika Anda kemudian perlu membuat sistem baru yang berperilaku seperti itu tetapi diterapkan secara berbeda, sangat bagus untuk memiliki tes yang dapat Anda jalankan pada sistem lama dan baru. ini terjadi pada saya lebih dari sekali, kadang-kadang Anda perlu bekerja pada sistem yang baru sementara yang lama masih dalam tekanan atau bahkan menjalankannya berdampingan. Lihat cara Facebook menguji 'reaksi serat' dengan tes reaksi untuk mencapai paritas.
user1852503
10

Berikut adalah beberapa alasan tambahan yang dapat saya pikirkan:

  • memiliki tes di perpustakaan terpisah membuatnya lebih mudah untuk menautkan hanya perpustakaan itu terhadap kerangka pengujian Anda, dan bukan kode produksi Anda (ini bisa dihindari oleh beberapa preprocessor, tetapi mengapa membangun hal seperti itu ketika solusi yang lebih mudah adalah dengan menulis tes di tempat terpisah)

  • tes fungsi, kelas, pustaka biasanya ditulis dari sudut pandang "pengguna" (pengguna fungsi / kelas / pustaka). "Menggunakan kode" seperti itu biasanya ditulis dalam file atau pustaka yang terpisah, dan tes mungkin lebih jelas atau "lebih realistis" jika meniru situasi itu.

Doc Brown
sumber
5

Jika pengujian sesuai, akan diperlukan untuk menghapus kode yang Anda butuhkan untuk pengujian ketika Anda mengirimkan produk ke pelanggan Anda. Jadi tempat tambahan di mana Anda menyimpan tes Anda hanya memisahkan antara kode yang Anda butuhkan dan kode yang dibutuhkan pelanggan Anda .

jam
sumber
9
Bukan tidak mungkin. Ini akan membutuhkan fase preprocessing tambahan, seperti halnya LP. Itu bisa dilakukan dengan mudah dalam bahasa C, atau bahasa kompilasi-ke-js, misalnya.
Chris Devereux
+1 untuk menunjukkan hal itu kepada saya. Saya sudah mengedit jawaban saya untuk mewakili itu.
mhr
Ada juga asumsi bahwa ukuran kode penting dalam setiap kasus. Hanya karena itu penting dalam beberapa kasus tidak berarti itu penting dalam semua kasus. Ada banyak lingkungan di mana programmer tidak didorong untuk mengoptimalkan ukuran kode sumber. Jika itu masalahnya, mereka tidak akan membuat banyak kelas.
zumalifeguard
5

Ide ini sama dengan metode "Self_Test" dalam konteks desain berbasis objek atau berorientasi objek. Jika menggunakan bahasa berbasis objek yang dikompilasi seperti Ada, semua kode swa-uji akan ditandai oleh kompiler sebagai tidak digunakan (tidak pernah dipanggil) selama kompilasi produksi, dan oleh karena itu semua akan dioptimalkan jauh - tidak ada yang akan muncul di dapat dieksekusi.

Menggunakan metode "Self_Test" adalah ide yang sangat bagus, dan jika programmer benar-benar peduli dengan kualitas, mereka semua akan melakukannya. Namun, satu masalah penting adalah bahwa metode "Self_Test" perlu memiliki disiplin yang kuat, karena metode ini tidak dapat mengakses detail implementasi apa pun dan harus mengandalkan hanya pada semua metode lain yang dipublikasikan dalam spesifikasi objek. Jelas, jika tes mandiri gagal, implementasinya perlu diubah. Tes mandiri harus secara ketat menguji semua properti yang dipublikasikan dari metode objek, tetapi tidak pernah mengandalkan cara apa pun pada detail implementasi apa pun yang spesifik.

Bahasa berbasis objek dan berorientasi objek sering menyediakan jenis disiplin yang tepat sehubungan dengan metode eksternal untuk objek yang diuji (mereka menegakkan spesifikasi objek, mencegah akses ke detail implementasi dan meningkatkan kesalahan kompilasi jika upaya tersebut terdeteksi ). Tetapi metode internal objek itu sendiri semua diberikan akses lengkap ke setiap detail implementasi. Jadi metode uji diri berada dalam situasi yang unik: perlu metode internal karena sifatnya (uji diri jelas merupakan metode objek yang diuji), namun perlu menerima semua disiplin kompilator dari metode eksternal ( itu harus independen dari rincian implementasi objek). Sedikit jika ada bahasa pemrograman memberikan kemampuan untuk mendisiplinkan objek ' Metode internal seolah-olah itu adalah metode eksternal. Jadi ini adalah masalah desain bahasa pemrograman yang penting.

Dengan tidak adanya dukungan bahasa pemrograman yang tepat, cara terbaik untuk melakukannya adalah dengan membuat objek pendamping. Dengan kata lain, untuk setiap objek yang Anda kode (sebut saja "Big_Object"), Anda juga membuat objek pendamping kedua yang namanya terdiri dari akhiran standar yang disatukan dengan nama objek "nyata" (dalam hal ini, "Big_Object_Self_Test" "), dan yang spesifikasinya terdiri dari metode tunggal (" Big_Object_Self_Test.Self_Test (This_Big_Object: Big_Object) mengembalikan Boolean; "). Objek pendamping kemudian akan tergantung pada spesifikasi objek utama, dan kompiler akan sepenuhnya menegakkan semua disiplin spesifikasi itu terhadap implementasi objek pendamping.

commenter8
sumber
4

Ini sebagai tanggapan terhadap sejumlah besar komentar yang menunjukkan bahwa tes inline tidak dilakukan karena sulit untuk tidak mungkin menghapus kode tes dari rilis rilis. Ini tidak benar. Hampir semua kompiler dan assembler sudah mendukung ini, dengan bahasa yang dikompilasi, seperti C, C ++, C #, ini dilakukan dengan apa yang disebut arahan kompiler.

Dalam kasus c # (saya percaya c ++ juga, sintaks mungkin sedikit berbeda tergantung pada apa yang Anda gunakan kompiler) ini adalah bagaimana Anda dapat melakukannya.

#define DEBUG //  = true if c++ code
#define TEST /* can also be defined in the make file for c++ or project file for c# and applies to all associated .cs/.cpp files */

//somewhere in your code
#if DEBUG
// debug only code
#elif TEST
// test only code
#endif

Karena ini menggunakan arahan kompiler, kode tidak akan ada dalam file yang dapat dieksekusi yang dibangun jika flag tidak disetel. Ini juga cara Anda membuat program "tulis sekali, kompilasi dua kali" untuk beberapa platform / perangkat keras.

john
sumber
2

Kami menggunakan tes sebaris dengan kode Perl kami. Ada modul, Test :: Inline , yang menghasilkan file uji dari kode inline.

Saya tidak terlalu pandai mengatur tes, dan merasa lebih mudah dan lebih mungkin dipertahankan saat digarisbawahi.

Menanggapi beberapa masalah yang dikemukakan:

  • Tes sebaris ditulis di bagian POD, jadi itu bukan bagian dari kode aktual. Mereka diabaikan oleh penerjemah, jadi tidak ada kode mengasapi.
  • Kami menggunakan lipatan Vim untuk menyembunyikan bagian uji. Satu-satunya hal yang Anda lihat adalah satu baris di atas setiap metode yang diuji +-- 33 lines: #test----. Saat Anda ingin mengerjakan tes, Anda cukup mengembangkannya.
  • Modul Test :: Inline "mengkompilasi" tes ke file yang kompatibel dengan TAP normal, sehingga mereka dapat hidup berdampingan dengan tes tradisional.

Sebagai referensi:

mla
sumber
1

Erlang 2 sebenarnya mendukung tes inline. Setiap ekspresi boolean dalam kode yang tidak digunakan (misalnya ditugaskan ke variabel atau lulus) secara otomatis diperlakukan sebagai tes dan dievaluasi oleh kompiler; jika ekspresi salah, kode tidak dikompilasi.

Mark Rendle
sumber
1

Alasan lain untuk memisahkan tes adalah bahwa Anda sering menggunakan pustaka tambahan atau bahkan berbeda untuk pengujian daripada untuk implementasi yang sebenarnya. Jika Anda mencampur tes dan implementasi, penggunaan pustaka uji secara tidak sengaja dalam implementasi tidak dapat ditangkap oleh kompiler.

Selain itu, tes cenderung memiliki lebih banyak baris kode daripada bagian implementasi yang diuji, sehingga Anda akan kesulitan menemukan implementasi di antara semua tes. :-)

Hans-Peter Störr
sumber
0

Ini tidak benar. Jauh lebih baik untuk menempatkan unit-tes Anda di samping kode produksi ketika kode produksi terutama ketika rutinitas produksi murni.

Misalnya, jika Anda mengembangkan di bawah .NET, Anda dapat memasukkan kode pengujian dalam unit produksi, dan kemudian menggunakan Scalpel untuk menghapusnya sebelum mengirim.

zumalifeguard
sumber