Haruskah kita merancang kode kita dari awal untuk mengaktifkan pengujian unit?

91

Saat ini ada perdebatan di tim kami mengenai apakah memodifikasi desain kode untuk memungkinkan pengujian unit adalah bau kode, atau sejauh mana hal itu dapat dilakukan tanpa menjadi bau kode. Ini terjadi karena kami baru saja mulai menerapkan praktik yang ada di hampir setiap perusahaan pengembang perangkat lunak lainnya.

Secara khusus, kami akan memiliki layanan API Web yang akan sangat tipis. Tanggung jawab utamanya adalah menyusun permintaan / tanggapan web dan memanggil API yang mendasari yang berisi logika bisnis.

Salah satu contoh adalah bahwa kami berencana membuat pabrik yang akan mengembalikan jenis metode otentikasi. Kami tidak memerlukannya untuk mewarisi antarmuka karena kami tidak mengantisipasinya selain jenis konkretnya. Namun, untuk menguji unit layanan Web API kita perlu mengejek pabrik ini.

Ini pada dasarnya berarti kita merancang kelas pengontrol API Web untuk menerima DI (melalui konstruktor atau setternya), yang berarti kita sedang merancang bagian dari pengontrol hanya untuk memungkinkan DI dan mengimplementasikan antarmuka yang tidak kita perlukan, atau kita menggunakan kerangka kerja pihak ketiga seperti Ninject untuk menghindari keharusan mendesain controller dengan cara ini, tetapi kita masih harus membuat antarmuka.

Beberapa anggota tim tampaknya enggan mendesain kode hanya untuk kepentingan pengujian. Tampaknya bagi saya bahwa harus ada kompromi jika Anda berharap untuk unit test, tapi saya tidak yakin bagaimana menghilangkan kekhawatiran mereka.

Untuk lebih jelasnya, ini adalah proyek baru, jadi ini bukan tentang memodifikasi kode untuk mengaktifkan pengujian unit; ini tentang mendesain kode yang akan kita tulis agar unit dapat diuji.

Lee
sumber
33
Biarkan saya ulangi ini: Anda rekan ingin unit test untuk kode baru, tetapi mereka menolak untuk menulis kode dengan cara itu unit testable, meskipun tidak ada risiko dalam memecahkan sesuatu yang ada? Jika itu benar, Anda harus menerima jawaban @ KilianFoth dan memintanya untuk menyorot kalimat pertama dalam jawabannya dengan huruf tebal! Rekan kerja Anda tampaknya memiliki kesalahpahaman yang sangat besar tentang apa pekerjaan mereka.
Doc Brown
20
@ Lee: Siapa bilang decoupling selalu merupakan ide yang bagus? Pernahkah Anda melihat basis kode di mana semuanya dilewatkan sebagai antarmuka yang dibuat dari pabrik antarmuka menggunakan beberapa antarmuka konfigurasi? Saya sudah; itu ditulis di Jawa, dan itu adalah kekacauan yang lengkap, tidak dapat dipelihara, dan bermasalah. Decoupling ekstrim adalah kebingungan kode.
Christian Hackl
8
Bekerja dengan Efektif dengan Legacy Code dari Michael Feathers sangat baik menangani masalah ini, dan seharusnya memberi Anda ide bagus tentang keuntungan pengujian bahkan dalam basis kode baru.
l0b0
8
@ l0b0 Cukup banyak Alkitab untuk ini. Pada stackexchange, ini bukan jawaban untuk pertanyaan, tetapi dalam RL saya akan memberi tahu OP agar buku ini dibaca (setidaknya sebagian). OP, dapatkan Bekerja Efektif dengan Legacy Code dan bacalah, setidaknya sebagian (atau beri tahu atasan Anda untuk mendapatkannya). Ini membahas pertanyaan-pertanyaan seperti ini. Terutama jika Anda tidak melakukan pengujian dan sekarang Anda melakukannya - Anda mungkin memiliki 20 tahun pengalaman, tetapi sekarang Anda akan melakukan hal-hal yang tidak Anda alami . Jauh lebih mudah untuk membaca tentang mereka daripada dengan susah payah mempelajari semua itu dengan coba-coba.
R. Schmitz
4
Terima kasih atas rekomendasi buku Michael Feathers, saya pasti akan mengambil salinannya.
Lee

Jawaban:

204

Keengganan untuk memodifikasi kode demi pengujian menunjukkan bahwa pengembang belum memahami peran pengujian, dan implikasinya, peran mereka sendiri dalam organisasi.

Bisnis perangkat lunak berputar di sekitar memberikan basis kode yang menciptakan nilai bisnis. Kami telah menemukan, melalui pengalaman yang panjang dan pahit, bahwa kami tidak dapat membuat basis kode dengan ukuran nontrivial tanpa pengujian. Oleh karena itu, suite tes merupakan bagian integral dari bisnis.

Banyak coders yang tidak setuju dengan prinsip ini tetapi secara tidak sadar tidak pernah menerimanya. Sangat mudah untuk memahami mengapa ini terjadi; kesadaran bahwa kemampuan mental kita sendiri tidak terbatas, dan pada kenyataannya, secara mengejutkan terbatas ketika dihadapkan dengan kompleksitas yang sangat besar dari basis kode modern, tidak disukai dan dengan mudah ditekan atau dirasionalisasi. Fakta bahwa kode pengujian tidak dikirimkan kepada pelanggan membuatnya mudah untuk percaya bahwa itu adalah warga negara kelas dua dan tidak penting dibandingkan dengan kode bisnis "esensial". Dan gagasan menambahkan kode pengujian ke kode bisnis tampaknya menyinggung banyak orang.

