Apakah normal menghabiskan lebih banyak, jika tidak lebih, tes menulis waktu daripada kode yang sebenarnya?

211

Saya menemukan tes jauh lebih sulit dan sulit untuk ditulis daripada kode aktual yang mereka uji. Ini tidak biasa bagi saya untuk menghabiskan lebih banyak waktu menulis tes daripada kode yang diuji.

Apakah itu normal atau saya melakukan sesuatu yang salah?

Pertanyaan “ Apakah pengujian unit atau pengembangan yang digerakkan oleh pengujian bermanfaat? ”,“ Kami menghabiskan lebih banyak waktu untuk mengimplementasikan tes fungsional daripada mengimplementasikan sistem itu sendiri, apakah ini normal? ”Dan jawaban mereka lebih lanjut tentang apakah pengujian itu layak (seperti dalam" haruskah kita melewatkan tes menulis sama sekali? "). Sementara saya yakin tes itu penting, saya bertanya-tanya apakah saya menghabiskan lebih banyak waktu untuk tes daripada kode yang sebenarnya adalah normal atau apakah hanya saya.

Menilai dari jumlah pandangan, jawaban, dan peningkatan pertanyaan saya yang diterima, saya hanya bisa menganggap itu masalah sah yang tidak dibahas dalam pertanyaan lain di situs web.

springloaded
sumber
20
Anekdotal, tetapi saya menemukan saya menghabiskan kira-kira sebanyak waktu menulis tes sebagai menulis kode ketika saya TDD. Justru ketika saya tergelincir dan menulis tes setelah fakta bahwa saya menghabiskan lebih banyak waktu pada tes daripada kode.
RubberDuck
10
Anda juga menghabiskan lebih banyak waktu membaca daripada menulis kode Anda.
Thorbjørn Ravn Andersen
27
Juga, tes adalah kode aktual. Anda hanya tidak mengirimkan bagian itu ke pelanggan.
Thorbjørn Ravn Andersen
5
Idealnya, Anda akan menghabiskan lebih banyak waktu untuk menjalankan daripada menulis kode Anda juga. (Kalau tidak, Anda hanya akan melakukan tugas dengan tangan.)
Joshua Taylor
5
@RubberDuck: Pengalaman yang berlawanan di sini. Kadang-kadang ketika saya menulis tes setelah faktanya kode dan desain sudah cukup rapi sehingga saya tidak perlu menulis ulang kode dan tes terlalu banyak. Jadi dibutuhkan lebih sedikit waktu untuk menulis tes. Ini bukan aturan, tapi itu sering terjadi pada saya.
Giorgio

Jawaban:

205

Saya ingat dari kursus rekayasa perangkat lunak, yang dihabiskan ~ 10% dari waktu pengembangan untuk menulis kode baru, dan 90% lainnya adalah debugging, pengujian, dan dokumentasi.

Karena unit-test menangkap debugging, dan menguji upaya ke dalam kode (yang berpotensi dapat diotomatiskan), masuk akal bahwa lebih banyak upaya yang dilakukan ke dalamnya; waktu aktual yang diambil seharusnya tidak lebih dari debugging dan pengujian yang akan dilakukan tanpa menulis tes.

Akhirnya tes juga harus berfungsi ganda sebagai dokumentasi! Seseorang harus menulis unit-test dengan cara kode dimaksudkan untuk digunakan; yaitu tes (dan penggunaan) harus sederhana, letakkan hal-hal rumit dalam implementasi.

Jika tes Anda sulit untuk ditulis, kode yang mereka uji mungkin sulit digunakan!