Masalah dengan membenarkan praktik ini berkaitan dengan fakta bahwa seluruh gambaran tentang bagaimana nilai dibuat dalam bisnis perangkat lunak sering hanya dipahami oleh atasan dalam hierarki perusahaan, tetapi orang-orang ini tidak memiliki pemahaman teknis terperinci tentang alur kerja pengkodean yang diperlukan untuk memahami mengapa pengujian tidak bisa dihilangkan. Karena itu mereka terlalu sering ditenangkan oleh para praktisi yang meyakinkan mereka bahwa pengujian mungkin merupakan ide yang baik secara umum, tetapi "kami adalah programmer elit yang tidak membutuhkan kruk seperti itu", atau bahwa "kami tidak punya waktu untuk itu sekarang", dll. Fakta bahwa kesuksesan bisnis adalah permainan angka dan menghindari hutang teknis, memastikan kualitas dll. menunjukkan nilainya hanya dalam jangka panjang berarti bahwa mereka seringkali cukup tulus dalam kepercayaan itu.

Singkatnya: membuat kode dapat diuji adalah bagian penting dari proses pengembangan, tidak berbeda dengan di bidang lain (banyak microchip dirancang dengan proporsi elemen yang substansial hanya untuk tujuan pengujian), tetapi sangat mudah untuk mengabaikan alasan yang sangat bagus untuk bahwa. Jangan jatuh ke dalam perangkap itu.

Kilian Foth
sumber
39
Saya berpendapat itu tergantung pada jenis perubahan. Ada perbedaan antara membuat kode lebih mudah untuk diuji, dan memperkenalkan kait khusus uji yang TIDAK PERNAH digunakan dalam produksi. Saya pribadi waspada terhadap yang terakhir, karena Murphy ...
Matthieu M.
61
Tes unit sering merusak enkapsulasi dan membuat kode yang diuji lebih kompleks dari yang seharusnya diperlukan (misalnya dengan memperkenalkan jenis antarmuka tambahan atau menambahkan flag). Seperti biasa dalam rekayasa perangkat lunak, setiap praktik yang baik dan setiap aturan yang baik memiliki bagian kewajiban. Secara buta memproduksi banyak unit test dapat memiliki efek yang merugikan pada nilai bisnis, belum lagi bahwa menulis dan memelihara tes sudah menghabiskan waktu dan tenaga. Dalam pengalaman saya, tes integrasi memiliki ROI yang jauh lebih besar dan cenderung meningkatkan arsitektur perangkat lunak dengan kompromi yang lebih sedikit.
Christian Hackl
20
@ Lee Tentu tetapi Anda perlu mempertimbangkan apakah memiliki jenis tes tertentu menjamin peningkatan kompleksitas kode. Pengalaman pribadi saya adalah bahwa unit test adalah alat yang hebat sampai pada titik di mana mereka memerlukan perubahan desain mendasar untuk mengakomodasi ejekan. Di situlah saya beralih ke jenis tes yang berbeda. Tes unit menulis dengan mengorbankan membuat arsitektur secara substansial lebih kompleks, untuk tujuan tunggal memiliki unit test, menatap ke bawah.
Konrad Rudolph
21
@ChristianHackl mengapa unit test istirahat enkapsulasi? Saya telah menemukan bahwa untuk kode yang telah saya kerjakan, jika ada kebutuhan yang dirasakan untuk menambahkan fungsionalitas tambahan untuk mengaktifkan pengujian, masalah sebenarnya adalah bahwa fungsi yang akan Anda uji perlu refactoring, sehingga semua fungsionalitas pada saat yang sama level abstraction (perbedaan level abstraksi yang biasanya membuat "kebutuhan" untuk kode tambahan ini), dengan kode level yang lebih rendah dipindahkan ke fungsinya sendiri (yang dapat diuji).
Baldrickk
29
@ChristianHackl Unit test tidak boleh merusak enkapsulasi, jika Anda mencoba mengakses variabel pribadi, dilindungi, atau lokal dari unit test, Anda salah melakukannya. Jika Anda menguji fungsionalitas, Anda hanya menguji apakah itu benar-benar berfungsi, bukan jika variabel lokal x adalah akar kuadrat dari input y pada iterasi ketiga dari loop kedua. Jika beberapa fungsionalitas bersifat pribadi maka biarlah, Anda akan mengujinya secara transitif. apakah itu benar-benar besar dan pribadi? Itu cacat desain, tetapi mungkin bahkan tidak mungkin di luar C dan C ++ dengan pemisahan implementasi header.
opa
75

Ini tidak sesederhana yang Anda bayangkan. Mari kita jabarkan.

  • Tes unit menulis jelas merupakan hal yang baik.

TAPI!

  • Setiap perubahan pada kode Anda dapat menimbulkan bug. Jadi mengubah kode tanpa alasan bisnis yang baik bukanlah ide yang baik.

  • Webapi 'sangat tipis' Anda sepertinya bukan kasus terbesar untuk pengujian unit.

  • Mengubah kode dan tes pada saat yang sama adalah hal yang buruk.

Saya akan menyarankan pendekatan berikut:

  1. Tulis tes integrasi . Ini seharusnya tidak memerlukan perubahan kode apa pun. Ini akan memberi Anda kasus uji dasar dan memungkinkan Anda untuk memeriksa bahwa perubahan kode lebih lanjut yang Anda buat tidak menimbulkan bug.

  2. Pastikan kode baru dapat diuji dan memiliki tes unit dan integrasi.

  3. Pastikan rantai CI Anda menjalankan tes setelah pembuatan dan penerapan.

Ketika Anda sudah menyiapkan hal-hal itu, baru mulai berpikir tentang refactoring proyek warisan untuk diuji.

Semoga semua orang mendapat pelajaran dari proses tersebut dan memiliki ide bagus tentang di mana pengujian sangat dibutuhkan, bagaimana Anda ingin menyusunnya dan nilai yang diberikannya pada bisnis.

EDIT : Sejak saya menulis jawaban ini, OP telah mengklarifikasi pertanyaan untuk menunjukkan bahwa mereka berbicara tentang kode baru, bukan modifikasi pada kode yang ada. Saya mungkin secara naif berpikir, "Apakah pengujian unit bagus?" Argumen diselesaikan beberapa tahun lalu.

Sulit membayangkan perubahan kode apa yang diperlukan oleh unit test tetapi bukan praktik umum yang baik yang Anda inginkan dalam hal apa pun. Mungkin akan bijaksana untuk memeriksa keberatan yang sebenarnya, mungkin itu adalah gaya pengujian unit yang sedang keberatan.

Ewan
sumber
12
Ini adalah jawaban yang jauh lebih baik daripada yang diterima. Ketidakseimbangan dalam pemilihan ini mencemaskan.
Konrad Rudolph
4
@Lee Tes unit harus menguji unit fungsionalitas , yang mungkin berhubungan atau tidak dengan kelas. Unit fungsionalitas harus diuji pada antarmuka (yang mungkin merupakan API dalam hal ini). Pengujian dapat menyoroti bau desain dan kebutuhan untuk menerapkan beberapa levelisasi yang berbeda / lebih. Bangun sistem Anda dari potongan-potongan kecil yang dapat digabungkan, mereka akan lebih mudah untuk dipertimbangkan dan diuji.
Wes Toleman
2
@KonradRudolph: Saya kira melewatkan titik di mana OP menambahkan bahwa pertanyaan ini adalah tentang merancang kode baru, bukan mengubah yang sudah ada. Jadi tidak ada yang perlu dipecahkan, yang membuat sebagian besar jawaban ini tidak berlaku.
Doc Brown
1
Saya sangat tidak setuju dengan pernyataan bahwa menulis unit test selalu merupakan hal yang baik. Tes unit hanya baik dalam beberapa kasus. Konyol menggunakan unit test untuk menguji kode frontend (UI), mereka dibuat untuk menguji logika bisnis. Juga, ada baiknya untuk menulis unit test untuk menggantikan pemeriksaan kompilasi yang hilang (mis. Dalam Javascript). Kebanyakan kode frontend-only harus menulis tes ujung ke ujung secara eksklusif, bukan tes unit.
Sulthan
1
Desain pasti dapat menderita "Uji kerusakan yang diinduksi". Biasanya testability meningkatkan desain: Anda perhatikan saat menulis tes bahwa sesuatu tidak dapat diambil tetapi harus dilewatkan, membuat antarmuka yang lebih jelas dan sebagainya. Tetapi kadang-kadang Anda akan menemukan sesuatu yang membutuhkan desain yang tidak nyaman hanya untuk pengujian. Contoh dapat berupa konstruktor khusus uji yang diperlukan dalam kode baru Anda karena kode pihak ketiga yang ada yang menggunakan singleton misalnya. Ketika itu terjadi: mundur selangkah dan buat tes integrasi saja, alih-alih merusak desain Anda sendiri atas nama testabilitas.
Anders Forsgren
18

Merancang kode agar bisa diuji secara inheren bukanlah bau kode; sebaliknya, itu adalah tanda desain yang bagus. Ada beberapa pola desain yang terkenal dan banyak digunakan berdasarkan ini (misalnya, Model-View-Presenter) yang menawarkan pengujian mudah (lebih mudah) sebagai keuntungan besar.

Jadi, jika Anda perlu menulis antarmuka untuk kelas beton Anda agar lebih mudah mengujinya, itu adalah hal yang baik. Jika Anda sudah memiliki kelas konkret, sebagian besar IDE dapat mengekstrak antarmuka darinya, membuat upaya yang diperlukan minimal. Ini sedikit lebih banyak pekerjaan untuk menjaga keduanya dalam sinkronisasi, tetapi antarmuka tidak boleh banyak berubah, dan manfaat dari pengujian mungkin lebih besar daripada upaya ekstra.

Di sisi lain, sebagai @MatthieuM. disebutkan dalam komentar, jika Anda menambahkan titik masuk spesifik ke dalam kode Anda yang seharusnya tidak pernah digunakan dalam produksi, semata-mata demi pengujian, itu mungkin menjadi masalah.

mmathis
sumber
Masalah itu dapat diselesaikan melalui analisis kode statis - tandai metode (misalnya harus dinamai _ForTest) dan periksa basis kode untuk panggilan dari kode non-tes.
Bersepeda
13

Ini adalah IMHO sangat sederhana untuk memahami bahwa untuk membuat unit test, kode yang akan diuji harus memiliki setidaknya beberapa properti tertentu. Misalnya, jika kode tidak terdiri dari unit-unit individual yang dapat diuji secara terpisah, kata "unit testing" bahkan tidak masuk akal. Jika kode tidak memiliki properti ini, itu harus diubah terlebih dahulu, itu cukup jelas.