esoterik
sumber
4
Mungkin ini saat yang tepat untuk melihat mengapa kode ini sangat sulit untuk diuji :) Cobalah untuk "membuka gulungan" baris multi-fungsi kompleks yang tidak sepenuhnya diperlukan, seperti operator biner / ternary bersarang besar-besaran ... Saya benar-benar benci biner yang tidak perlu / operator ternary yang juga memiliki operator biner / ternary sebagai salah satu jalur ...
Nelson
53
Saya mohon tidak setuju dengan bagian terakhir. Jika Anda bertujuan untuk cakupan tes unit yang sangat tinggi, Anda perlu membahas kasus penggunaan yang jarang dan kadang-kadang bertentangan dengan tujuan penggunaan kode Anda. Tes menulis untuk kasus sudut mungkin hanya menjadi bagian yang paling memakan waktu dari seluruh tugas.
otto
Saya sudah mengatakan ini di tempat lain, tetapi pengujian unit cenderung berjalan jauh lebih lama, karena sebagian besar kode cenderung mengikuti semacam pola "Prinsip Pareto": Anda dapat mencakup sekitar 80% dari logika Anda dengan sekitar 20% dari kode yang diperlukan untuk tutupi 100% dari logika Anda (yaitu mencakup semua kasus tepi membutuhkan sekitar lima kali lebih banyak kode pengujian unit). Tentu saja, tergantung pada kerangka kerjanya, Anda dapat menginisialisasi lingkungan untuk beberapa tes, mengurangi keseluruhan kode yang diperlukan, tetapi bahkan itu membutuhkan perencanaan tambahan. Mendekati kepercayaan 100% membutuhkan lebih banyak waktu daripada sekadar menguji jalur utama.
phyrfox
2
@ phyrfox Saya pikir itu terlalu hati-hati, ini lebih seperti "99% kode lainnya adalah kasus tepi". Yang berarti 99% tes lainnya adalah untuk kasus tepi tersebut.
Moz
@Nelson Saya setuju operator ternary sulit dibaca, tetapi saya tidak berpikir mereka membuat pengujian sangat sulit (alat cakupan yang baik akan memberi tahu Anda jika Anda melewatkan salah satu kombinasi yang mungkin). IMO, perangkat lunak sulit untuk diuji ketika itu terlalu erat, atau tergantung pada data bawaan atau data yang tidak lulus sebagai parameter (misalnya ketika suatu kondisi tergantung pada waktu saat ini, dan ini tidak dilewatkan sebagai parameter). Ini tidak terkait langsung dengan seberapa "terbaca" kode itu, meskipun tentu saja, semua hal lain sama, kode yang dapat dibaca lebih baik!
Andres F.
96

Ini.

Bahkan jika Anda hanya melakukan pengujian unit, bukan tidak biasa memiliki lebih banyak kode dalam pengujian daripada kode yang sebenarnya diuji. Tidak ada yang salah dengan itu.

Pertimbangkan kode sederhana:

public void SayHello(string personName)
{
    if (personName == null) throw new NullArgumentException("personName");

    Console.WriteLine("Hello, {0}!", personName);
}

Apa yang akan menjadi tes? Setidaknya ada empat kasus sederhana untuk diuji di sini:

  1. Nama orang itu null. Apakah pengecualian benar-benar dilemparkan? Itu setidaknya tiga baris kode tes untuk ditulis.

  2. Nama orang itu "Jeff". Apakah kita mendapat "Hello, Jeff!"tanggapan? Itu empat baris kode uji.

  3. Nama orang adalah string kosong. Output apa yang kita harapkan? Apa hasil aktualnya? Pertanyaan sampingan: apakah cocok dengan persyaratan fungsional? Itu berarti empat baris kode lain untuk unit test.

  4. Nama orang cukup pendek untuk sebuah string, tetapi terlalu panjang untuk digabungkan dengan "Hello, "dan tanda seru. Apa yang terjadi? ¹

Ini membutuhkan banyak kode pengujian. Selain itu, potongan kode yang paling dasar sering memerlukan kode pengaturan yang menginisialisasi objek yang diperlukan untuk kode yang diuji, yang juga sering menyebabkan tulisan bertopik dan mengolok-olok, dll.