Mengatakan bahwa, secara teori, seseorang dapat mencoba untuk menulis beberapa unit kode yang dapat diuji terlebih dahulu, menerapkan semua prinsip SOLID, dan kemudian mencoba untuk menulis tes untuk itu setelahnya, tanpa lebih lanjut memodifikasi kode asli. Sayangnya, penulisan kode yang benar-benar unit yang dapat diuji tidak selalu sederhana, sehingga sangat mungkin akan ada beberapa perubahan yang diperlukan yang hanya akan terdeteksi ketika mencoba membuat tes. Ini berlaku untuk kode bahkan ketika ditulis dengan ide pengujian unit dalam pikiran, dan jelas lebih benar untuk kode yang ditulis di mana "unit testability" tidak ada dalam agenda di awal.

Ada pendekatan terkenal yang mencoba memecahkan masalah dengan menulis unit test terlebih dahulu - ini disebut Test Driven Development (TDD), dan tentunya dapat membantu membuat kode lebih unit dapat diuji langsung dari awal.

Tentu saja, keengganan untuk mengubah kode setelah itu agar dapat diuji muncul sering dalam situasi di mana kode diuji secara manual terlebih dahulu dan / atau berfungsi dengan baik dalam prodcution, jadi mengubah itu sebenarnya dapat memperkenalkan bug baru, itu benar. Pendekatan terbaik untuk memitigasi hal ini adalah dengan membuat suite uji regresi terlebih dahulu (yang sering dapat diimplementasikan dengan perubahan yang sangat minimal pada basis kode), serta langkah-langkah lain yang menyertainya seperti ulasan kode, atau sesi tes manual baru. Itu harus Anda berikan kepercayaan yang cukup untuk memastikan mendesain ulang beberapa internal tidak merusak sesuatu yang penting.

Doc Brown
sumber
Menarik bahwa Anda menyebutkan TDD. Kami mencoba untuk membawa BDD / TDD, yang juga telah menemui beberapa hambatan - yaitu arti dari "kode minimum yang harus dilewati".
Lee
2
@ Lee: membawa perubahan ke dalam organisasi selalu menyebabkan perlawanan, dan selalu membutuhkan waktu untuk mengadaptasi hal-hal baru, itu bukan kebijaksanaan baru. Ini masalah orang.
Doc Brown
Benar. Aku hanya berharap kita diberi lebih banyak waktu!
Lee
Sering kali menunjukkan kepada orang lain bahwa melakukannya dengan cara ini akan menghemat waktu mereka (dan mudah-mudahan juga cepat). Mengapa melakukan sesuatu yang tidak akan menguntungkan Anda?
Thorbjørn Ravn Andersen
@ ThorbjørnRavnAndersen: Tim mungkin juga menunjukkan kepada OP bahwa pendekatan mereka akan menghemat waktu. Siapa tahu? Tapi saya ingin tahu apakah kita sebenarnya tidak menghadapi masalah yang kurang teknis di sini; OP terus datang ke sini untuk memberi tahu kami apa yang salah timnya (menurut pendapatnya), seolah-olah ia berusaha mencari sekutu untuk tujuannya. Mungkin lebih bermanfaat untuk mendiskusikan proyek bersama dengan tim, bukan dengan orang asing di Stack Exchange.
Christian Hackl
11

Saya menerima masalah dengan pernyataan (tidak berdasar) yang Anda buat:

untuk menguji layanan API Web, kita perlu mengejek pabrik ini

Itu belum tentu benar. Ada banyak cara untuk menulis tes, dan ada yang cara untuk menulis tes unit yang tidak melibatkan mengolok-olok. Lebih penting lagi, ada jenis tes lain, seperti tes fungsional atau integrasi. Sering kali mungkin untuk menemukan "test seam" di "antarmuka" yang bukan bahasa pemrograman OOP interface.

Beberapa pertanyaan untuk membantu Anda menemukan jahitan tes alternatif, yang mungkin lebih alami:

  • Akankah saya ingin menulis Web API tipis di atas API yang berbeda ?
  • Bisakah saya mengurangi duplikasi kode antara API Web dan API yang mendasarinya? Bisakah satu dihasilkan dari yang lain?
  • Dapatkah saya memperlakukan seluruh API Web dan API yang mendasarinya sebagai satu unit "kotak hitam" dan secara bermakna membuat pernyataan tentang bagaimana keseluruhannya berperilaku?
  • Jika API Web harus diganti dengan implementasi baru di masa depan, bagaimana kita melakukannya?
  • Jika API Web diganti dengan implementasi baru di masa mendatang, apakah klien API Web dapat memperhatikan? Jika ya, bagaimana caranya?

Pernyataan tidak berdasar lain yang Anda buat adalah tentang DI:

kami merancang kelas pengontrol API Web untuk menerima DI (melalui konstruktor atau setternya), yang berarti kami merancang bagian dari pengontrol hanya untuk mengizinkan DI dan mengimplementasikan antarmuka yang tidak kami perlukan, atau kami menggunakan pihak ketiga Kerangka kerja seperti Ninject untuk menghindari harus merancang controller dengan cara ini, tetapi kita masih harus membuat antarmuka.

Ketergantungan injeksi tidak harus berarti membuat yang baru interface. Misalnya, dalam penyebab token otentikasi: dapat Anda cukup membuat nyata otentikasi tanda pemrograman? Kemudian tes dapat membuat token tersebut dan menyuntikkannya. Apakah proses untuk memvalidasi token bergantung pada semacam rahasia kriptografi? Saya harap Anda tidak memiliki hardcoded rahasia - saya berharap Anda dapat membacanya dari penyimpanan entah bagaimana, dan dalam hal ini Anda dapat menggunakan rahasia (terkenal) yang berbeda dalam kasus uji Anda.