Jika rasionya sangat besar, dalam hal ini Anda dapat memeriksa beberapa hal:

  • Apakah ada duplikasi kode di seluruh tes? Fakta bahwa itu kode uji tidak berarti bahwa kode tersebut harus digandakan (copy-paste) antara tes yang sama: duplikasi seperti itu akan membuat pemeliharaan tes tersebut sulit.

  • Apakah ada tes yang berlebihan? Sebagai aturan praktis, jika Anda menghapus unit test, jangkauan cabang akan berkurang. Jika tidak, ini mungkin mengindikasikan bahwa tes tidak diperlukan, karena jalur sudah dicakup oleh tes lain.

  • Apakah Anda hanya menguji kode yang harus Anda uji? Anda tidak diharapkan untuk menguji kerangka kerja yang mendasari perpustakaan pihak ketiga, tetapi secara eksklusif kode proyek itu sendiri.

Dengan tes asap, tes sistem dan integrasi, tes fungsional dan penerimaan, serta tes stres dan beban, Anda menambahkan lebih banyak kode uji, sehingga memiliki empat atau lima LOC tes untuk setiap LOC kode aktual bukanlah sesuatu yang harus Anda khawatirkan.

Catatan tentang TDD

Jika Anda khawatir tentang waktu yang diperlukan untuk menguji kode Anda, mungkin Anda melakukan kesalahan, itu kode pertama, tes nanti. Dalam hal ini, TDD dapat membantu dengan mendorong Anda untuk bekerja dalam iterasi 15-45 detik, beralih antara kode dan tes. Menurut para pendukung TDD, itu mempercepat proses pengembangan dengan mengurangi baik jumlah tes yang perlu Anda lakukan dan, yang lebih penting, jumlah kode bisnis untuk menulis dan terutama menulis ulang untuk pengujian.


¹ Biarkan n menjadi panjang maksimal string . Kita dapat memanggil SayHellodan mengirim referensi panjang string n - 1 yang seharusnya bekerja dengan baik. Sekarang, pada Console.WriteLinelangkah, format harus berakhir dengan string dengan panjang n + 8, yang akan menghasilkan pengecualian. Mungkin, karena batas memori, bahkan string yang berisi n / 2 karakter akan menghasilkan pengecualian. Pertanyaan yang harus ditanyakan adalah apakah tes keempat ini adalah unit test (sepertinya tes satu, tetapi mungkin memiliki dampak yang jauh lebih tinggi dalam hal sumber daya dibandingkan dengan tes unit rata-rata) dan jika tes ini kode aktual atau kerangka kerja yang mendasarinya.

Arseni Mourzenko
sumber
5
Jangan lupa seseorang dapat memiliki nama nol juga. stackoverflow.com/questions/4456438/…
psatek
1
@JacobRaihle Saya berasumsi @MainMa berarti nilai personNamecocok dalam a string, tetapi nilai personNameditambah nilai-nilai gabungan melimpah string.
woz
@ JacobRaihle: Saya mengedit jawaban saya untuk menjelaskan poin ini. Lihat catatan kaki.
Arseni Mourzenko
4
As a rule of thumb, if you remove a unit test, the branch coverage should decrease.Jika saya menulis keempat tes yang Anda sebutkan di atas, dan kemudian menghapus tes ke-3, apakah cakupannya akan berkurang?
Vivek
3
"cukup lama" → "terlalu lama" (dalam poin 4)?
Paŭlo Ebermann
59

Saya pikir penting untuk membedakan antara dua jenis strategi pengujian: pengujian unit dan pengujian integrasi / penerimaan.

Meskipun pengujian unit diperlukan dalam beberapa kasus, ini sering dilakukan tanpa harapan. Ini diperburuk oleh metrik yang tidak berarti yang dipaksakan pada pengembang, seperti "cakupan 100%". http://www.rbcs-us.com/documents/Why-Most-Unit-Testing-is-Waste.pdf memberikan argumen yang meyakinkan untuk ini. Pertimbangkan masalah berikut dengan pengujian unit agresif:

  • Banyak sekali tes yang tidak berguna yang tidak mendokumentasikan nilai bisnis, tetapi hanya ada untuk lebih dekat dengan cakupan 100% itu. Di tempat saya bekerja, kita harus menulis unit test untuk pabrik yang tidak melakukan apa-apa selain membuat instance kelas baru. Itu tidak menambah nilai. Atau metode .equals () panjang yang dihasilkan oleh Eclipse - tidak perlu mengujinya.
  • Untuk membuat pengujian lebih mudah, pengembang akan membagi algoritma yang kompleks ke dalam unit yang lebih kecil yang dapat diuji. Kedengarannya seperti menang, bukan? Tidak jika Anda perlu memiliki 12 kelas terbuka untuk mengikuti jalur kode umum. Dalam kasus ini, pengujian unit sebenarnya dapat mengurangi pembacaan kode. Masalah lain dengan ini adalah bahwa jika Anda memotong kode Anda menjadi potongan-potongan yang terlalu kecil, Anda berakhir dengan besarnya kelas (atau potongan-potongan kode) yang tampaknya tidak memiliki pembenaran di luar menjadi sub-set potongan kode lain.
  • Refactoring kode yang sangat tertutup bisa sulit, karena Anda juga perlu mempertahankan kelimpahan tes unit yang bergantung padanya berfungsi begitu saja . Ini diperburuk oleh tes unit perilaku, di mana bagian dari tes Anda juga memverifikasi interaksi kolaborator kelas (biasanya diejek).

Pengujian integrasi / penerimaan, di sisi lain, adalah bagian yang sangat penting dari kualitas perangkat lunak, dan dalam pengalaman saya Anda harus menghabiskan banyak waktu untuk memperbaikinya.

Banyak toko yang meminum bantuan kool TDD, tetapi seperti yang ditunjukkan oleh tautan di atas, sejumlah penelitian menunjukkan bahwa manfaatnya tidak dapat disimpulkan.

firtydank
sumber
10
+1 untuk poin tentang refactoring. Saya dulu bekerja pada produk warisan yang telah ditambal dan klug selama lebih dari satu dekade. Hanya mencoba menentukan dependensi untuk metode tertentu dapat mengambil bagian yang lebih baik dalam sehari, dan kemudian mencoba mencari tahu bagaimana mengejek mereka bisa memakan waktu lebih lama. Bukan tidak biasa untuk perubahan lima baris yang membutuhkan lebih dari 200 baris kode uji, dan mengambil bagian yang lebih baik dari seminggu.
TMN
3
Ini. Dalam tes jawaban MainMa 4 tidak boleh dilakukan (di luar konteks akademik), karena pikirkan tentang bagaimana hal itu akan terjadi dalam praktik ... jika nama seseorang mendekati ukuran maksimum untuk string, maka ada sesuatu yang salah. Jangan menguji, dalam banyak kasus tidak memiliki jalur kode untuk mendeteksinya. Respons yang sesuai adalah membiarkan kerangka kerja membuang pengecualian memori yang mendasarinya, karena itulah masalah sebenarnya.
Moz
3
Saya mendukung Anda sampai "tidak perlu menguji .equals()metode panjang yang dihasilkan oleh Eclipse." Saya menulis uji harness untuk equals()dan compareTo() github.com/GlenKPeterson/TestUtils Hampir setiap implementasi yang pernah saya uji kurang. Bagaimana Anda menggunakan koleksi jika equals()dan hashCode()tidak bekerja bersama dengan benar dan efisien? Saya bersorak lagi untuk sisa jawaban Anda dan telah memilihnya. Saya bahkan mengakui bahwa beberapa metode yang dihasilkan secara otomatis sama dengan () mungkin tidak perlu pengujian, tetapi saya sudah memiliki banyak bug dengan implementasi yang buruk sehingga membuat saya gugup.
GlenPeterson
1
@GlenPeterson Setuju. Seorang kolega saya menulis EqualsVerifier untuk tujuan ini. Lihat github.com/jqno/equalsverifier
Tohnmeister
@ Ӎσᶎ tidak, Anda masih harus menguji input yang tidak dapat diterima, itulah cara orang menemukan eksploitasi keamanan.
gbjbaanb
11

Tidak bisa digeneralisasi.

Jika saya perlu menerapkan formula atau algoritma dari rendering berbasis fisik, bisa jadi saya menghabiskan 10 jam pada tes unit paranoid, karena saya tahu sedikit bug atau keputusan dapat menyebabkan bug yang hampir mustahil untuk didiagnosis, beberapa bulan kemudian .