Ini bukan untuk mengatakan bahwa Anda tidak boleh membuat yang baru interface. Tetapi jangan terpaku pada hanya ada satu cara untuk menulis tes, atau satu cara untuk memalsukan perilaku. Jika Anda berpikir di luar kotak, Anda biasanya dapat menemukan solusi yang akan membutuhkan sedikit kontorsi kode Anda, namun tetap memberi Anda efek yang Anda inginkan.

Daniel Pryden
sumber
Poin diambil tentang pernyataan tentang antarmuka, tetapi bahkan jika kita tidak menggunakannya kita masih harus menyuntikkan objek beberapa cara, ini adalah perhatian dari seluruh tim. yaitu beberapa di tim akan senang dengan ctr parameterless instantiating implementasi konkret dan membiarkannya begitu saja. Bahkan satu anggota melayangkan ide menggunakan refleksi untuk menyuntikkan ejekan sehingga kita tidak perlu merancang kode untuk menerimanya. Yang merupakan imo bau kode bau
Lee
9

Anda beruntung karena ini adalah proyek baru. Saya telah menemukan bahwa Test Driven Design bekerja sangat baik untuk menulis kode yang baik (itulah sebabnya kami melakukannya di tempat pertama).

Dengan mencari tahu di muka bagaimana memanggil sepotong kode tertentu dengan data input realistis, dan kemudian mendapatkan data output realistis yang dapat Anda periksa sebagaimana dimaksud, Anda melakukan desain API sangat awal dalam proses dan memiliki peluang bagus untuk mendapatkan desain yang berguna karena Anda tidak terhalang oleh kode yang ada yang harus ditulis ulang untuk mengakomodasi. Juga lebih mudah dipahami oleh rekan-rekan Anda sehingga Anda dapat melakukan diskusi yang baik lagi di awal proses.

Perhatikan bahwa "berguna" dalam kalimat di atas tidak hanya berarti bahwa metode yang dihasilkan mudah dipanggil, tetapi juga bahwa Anda cenderung mendapatkan antarmuka yang bersih yang mudah dipasang dalam tes integrasi, dan untuk menulis maket.

Pikirkan itu. Apalagi dengan peer review. Dalam pengalaman saya, investasi waktu dan usaha akan sangat cepat dikembalikan.

Thorbjørn Ravn Andersen
sumber
Kami memiliki masalah dengan TDD juga, yaitu, apa yang merupakan "kode minimum untuk lulus". Saya menunjukkan kepada tim proses ini dan mereka mengambil pengecualian untuk tidak hanya menulis apa yang telah kami rancang - yang dapat saya pahami. "Minimum" tampaknya tidak didefinisikan. Jika kita menulis tes dan memiliki rencana serta desain yang jelas, mengapa tidak menulis itu untuk lulus tes?
Lee
@Lee "kode minimum untuk lulus" ... well, ini mungkin terdengar agak bodoh, tapi secara harfiah apa yang dikatakannya. Misalnya jika Anda memiliki tes UserCanChangeTheirPassword, maka dalam tes Anda memanggil fungsi (belum ada) untuk mengubah kata sandi dan kemudian Anda menyatakan bahwa kata sandi memang diubah. Kemudian Anda menulis fungsi, sampai Anda dapat menjalankan tes dan tidak membuang pengecualian atau memiliki pernyataan yang salah. Jika pada saat itu Anda punya alasan untuk menambahkan kode, maka alasan itu masuk ke tes lain, misalnya UserCantChangePasswordToEmptyString.
R. Schmitz
@ Lee Pada akhirnya, tes Anda pada akhirnya akan menjadi dokumentasi dari apa yang kode Anda lakukan, kecuali dokumentasinya yang memeriksa apakah sudah terpenuhi dengan sendirinya, alih-alih hanya berupa tinta di atas kertas. Bandingkan juga dengan pertanyaan ini - Metode CalculateFactorialyang hanya mengembalikan 120 dan tes berlalu. Itu adalah minimum. Ini juga jelas bukan apa yang dimaksudkan, tetapi itu hanya berarti Anda perlu tes lain untuk mengekspresikan apa yang dimaksudkan.
R. Schmitz
1
@ Lee Langkah kecil. Minimal mungkin lebih dari yang Anda pikirkan ketika kode naik di atas sepele. Juga desain yang Anda lakukan ketika mengimplementasikan semuanya sekaligus mungkin kurang optimal karena Anda membuat asumsi tentang bagaimana hal itu harus dilakukan tanpa harus menulis tes yang menunjukkannya. Sekali lagi ingat, kode harus gagal pada awalnya.
Thorbjørn Ravn Andersen
1
Juga, tes regresi sangat penting. Apakah mereka ada dalam ruang lingkup untuk tim?
Thorbjørn Ravn Andersen
8

Jika Anda perlu memodifikasi kode, itu adalah bau kode.

Dari pengalaman pribadi, jika kode saya sulit untuk menulis tes, itu kode yang buruk. Ini bukan kode yang buruk karena tidak berjalan atau berfungsi seperti yang dirancang, ini buruk karena saya tidak dapat dengan cepat memahami mengapa ia bekerja. Jika saya menemukan bug, saya tahu itu akan menjadi pekerjaan yang menyakitkan untuk memperbaikinya. Kode ini juga sulit / tidak mungkin untuk digunakan kembali.