Jika saya hanya ingin secara logis mengelompokkan beberapa baris kode dan memberikannya nama, hanya digunakan dalam lingkup file, saya mungkin tidak mengujinya sama sekali (jika Anda bersikeras menulis tes untuk setiap fungsi tunggal, tanpa kecuali, programmer dapat mundur kembali untuk menulis fungsi sesedikit mungkin).

phresnel
sumber
Ini adalah sudut pandang yang sangat berharga. Pertanyaan itu membutuhkan lebih banyak konteks untuk dijawab sepenuhnya.
CLF
3

Ya, itu normal jika Anda berbicara tentang TDDing. Ketika Anda memiliki tes otomatis, Anda mengamankan perilaku kode yang diinginkan. Saat Anda menulis tes terlebih dahulu, Anda menentukan apakah kode yang ada sudah memiliki perilaku yang Anda inginkan.

Ini berarti:

  • Jika Anda menulis tes yang gagal, maka koreksi kode dengan hal paling sederhana yang berfungsi lebih pendek daripada menulis tes.
  • Jika Anda menulis tes yang lulus, maka Anda tidak memiliki kode tambahan untuk ditulis, secara efektif menghabiskan lebih banyak waktu untuk menulis tes daripada kode.

(Ini tidak memperhitungkan refactoring kode, yang bertujuan menghabiskan lebih sedikit waktu untuk menulis kode berikutnya. Diseimbangkan dengan tes refactoring, yang bertujuan menghabiskan lebih sedikit waktu untuk menulis tes berikutnya.)

Ya juga jika Anda berbicara tentang menulis tes setelah fakta, maka Anda akan menghabiskan lebih banyak waktu:

  • Menentukan perilaku yang diinginkan.
  • Menentukan cara menguji perilaku yang diinginkan.
  • Memenuhi dependensi kode untuk dapat menulis tes.
  • Mengoreksi kode untuk tes yang gagal.

Daripada Anda akan menghabiskan benar-benar menulis kode.

Jadi ya, itu adalah ukuran yang diharapkan.

Laurent LA RIZZA
sumber
3

Saya menemukan itu menjadi bagian terpenting.

Pengujian unit tidak selalu tentang "melihat apakah itu bekerja dengan benar", ini tentang belajar. Setelah Anda cukup menguji sesuatu, itu menjadi "hardcode" ke dalam otak Anda dan Anda akhirnya menurunkan waktu pengujian unit Anda dan dapat menemukan diri Anda menulis seluruh kelas dan metode tanpa menguji apa pun sampai selesai.

Inilah sebabnya mengapa salah satu jawaban lain pada halaman ini menyebutkan bahwa dalam "kursus" mereka melakukan pengujian 90%, karena semua orang perlu mempelajari peringatan dari tujuan mereka.

Pengujian unit bukan hanya penggunaan waktu Anda yang sangat berharga karena benar-benar meningkatkan keterampilan Anda, ini adalah cara yang baik untuk memeriksa kode Anda sendiri lagi dan menemukan kesalahan logis di sepanjang jalan.

Jesse
sumber
2

Itu bisa untuk banyak orang, tetapi itu tergantung.

Jika Anda menulis tes terlebih dahulu (TDD), Anda mungkin mengalami beberapa tumpang tindih dalam waktu yang dihabiskan untuk menulis tes ini sebenarnya membantu untuk menulis kode. Mempertimbangkan:

  • Menentukan hasil dan input (parameter)
  • Konvensi Penamaan
  • Struktur - tempat meletakkan barang.
  • Pemikiran lama yang polos

Saat menulis tes setelah menulis kode, Anda mungkin menemukan kode Anda tidak mudah diuji, sehingga tes menulis lebih sulit / lebih lama.

Sebagian besar programmer telah menulis kode lebih lama daripada tes, jadi saya berharap sebagian besar dari mereka tidak lancar. Plus Anda bisa menambahkan waktu yang diperlukan untuk memahami dan memanfaatkan kerangka pengujian Anda.