Kode Good (Clean) memecah tugas menjadi bagian-bagian yang lebih kecil yang mudah dipahami dalam sekejap (atau setidaknya terlihat bagus). Menguji bagian yang lebih kecil ini mudah. Saya juga dapat menulis tes yang hanya menguji sepotong basis kode dengan kemudahan yang sama jika saya cukup yakin tentang subbagian (menggunakan kembali juga membantu di sini karena telah diuji).

Buat kode mudah untuk diuji, mudah untuk refactor, dan mudah untuk digunakan kembali dari awal dan Anda tidak akan bunuh diri kapan pun Anda perlu melakukan perubahan.

Saya sedang mengetik ini saat benar-benar membangun kembali sebuah proyek yang seharusnya menjadi prototipe sekali pakai menjadi kode yang lebih bersih. Adalah jauh lebih baik untuk memperbaikinya sejak awal dan memperbaiki kode buruk sesegera mungkin daripada menatap layar selama berjam-jam karena takut menyentuh apa pun karena takut merusak sesuatu yang sebagian berfungsi.

David
sumber
3
"Throwaway prototype" - setiap proyek memulai kehidupan sebagai salah satu dari ... yang terbaik untuk memikirkan hal-hal yang tidak pernah seperti itu. mengetik ini karena saya .. coba tebak? ... refactoring prototipe sekali pakai yang ternyata bukan;)
Algy Taylor
4
Jika Anda ingin memastikan bahwa prototipe yang dibuang akan dibuang, tulis dalam bahasa prototipe yang tidak akan pernah diizinkan diproduksi. Clojure dan Python adalah pilihan yang bagus.
Thorbjørn Ravn Andersen
2
@ ThorbjørnRavnAndersen Itu membuat saya tertawa. Apakah itu dimaksudkan untuk menggali bahasa-bahasa itu? :)
Lee
@Lee. Tidak, hanya contoh bahasa yang mungkin tidak dapat diterima untuk produksi - biasanya karena tidak ada dalam organisasi yang dapat mempertahankannya karena mereka tidak terbiasa dengan mereka dan kurva belajar mereka curam. Jika itu, dapat diterima pilih yang lain.
Thorbjørn Ravn Andersen
4

Saya berpendapat bahwa menulis kode yang tidak dapat diuji unit adalah bau kode. Secara umum, jika kode Anda tidak dapat diuji unit, maka itu bukan modular, yang membuatnya sulit untuk dipahami, dipertahankan, atau ditingkatkan. Mungkin jika kode tersebut adalah kode lem yang benar-benar hanya masuk akal dalam hal pengujian integrasi, Anda dapat mengganti pengujian integrasi untuk pengujian unit, tetapi meskipun integrasi gagal, Anda harus mengisolasi masalahnya dan pengujian unit adalah cara yang bagus untuk lakukan.

Kamu bilang

Kami berencana membuat pabrik yang akan mengembalikan jenis metode otentikasi. Kami tidak memerlukannya untuk mewarisi antarmuka karena kami tidak mengantisipasinya selain jenis konkretnya. Namun, untuk menguji unit layanan Web API kita perlu mengejek pabrik ini.

Saya tidak benar-benar mengikuti ini. Alasan memiliki pabrik yang menciptakan sesuatu adalah untuk memungkinkan Anda mengubah pabrik atau mengubah apa yang dibuat pabrik dengan mudah, sehingga bagian lain dari kode tidak perlu diubah. Jika metode otentikasi Anda tidak akan pernah berubah, maka pabrik itu adalah kode yang tidak berguna. Namun, jika Anda ingin memiliki metode otentikasi yang berbeda dalam pengujian daripada dalam produksi, memiliki pabrik yang mengembalikan metode otentikasi yang berbeda dalam pengujian daripada dalam produksi adalah solusi yang bagus.

Anda tidak perlu DI atau mengejek untuk ini. Anda hanya perlu pabrik Anda untuk mendukung berbagai jenis otentikasi dan untuk itu dapat dikonfigurasi, entah bagaimana, dari file konfigurasi atau variabel lingkungan.

Pro tua
sumber
2

Dalam setiap disiplin teknik yang dapat saya pikirkan, hanya ada satu cara untuk mencapai tingkat kualitas yang layak atau lebih tinggi:

Untuk memperhitungkan inspeksi / pengujian dalam desain.

Ini berlaku dalam konstruksi, desain chip, pengembangan perangkat lunak, dan manufaktur. Sekarang, ini tidak berarti bahwa pengujian adalah pilar yang harus dibangun setiap desain, bukan sama sekali. Tetapi dengan setiap keputusan desain, perancang harus jelas tentang dampak pada biaya pengujian dan membuat keputusan sadar tentang trade off.

Dalam beberapa kasus, pengujian manual atau otomatis (misalnya Selenium) akan lebih nyaman daripada Tes Unit, sementara juga memberikan cakupan tes yang dapat diterima sendiri. Dalam kasus yang jarang terjadi, melemparkan sesuatu ke luar sana yang hampir seluruhnya belum diuji juga dapat diterima. Tetapi ini harus menjadi kasus per kasus keputusan sadar. Memanggil desain yang diperhitungkan untuk menguji "bau kode" menunjukkan kurangnya pengalaman yang serius.

Peter
sumber
1

Saya telah menemukan bahwa pengujian unit (dan jenis pengujian otomatis lainnya) memiliki kecenderungan untuk mengurangi bau kode, dan saya tidak bisa memikirkan satu contoh pun di mana mereka memperkenalkan bau kode. Tes unit biasanya memaksa Anda untuk menulis kode yang lebih baik. Jika Anda tidak dapat menggunakan metode dengan mudah dalam pengujian, mengapa lebih mudah dalam kode Anda?

Tes unit yang ditulis dengan baik menunjukkan kepada Anda bagaimana kode ini dimaksudkan untuk digunakan. Mereka adalah bentuk dokumentasi yang dapat dieksekusi. Saya telah melihat tes unit yang ditulis dengan sangat mengerikan dan terlalu panjang yang tidak dapat dipahami. Jangan tulis itu! Jika Anda perlu menulis tes panjang untuk mengatur kelas Anda, kelas Anda perlu refactoring.

Tes unit akan menyoroti di mana beberapa kode Anda berbau. Saya akan menyarankan membaca Michael C. Feathers ' Bekerja Efektif dengan Legacy Code . Meskipun proyek Anda baru, jika belum memiliki (atau banyak) pengujian unit, Anda mungkin memerlukan beberapa teknik yang tidak jelas untuk membuat kode Anda diuji dengan baik.

CJ Dennis
sumber
3
Anda bisa tergoda untuk memperkenalkan banyak lapisan tipuan agar dapat menguji, dan kemudian tidak pernah menggunakannya seperti yang diharapkan.
Thorbjørn Ravn Andersen
1

Pendeknya:

Kode yang dapat diuji adalah (biasanya) kode yang dapat dipelihara - atau lebih tepatnya, kode yang sulit untuk diuji biasanya sulit untuk dipelihara. Merancang kode yang tidak dapat diuji mirip dengan merancang mesin yang tidak dapat diperbaiki - kasihan orang miskin yang akan ditugaskan untuk memperbaikinya pada akhirnya (mungkin Anda).

Salah satu contoh adalah bahwa kami berencana membuat pabrik yang akan mengembalikan jenis metode otentikasi. Kami tidak memerlukannya untuk mewarisi antarmuka karena kami tidak mengantisipasinya selain jenis konkretnya.

Anda tahu bahwa Anda akan memerlukan lima jenis tipe metode otentikasi dalam tiga tahun, setelah Anda mengatakannya, bukan? Persyaratan berubah, dan walaupun Anda harus menghindari rekayasa yang berlebihan pada desain Anda, memiliki desain yang dapat diuji berarti bahwa desain Anda memiliki (hanya) jahitan yang cukup untuk diubah tanpa (terlalu banyak) rasa sakit - dan bahwa tes modul akan memberi Anda cara otomatis untuk melihat bahwa perubahan Anda tidak merusak apa pun.

CharonX
sumber
1

Mendesain injeksi ketergantungan bukanlah bau kode - ini praktik terbaik. Menggunakan DI tidak hanya untuk testabilitas. Membangun komponen-komponen Anda di sekitar DI bantu modularitas dan usabilitas, lebih mudah memungkinkan komponen utama untuk ditukar (seperti lapisan antarmuka basis data). Sementara itu menambah tingkat kerumitan, dilakukan dengan benar memungkinkan untuk pemisahan lapisan yang lebih baik dan isolasi fungsionalitas yang membuat kompleksitas lebih mudah untuk dikelola dan dinavigasi. Ini membuatnya lebih mudah untuk memvalidasi perilaku masing-masing komponen dengan benar, mengurangi bug, dan juga dapat mempermudah melacak bug.