Saya pikir kita perlu mengubah pola pikir kita tentang berapa lama waktu yang dibutuhkan untuk kode dan bagaimana unit testing terlibat. Jangan pernah melihatnya dalam jangka pendek dan tidak pernah hanya membandingkan total waktu untuk memberikan fitur tertentu karena Anda harus mempertimbangkan tidak hanya Anda menulis kode yang lebih baik / kurang kereta, tetapi kode yang lebih mudah untuk diubah dan tetap membuatnya lebih baik / kurang buggy.

Pada titik tertentu, kita semua hanya mampu menulis kode yang begitu bagus, sehingga beberapa alat dan teknik hanya dapat menawarkan begitu banyak dalam meningkatkan keterampilan kita. Bukannya aku bisa membangun rumah jika aku hanya punya gergaji laser.

JeffO
sumber
2

Apakah normal menghabiskan lebih banyak, jika tidak lebih, tes menulis waktu daripada kode yang sebenarnya?

Ya itu. Dengan beberapa peringatan.

Pertama-tama, adalah "normal" dalam arti bahwa sebagian besar toko besar bekerja dengan cara ini, jadi meskipun cara ini sepenuhnya salah arah dan bodoh, tetap saja, fakta bahwa sebagian besar toko besar bekerja dengan cara ini menjadikannya "normal".

Dengan ini saya tidak bermaksud mengatakan bahwa pengujian itu salah. Saya telah bekerja di lingkungan tanpa pengujian, dan di lingkungan dengan pengujian obsesif-kompulsif, dan saya masih bisa memberi tahu Anda bahwa bahkan pengujian obsesif-kompulsif lebih baik daripada tanpa pengujian.

Dan saya belum melakukan TDD, (siapa tahu, saya mungkin di masa depan), tetapi saya melakukan sebagian besar siklus edit-run-debug dengan menjalankan tes, bukan aplikasi yang sebenarnya, jadi tentu saja, saya bekerja banyak pada tes saya, sehingga untuk menghindari sebanyak mungkin harus menjalankan aplikasi yang sebenarnya.

Namun, perlu diketahui bahwa ada bahaya dalam pengujian yang berlebihan, dan khususnya dalam jumlah waktu yang dihabiskan untuk mempertahankan tes. (Saya terutama menulis jawaban ini untuk secara khusus menunjukkan hal ini.)

Dalam pengantar The Art of Unit Testing karya Roy Osherove (Manning, 2009) penulis mengakui telah berpartisipasi dalam proyek yang gagal sebagian besar karena beban pengembangan yang luar biasa yang ditimbulkan oleh tes unit yang dirancang dengan buruk yang harus dipertahankan sepanjang tahun. durasi upaya pengembangan. Jadi, jika Anda mendapati diri Anda menghabiskan terlalu banyak waktu hanya melakukan tes, ini tidak berarti Anda berada di jalur yang benar, karena itu "normal". Upaya pengembangan Anda mungkin telah memasuki mode tidak sehat, di mana pemikiran ulang radikal terhadap metodologi pengujian Anda mungkin diperlukan untuk menyelamatkan proyek.

Mike Nakis
sumber
0

Apakah normal menghabiskan lebih banyak, jika tidak lebih, tes menulis waktu daripada kode yang sebenarnya?

  • Ya untuk menulis unit-tes (Uji modul dalam isolasi) jika kode sangat digabungkan (warisan, tidak cukup pemisahan masalah , injeksi ketergantungan tidak ada, tidak dikembangkan tdd )
  • Ya untuk menulis tes integrasi / penerimaan jika logika yang akan diuji hanya dapat dicapai melalui kode-gui
  • Tidak untuk menulis tes integrasi / penerimaan selama kode gui dan logika bisnis dipisahkan (Tes tidak perlu berinteraksi dengan gui)
  • Tidak untuk menulis unit-tes jika ada kekhawatiran terpisah, injeksi dependensi, kode diuji dikembangkan (tdd)
k3b
sumber