Zenilogix
sumber
1
"dilakukan dengan benar" adalah masalah. Saya harus memelihara dua proyek di mana DI salah dilakukan (walaupun bertujuan untuk melakukannya "benar"). Ini membuat kode ini benar-benar mengerikan dan jauh lebih buruk daripada proyek lawas tanpa DI dan pengujian unit. Mendapatkan DI yang benar tidak mudah.
Jan
@ Jan Itu menarik. Bagaimana mereka melakukan kesalahan?
Lee
1
@Lee One proyek adalah layanan yang membutuhkan waktu mulai yang cepat tetapi sangat lambat di awal karena semua inisialisasi kelas dilakukan dimuka oleh kerangka kerja DI (Castle Windsor dalam C #). Masalah lain yang saya lihat dalam proyek-proyek ini adalah mencampur DI dengan penciptaan objek dengan "baru", menghindari DI. Itu membuat pengujian sulit lagi dan menyebabkan beberapa kondisi balapan tidak menyenangkan.
Jan
1

Ini pada dasarnya berarti kita merancang kelas pengontrol API Web untuk menerima DI (melalui konstruktor atau setternya), yang berarti kita sedang merancang bagian dari pengontrol hanya untuk memungkinkan DI dan mengimplementasikan antarmuka yang tidak kita perlukan, atau kita menggunakan kerangka kerja pihak ketiga seperti Ninject untuk menghindari keharusan mendesain controller dengan cara ini, tetapi kita masih harus membuat antarmuka.

Mari kita lihat perbedaan antara yang diuji:

public class MyController : Controller
{
    private readonly IMyDependency _thing;

    public MyController(IMyDependency thing)
    {
        _thing = thing;
    }
}

dan pengontrol yang tidak dapat diuji:

public class MyController : Controller
{
}

Opsi sebelumnya secara harfiah memiliki 5 baris kode tambahan, dua di antaranya dapat di-autogenerasi oleh Visual Studio. Setelah Anda menyiapkan kerangka kerja dependensi injeksi Anda untuk menggantikan tipe konkret untuk IMyDependencysaat runtime - yang untuk kerangka kerja DI yang layak, adalah satu baris kode lainnya - semuanya Hanya Bekerja, kecuali sekarang Anda dapat mengejek dan dengan demikian menguji pengontrol Anda sesuai dengan isi hati Anda. .

6 baris kode tambahan untuk memungkinkan pengujian ... dan kolega Anda berpendapat bahwa "terlalu banyak pekerjaan"? Argumen itu tidak cocok dengan saya, dan itu seharusnya tidak cocok dengan Anda.

Dan Anda tidak harus membuat dan mengimplementasikan antarmuka untuk pengujian: Moq , misalnya, memungkinkan Anda untuk mensimulasikan perilaku tipe konkret untuk tujuan pengujian unit. Tentu saja, itu tidak akan banyak berguna bagi Anda jika Anda tidak dapat menyuntikkan tipe-tipe itu ke dalam kelas yang Anda uji.

Injeksi ketergantungan adalah salah satu dari hal-hal yang setelah Anda memahaminya, Anda bertanya-tanya "bagaimana saya bekerja tanpa ini?". Sederhana, efektif, dan itu Masuk Akal. Tolong, jangan biarkan kolega Anda kurang memahami hal-hal baru untuk menghalangi proyek Anda diuji.

Ian Kemp
sumber
1
Apa yang Anda begitu cepat tolak sebagai "kurangnya pemahaman tentang hal-hal baru" mungkin berubah menjadi pemahaman yang baik tentang hal-hal lama. Ketergantungan injeksi tentu bukan hal baru. Idenya, dan mungkin implementasi paling awal, sudah puluhan tahun. Dan ya, saya yakin jawaban Anda adalah contoh kode menjadi lebih rumit karena pengujian unit, dan mungkin contoh tes unit melanggar enkapsulasi (karena siapa bilang kelas memiliki konstruktor publik di tempat pertama?). Saya sering menghapus injeksi ketergantungan dari basis kode yang saya warisi dari orang lain, karena pengorbanannya.
Christian Hackl
Pengendali selalu memiliki konstruktor publik, implisit atau tidak, karena MVC memerlukannya. "Complicated" - mungkin, jika Anda tidak mengerti cara kerja konstruktor. Enkapsulasi - ya dalam beberapa kasus, tetapi debat DI vs enkapsulasi adalah yang sedang berlangsung, sangat subyektif yang tidak akan membantu di sini, dan khususnya untuk sebagian besar aplikasi, DI akan melayani Anda lebih baik daripada enkapsulasi IMO.
Ian Kemp
Mengenai konstruktor publik: memang, itulah kekhasan kerangka kerja yang digunakan. Saya sedang berpikir tentang kasus yang lebih umum dari kelas biasa yang tidak dibentuk oleh suatu kerangka kerja. Mengapa Anda percaya bahwa melihat parameter metode tambahan sebagai kompleksitas tambahan sama dengan kurangnya pemahaman tentang cara kerja konstruktor? Namun, saya menghargai bahwa Anda mengakui adanya tradeoff antara DI dan enkapsulasi.
Christian Hackl
0

Ketika saya menulis unit test, saya mulai memikirkan apa yang bisa salah dalam kode saya. Ini membantu saya meningkatkan desain kode dan menerapkan prinsip tanggung jawab tunggal (SRP). Juga, ketika saya kembali untuk memodifikasi kode yang sama beberapa bulan kemudian, ini membantu saya untuk mengkonfirmasi bahwa fungsi yang ada tidak rusak.

Ada kecenderungan untuk menggunakan fungsi murni sebanyak yang Anda bisa (aplikasi tanpa server). Pengujian unit membantu saya mengisolasi keadaan dan menulis fungsi murni.

Secara khusus, kami akan memiliki layanan API Web yang akan sangat tipis. Tanggung jawab utamanya adalah menyusun permintaan / tanggapan web dan memanggil API yang mendasari yang berisi logika bisnis.

Tulis tes unit untuk API yang mendasarinya terlebih dahulu dan jika Anda memiliki waktu pengembangan yang cukup, Anda perlu menulis tes untuk layanan Web API yang tipis juga.

TL; DR, pengujian unit membantu meningkatkan kualitas kode dan membantu membuat perubahan di masa depan menjadi bebas risiko kode. Ini juga meningkatkan keterbacaan kode. Gunakan tes alih-alih komentar untuk menegaskan maksud Anda.

Ashutosh
sumber
0

Intinya, dan apa yang seharusnya menjadi argumen Anda dengan lot yang enggan, adalah bahwa tidak ada konflik. Kesalahan besar tampaknya adalah bahwa seseorang menciptakan ide untuk "merancang pengujian" kepada orang-orang yang membenci pengujian. Mereka seharusnya hanya menutup mulut atau mengucapkannya secara berbeda, seperti "mari kita luangkan waktu untuk melakukan ini dengan benar".

Gagasan bahwa "Anda harus mengimplementasikan antarmuka" untuk membuat sesuatu dapat diuji adalah salah. Antarmuka sudah diterapkan, hanya belum dideklarasikan dalam deklarasi kelas. Ini adalah masalah mengenali metode publik yang ada, menyalin tanda tangan mereka ke antarmuka dan mendeklarasikan antarmuka itu dalam deklarasi kelas. Tidak ada pemrograman, tidak ada perubahan pada logika yang ada.

Rupanya beberapa orang memiliki ide berbeda tentang hal ini. Saya sarankan Anda mencoba untuk memperbaikinya terlebih dahulu.

Martin Maat
sumber