Apakah kode yang dapat diuji adalah kode yang lebih baik?

103

Saya mencoba untuk membiasakan menulis unit test secara teratur dengan kode saya, tetapi saya telah membaca bahwa pertama - tama penting untuk menulis kode yang dapat diuji . Pertanyaan ini menyentuh prinsip-prinsip SOLID dalam penulisan kode yang dapat diuji, tetapi saya ingin tahu apakah prinsip-prinsip desain tersebut bermanfaat (atau setidaknya tidak berbahaya) tanpa berencana menulis tes sama sekali. Untuk memperjelas - Saya mengerti pentingnya tes menulis; ini bukan pertanyaan tentang kegunaannya.

Untuk mengilustrasikan kebingungan saya, pada bagian yang mengilhami pertanyaan ini, penulis memberikan contoh fungsi yang memeriksa waktu saat ini, dan mengembalikan beberapa nilai tergantung pada waktu. Penulis menunjuk ini sebagai kode buruk karena menghasilkan data (waktu) yang digunakan secara internal, sehingga membuatnya sulit untuk diuji. Namun bagi saya, sepertinya terlalu banyak menghabiskan waktu sebagai argumen. Pada titik tertentu, nilai perlu diinisialisasi, dan mengapa tidak paling dekat dengan konsumsi? Plus, tujuan metode ini dalam pikiran saya adalah mengembalikan beberapa nilai berdasarkan waktu saat ini , dengan menjadikannya parameter yang Anda maksudkan bahwa tujuan ini dapat / harus diubah. Ini, dan pertanyaan lain, membuat saya bertanya-tanya apakah kode yang dapat diuji identik dengan kode "lebih baik".

Apakah menulis kode yang dapat diuji masih merupakan praktik yang baik meskipun tidak ada tes?


Apakah kode yang dapat diuji sebenarnya lebih stabil? telah disarankan sebagai duplikat. Namun, pertanyaan itu adalah tentang "stabilitas" kode, tetapi saya bertanya lebih luas tentang apakah kode lebih unggul karena alasan lain juga, seperti keterbacaan, kinerja, penggandengan, dan sebagainya.

WannabeCoder
sumber
24
Ada properti khusus dari fungsi yang mengharuskan Anda untuk lulus dalam waktu yang disebut idempotensi. Fungsi seperti itu akan menghasilkan hasil yang sama setiap kali ia dipanggil dengan nilai argumen yang diberikan, yang tidak hanya membuatnya lebih dapat diuji, tetapi lebih mudah disusun dan lebih mudah untuk dipikirkan.
Robert Harvey
4
Bisakah Anda mendefinisikan "kode yang lebih baik"? maksud Anda "maintainable" ?, "lebih mudah digunakan tanpa IOC-Container-Magic"?
k3b
7
Saya kira Anda belum pernah mengalami tes gagal karena menggunakan waktu sistem yang sebenarnya dan kemudian zona waktu offset berubah.
Andy
5
Itu lebih baik daripada kode yang tidak bisa diuji.
Tulains Córdova
14
@ RobertTarvey Saya tidak akan menyebut idempotensi itu, saya akan mengatakan itu referensial transparansi : jika func(X)kembali "Morning", maka mengganti semua kejadian func(X)dengan "Morning"tidak akan mengubah program (mis. Panggilan functidak melakukan apa pun selain mengembalikan nilai). Idempotency menyiratkan apakah itu func(func(X)) == X(yang bukan tipe-benar), atau yang func(X); func(X);melakukan efek samping yang sama dengan func(X)(tetapi tidak ada efek samping di sini)
Warbo

Jawaban:

116

Berkenaan dengan definisi umum unit test, saya akan mengatakan tidak. Saya telah melihat kode sederhana dibuat berbelit-belit karena kebutuhan untuk memutarnya agar sesuai dengan kerangka pengujian (mis. Antarmuka dan IoC di mana-mana membuat hal-hal sulit untuk ditindaklanjuti melalui lapisan panggilan antarmuka dan data yang harus jelas diteruskan oleh sihir). Diberi pilihan antara kode yang mudah dimengerti atau kode yang mudah untuk unit test, saya selalu menggunakan kode yang bisa dipelihara setiap waktu.

Ini tidak berarti untuk tidak menguji, tetapi agar sesuai dengan alat yang sesuai dengan Anda, bukan sebaliknya. Ada beberapa cara lain untuk menguji (tetapi kode yang sulit dipahami adalah kode yang buruk). Misalnya, Anda dapat membuat tes unit yang kurang granular (mis. , Sikap Martin Fowler bahwa unit umumnya kelas, bukan metode), atau Anda dapat menekan program Anda dengan tes integrasi otomatis sebagai gantinya. Semacam itu mungkin tidak secantik kerangka pengujian Anda menyala dengan kutu hijau, tapi kami setelah kode diuji, bukan gamification proses, kan?

Anda dapat membuat kode Anda mudah dipelihara dan masih bagus untuk pengujian unit dengan mendefinisikan antarmuka yang baik di antara mereka dan kemudian menulis tes yang menggunakan antarmuka publik dari komponen; atau Anda bisa mendapatkan kerangka uji yang lebih baik (yang menggantikan fungsi saat runtime untuk mengejek mereka, daripada membutuhkan kode untuk dikompilasi dengan mengejek di tempat). Kerangka kerja pengujian unit yang lebih baik memungkinkan Anda mengganti fungsionalitas sistem GetCurrentTime () dengan fungsi Anda sendiri, saat runtime, sehingga Anda tidak perlu memperkenalkan pembungkus buatan untuk ini hanya agar sesuai dengan alat uji.

gbjbaanb
sumber
3
Komentar bukan untuk diskusi panjang; percakapan ini telah dipindahkan ke obrolan .
Insinyur Dunia
2
Saya pikir perlu dicatat bahwa saya tahu setidaknya satu bahasa yang memungkinkan Anda untuk melakukan apa yang dijelaskan paragraf terakhir Anda: Python dengan Mock. Karena cara kerja modul impor, hampir semua hal selain kata kunci dapat diganti dengan tiruan, bahkan metode API standar / kelas / dll. Jadi itu mungkin, tetapi mungkin mengharuskan bahasa dirancang sedemikian rupa untuk mendukung fleksibilitas semacam itu.
jpmc26
6
Saya pikir ada perbedaan antara "kode yang dapat diuji" dan "kode [diputar] agar sesuai dengan kerangka pengujian". Saya tidak yakin ke mana saya akan pergi dengan komentar ini, selain untuk mengatakan saya setuju bahwa kode "memutar" itu buruk, dan kode "dapat diuji" dengan antarmuka yang baik adalah baik.
Bryan Oakley
2
Saya mengungkapkan beberapa pemikiran saya dalam komentar artikel (karena komentar yang panjang tidak diperbolehkan di sini), lihatlah! Untuk lebih jelasnya: Saya adalah penulis artikel yang disebutkan :)
Sergey Kolodiy
Saya harus setuju dengan @BryanOakley. "Kode yang dapat diuji" menyarankan kekhawatiran Anda terpisah: ada kemungkinan untuk menguji suatu aspek (modul) tanpa gangguan dari aspek lain. Saya akan mengatakan ini berbeda dari "menyesuaikan proyek Anda mendukung konvensi pengujian khusus". Ini mirip dengan pola desain: mereka tidak boleh dipaksa. Kode yang memanfaatkan pola desain dengan benar akan dianggap sebagai kode yang kuat. Hal yang sama berlaku untuk prinsip pengujian. Jika membuat kode Anda "dapat diuji" mengakibatkan memutar kode proyek Anda secara berlebihan, Anda melakukan sesuatu yang salah.
Vince Emigh
68

Apakah menulis kode yang dapat diuji masih merupakan praktik yang baik meskipun tidak ada tes?

Hal pertama yang pertama, tidak adanya tes adalah masalah yang jauh lebih besar daripada kode Anda diuji atau tidak. Tidak memiliki unit test berarti Anda tidak selesai dengan kode / fitur Anda.

Itu keluar dari jalan, saya tidak akan mengatakan bahwa penting untuk menulis kode yang dapat diuji - penting untuk menulis kode yang fleksibel . Kode yang tidak fleksibel sulit untuk diuji, jadi ada banyak tumpang tindih dalam pendekatan dan apa yang orang sebut.

Jadi bagi saya, selalu ada seperangkat prioritas dalam penulisan kode:

  1. Buat itu berfungsi - jika kode tidak melakukan apa yang perlu dilakukan, itu tidak berharga.
  2. Jadikan dapat dipertahankan - jika kode tidak dapat dipelihara, ia akan segera berhenti bekerja.
  3. Jadikan fleksibel - jika kode tidak fleksibel, itu akan berhenti bekerja ketika bisnis mau tidak mau datang dan bertanya apakah kode dapat melakukan XYZ.
  4. Jadikan cepat - di luar level dasar yang dapat diterima, kinerja hanyalah saus.

Tes unit membantu menjaga kode, tetapi hanya sampai titik tertentu. Jika Anda membuat kode lebih mudah dibaca, atau lebih rapuh untuk membuat unit test berfungsi, itu menjadi kontra produktif. "Kode yang dapat diuji" umumnya adalah kode yang fleksibel, jadi itu bagus, tetapi tidak sepenting fungsi atau perawatan. Untuk sesuatu seperti waktu saat ini, membuat fleksibel itu bagus tetapi membahayakan perawatan dengan membuat kode lebih sulit untuk digunakan dengan benar dan lebih kompleks. Karena pemeliharaan lebih penting, saya biasanya akan melakukan kesalahan terhadap pendekatan yang lebih sederhana bahkan jika itu kurang teruji.

Telastyn
sumber
4
Saya suka hubungan yang Anda tunjukkan antara dapat diuji dan fleksibel - yang membuat seluruh masalah lebih mudah dipahami oleh saya. Fleksibilitas memungkinkan kode Anda untuk beradaptasi, tetapi tentu membuatnya sedikit lebih abstrak dan kurang intuitif untuk dipahami, tetapi itu adalah pengorbanan yang berharga untuk manfaatnya.
WannabeCoder
3
yang mengatakan, sering saya melihat metode yang seharusnya swasta dipaksakan publik atau tingkat paket agar kerangka pengujian unit dapat mengaksesnya secara langsung. Jauh dari pendekatan yang ideal.
jwenting
4
@ WannabeCoder Tentu saja, itu hanya layak menambah fleksibilitas ketika pada akhirnya menghemat waktu Anda. Itulah sebabnya kami tidak menulis setiap metode pada antarmuka - sebagian besar waktu lebih mudah untuk menulis ulang kode daripada memasukkan terlalu banyak fleksibilitas sejak awal. YAGNI masih merupakan prinsip yang sangat kuat - pastikan saja apa pun yang "Anda tidak akan butuhkan" adalah, menambahkannya secara surut tidak akan memberi Anda lebih banyak pekerjaan rata - rata daripada mengimplementasikannya sebelumnya. Ini adalah kode yang tidak mengikuti YAGNI yang memiliki banyak masalah dengan fleksibilitas dalam pengalaman saya.
Luaan
3
"Tidak memiliki unit test berarti Anda tidak selesai dengan kode / fitur Anda" - Tidak Benar. "Definisi selesai" adalah sesuatu yang diputuskan tim. Ini mungkin atau mungkin tidak termasuk beberapa tingkat cakupan tes. Tetapi tidak ada persyaratan ketat yang mengatakan bahwa fitur tidak dapat "dilakukan" jika tidak ada tes untuk itu. Tim dapat memilih untuk meminta tes, atau mungkin tidak.
Agustus
3
@Telastyn Dalam lebih dari 10 tahun pengembangan, saya tidak pernah memiliki tim yang mengamanatkan kerangka kerja unit testing, dan hanya dua yang bahkan memiliki satu (keduanya memiliki jangkauan yang buruk). Satu tempat memerlukan dokumen kata tentang cara menguji fitur yang Anda tulis. Itu dia. Mungkin saya kurang beruntung? Saya bukan anti-unit test (serius, saya mod situs SQA.SE, saya sangat pro unit test!) Tapi saya belum menemukan mereka menjadi seluas seperti pernyataan klaim Anda.
corsiKa
50

Ya, itu praktik yang baik. Alasannya adalah bahwa testability bukan untuk kepentingan tes. Demi kejelasan dan pengertian yang dibawanya.

Tidak ada yang peduli dengan tes itu sendiri. Adalah kenyataan hidup yang menyedihkan bahwa kita memerlukan rangkaian uji regresi besar karena kita tidak cukup cemerlang untuk menulis kode sempurna tanpa terus-menerus memeriksa pijakan kita. Jika kita bisa, konsep tes tidak akan diketahui, dan semua ini tidak akan menjadi masalah. Saya tentu berharap saya bisa. Tetapi pengalaman menunjukkan bahwa hampir semua dari kita tidak bisa, oleh karena itu tes yang mencakup kode kita adalah hal yang baik bahkan jika mereka mengambil waktu dari penulisan kode bisnis.

Bagaimana memiliki tes meningkatkan kode bisnis kita secara independen dari tes itu sendiri? Dengan memaksa kami untuk membagi fungsi kami ke dalam unit yang dengan mudah ditunjukkan benar. Unit-unit ini juga lebih mudah untuk diperbaiki daripada yang kita akan tergoda untuk menulis.

Contoh waktu Anda adalah poin yang bagus. Selama Anda hanya memiliki fungsi mengembalikan waktu saat ini Anda mungkin berpikir bahwa tidak ada gunanya memprogramnya. Seberapa sulit untuk melakukan ini dengan benar? Tapi pasti program anda akan menggunakan fungsi ini dalam kode lain, dan Anda pasti ingin menguji bahwa kode di bawah kondisi yang berbeda, termasuk pada waktu yang berbeda. Oleh karena itu ide yang baik untuk dapat memanipulasi waktu fungsi Anda kembali - bukan karena Anda tidak mempercayai currentMillis()panggilan satu-line Anda , tetapi karena Anda perlu memverifikasi penelepon dari panggilan itu dalam keadaan terkendali. Jadi Anda tahu, memiliki kode yang dapat diuji sangat berguna bahkan jika dengan sendirinya, tampaknya tidak terlalu menarik perhatian.

Kilian Foth
sumber
Contoh lain adalah jika Anda ingin menarik beberapa bagian kode dari satu proyek ke tempat lain (untuk alasan apa pun). Semakin banyak bagian fungsi yang berbeda satu sama lain, semakin mudah untuk mengekstrak fungsi yang Anda butuhkan dan tidak lebih.
valenterri
10
Nobody cares about the tests themselves- saya lakukan. Saya menemukan tes sebagai dokumentasi yang lebih baik dari apa yang kode lakukan daripada komentar atau file readme.
jcollum
Saya telah perlahan membaca tentang praktik pengujian untuk sementara waktu sekarang (entah bagaimana yang tidak melakukan pengujian unit sama sekali) dan saya harus mengatakan, bagian terakhir tentang memverifikasi panggilan dalam keadaan terkendali, dan kode yang lebih fleksibel yang datang dengan itu, membuat segala macam hal klik pada tempatnya. Terima kasih.
plast1k
12

Pada titik tertentu, nilai perlu diinisialisasi, dan mengapa tidak paling dekat dengan konsumsi?

Karena Anda mungkin perlu menggunakan kembali kode itu, dengan nilai yang berbeda dari yang dihasilkan secara internal. Kemampuan untuk memasukkan nilai yang akan Anda gunakan sebagai parameter, memastikan bahwa Anda dapat menghasilkan nilai-nilai berdasarkan kapan saja Anda suka, bukan hanya "sekarang" (dengan "sekarang" yang berarti ketika Anda memanggil kode).

Membuat kode dapat diuji pada dasarnya berarti membuat kode yang dapat (sejak awal) digunakan dalam dua skenario berbeda (produksi dan pengujian).

Pada dasarnya, walaupun Anda dapat berdebat bahwa tidak ada insentif untuk membuat kode dapat diuji tanpa adanya tes, ada keuntungan besar dalam menulis kode yang dapat digunakan kembali, dan keduanya adalah sinonim.

Plus, tujuan metode ini dalam pikiran saya adalah mengembalikan beberapa nilai berdasarkan waktu saat ini, dengan menjadikannya parameter yang Anda maksudkan bahwa tujuan ini dapat / harus diubah.

Anda juga bisa berpendapat bahwa tujuan dari metode ini adalah mengembalikan beberapa nilai berdasarkan nilai waktu, dan Anda memerlukannya untuk menghasilkan nilai berdasarkan "sekarang". Salah satunya lebih fleksibel, dan jika Anda terbiasa memilih varian itu, pada waktunya, tingkat penggunaan kembali kode Anda akan naik.

utnapistim
sumber
10

Tampaknya konyol untuk mengatakannya seperti ini, tetapi jika Anda ingin dapat menguji kode Anda, maka ya, menulis kode yang dapat diuji lebih baik. Anda bertanya:

Pada titik tertentu, nilai perlu diinisialisasi, dan mengapa tidak paling dekat dengan konsumsi?

Justru karena, dalam contoh yang Anda maksudkan, itu membuat kode itu tidak dapat diuji. Kecuali jika Anda hanya menjalankan sebagian tes pada waktu yang berbeda dalam sehari. Atau Anda mengatur ulang jam sistem. Atau solusi lain. Semuanya lebih buruk daripada hanya membuat kode Anda fleksibel.

Selain tidak fleksibel, metode kecil tersebut memiliki dua tanggung jawab: (1) mendapatkan waktu sistem dan kemudian (2) mengembalikan beberapa nilai berdasarkan itu.

public static string GetTimeOfDay()
{
    DateTime time = DateTime.Now;
    if (time.Hour >= 0 && time.Hour < 6)
    {
        return "Night";
    }
    if (time.Hour >= 6 && time.Hour < 12)
    {
        return "Morning";
    }
    if (time.Hour >= 12 && time.Hour < 18)
    {
        return "Afternoon";
    }
    return "Evening";
}

Masuk akal untuk memecah tanggung jawab lebih lanjut, sehingga bagian di luar kendali Anda ( DateTime.Now) memiliki dampak paling kecil pada sisa kode Anda. Melakukan hal itu akan membuat kode di atas lebih sederhana, dengan efek samping yang dapat diuji secara sistematis.

Eric King
sumber
1
Jadi, Anda harus menguji pagi-pagi sekali untuk memastikan bahwa Anda mendapatkan hasil "Malam" saat menginginkannya. Itu sulit. Sekarang asumsikan Anda ingin memeriksa bahwa penanganan tanggal sudah benar pada 29 Februari 2016 ... Dan beberapa programmer iOS (dan mungkin yang lain) terganggu oleh kesalahan pemula yang mengacaukan semuanya sesaat sebelum atau setelah awal tahun, bagaimana Anda tes untuk itu. Dan dari pengalaman, saya akan memeriksa penanganan tanggal pada 2 Februari 2020.
gnasher729
1
@ gnasher729 Persis maksud saya. "Membuat kode ini dapat diuji" adalah perubahan sederhana yang dapat menyelesaikan banyak masalah (pengujian). Jika Anda tidak ingin mengotomatisasi pengujian, maka saya kira kodenya bisa diterima apa adanya. Tetapi akan lebih baik setelah "diuji".
Eric King
9

Memang ada biaya, tetapi beberapa pengembang begitu terbiasa membayarnya sehingga mereka lupa biayanya ada. Sebagai contoh, Anda sekarang memiliki dua unit alih-alih satu, Anda memerlukan kode panggilan untuk menginisialisasi dan mengelola ketergantungan tambahan, dan sementara GetTimeOfDaylebih dapat diuji, Anda segera kembali ke perahu yang sama menguji baru Anda IDateTimeProvider. Hanya saja jika Anda memiliki tes yang baik, manfaatnya biasanya lebih besar daripada biayanya.

Juga, pada tingkat tertentu, menulis kode yang dapat diuji mendorong Anda untuk merancang kode Anda dengan cara yang lebih mudah dikelola. Kode manajemen dependensi baru ini mengganggu, jadi Anda harus mengelompokkan semua fungsi yang tergantung waktu bersama-sama, jika memungkinkan. Itu dapat membantu mengurangi dan memperbaiki bug seperti, misalnya, ketika Anda memuat halaman tepat pada batas waktu, membuat beberapa elemen dirender menggunakan waktu sebelum dan beberapa menggunakan waktu sesudah. Ini juga dapat mempercepat program Anda dengan menghindari panggilan sistem berulang untuk mendapatkan waktu saat ini.

Tentu saja, perbaikan arsitektur tersebut sangat tergantung pada seseorang yang memperhatikan peluang dan mengimplementasikannya. Salah satu bahaya terbesar dari memusatkan perhatian begitu erat pada unit adalah kehilangan pandangan dari gambaran yang lebih besar.

Banyak kerangka kerja unit test memungkinkan Anda menambal objek tiruan pada saat runtime, yang memungkinkan Anda mendapatkan manfaat dari testabilitas tanpa semua kekacauan. Saya bahkan pernah melihatnya di C ++. Lihatlah kemampuan itu dalam situasi di mana tampaknya biaya pengujian tidak sepadan.

Karl Bielefeldt
sumber
+1 - Anda perlu meningkatkan desain dan arsitektur untuk membuat tes unit menulis lebih mudah.
BЈовић
3
+ - ini adalah arsitektur kode Anda yang penting. Pengujian yang lebih mudah hanyalah efek samping yang menyenangkan.
gbjbaanb
8

Ada kemungkinan bahwa tidak semua karakteristik yang berkontribusi terhadap testabilitas diinginkan di luar konteks testability - Saya mengalami masalah dengan pembenaran terkait non-tes untuk parameter waktu yang Anda sebutkan, misalnya - tetapi secara umum karakteristik yang berkontribusi terhadap testability juga berkontribusi pada kode yang baik terlepas dari testabilitas.

Secara umum, kode yang dapat diuji adalah kode yang dapat ditempa. Ini dalam potongan kecil, diskrit, kohesif, sehingga bit individu dapat dipanggil untuk digunakan kembali. Ini terorganisasi dengan baik dan diberi nama baik (untuk dapat menguji beberapa fungsionalitas Anda memberikan lebih banyak perhatian pada penamaan; jika Anda tidak menulis tes, nama untuk fungsi sekali pakai akan kurang penting). Itu cenderung lebih parametrik (seperti contoh waktu Anda), jadi terbuka untuk digunakan dari konteks lain daripada tujuan yang dimaksudkan. KERING, jadi kurang berantakan dan lebih mudah dipahami.

Iya. Merupakan praktik yang baik untuk menulis kode yang dapat diuji, meskipun terlepas dari pengujian.

Carl Manaster
sumber
tidak setuju tentang hal itu menjadi KERING - membungkus GetCurrentTime dalam metode MyGetCurrentTime sangat mengulangi panggilan OS tanpa manfaat kecuali untuk membantu pengujian alat. Itu hanya contoh paling sederhana, mereka menjadi jauh lebih buruk dalam kenyataan.
gbjbaanb
1
"repating panggilan OS tanpa manfaat" - sampai Anda akhirnya berjalan di server dengan satu jam, berbicara dengan server aws di zona waktu yang berbeda, dan itu merusak kode Anda, dan Anda kemudian harus melalui semua kode Anda dan perbarui untuk menggunakan MyGetCurrentTime, yang sebaliknya mengembalikan UTC. ; kemiringan jam, penghematan siang hari, dan ada alasan lain mengapa mungkin bukan ide yang baik untuk mempercayai panggilan OS secara membabi buta, atau setidaknya memiliki satu titik di mana Anda dapat memasukkan pengganti lain.
Andrew Hill
8

Menulis kode yang dapat diuji sangat penting jika Anda ingin dapat membuktikan bahwa kode Anda benar-benar berfungsi.

Saya cenderung setuju dengan sentimen negatif tentang membengkokkan kode Anda ke dalam contortions yang keji hanya agar sesuai dengan kerangka uji tertentu.

Di sisi lain, semua orang di sini, pada satu titik atau lain, harus berurusan dengan fungsi sulap panjang 1.000 baris yang hanya harus ditangani, hampir tidak dapat disentuh tanpa melanggar satu atau lebih yang tidak jelas, tidak ketergantungan yang jelas di tempat lain (atau di suatu tempat di dalam dirinya sendiri, di mana ketergantungan hampir tidak mungkin untuk divisualisasikan) dan cukup banyak dengan definisi yang tidak dapat disembuhkan. Gagasan (yang bukan tanpa prestasi) bahwa kerangka kerja pengujian telah menjadi berlebihan tidak boleh dianggap sebagai lisensi gratis untuk menulis kualitas buruk, kode yang tidak dapat diuji, menurut pendapat saya.

Cita-cita pembangunan yang digerakkan oleh tes cenderung mendorong Anda untuk menulis prosedur tanggung jawab tunggal, misalnya, dan itu jelas merupakan hal yang baik. Secara pribadi, saya katakan beli menjadi tanggung jawab tunggal, sumber kebenaran tunggal, lingkup terkontrol (tidak ada variabel global freakin ') dan menjaga ketergantungan rapuh seminimal mungkin, dan kode Anda akan dapat diuji. Diuji oleh beberapa kerangka kerja pengujian tertentu? Siapa tahu. Tapi mungkin itu kerangka pengujian yang perlu menyesuaikan diri dengan kode yang baik, dan bukan sebaliknya.

Tetapi hanya untuk memperjelas, kode yang sangat pintar, atau sangat panjang dan / atau saling tergantung sehingga tidak mudah dipahami oleh manusia lain bukanlah kode yang baik. Dan itu juga, secara kebetulan, bukan kode yang dapat dengan mudah diuji.

Jadi mendekati ringkasan saya, apakah kode yang dapat diuji adalah kode yang lebih baik ?

Saya tidak tahu, mungkin juga tidak. Orang-orang di sini memiliki beberapa poin yang valid.

Tetapi saya percaya bahwa kode yang lebih baik cenderung juga merupakan kode yang dapat diuji .

Dan bahwa jika Anda berbicara tentang perangkat lunak serius untuk digunakan dalam upaya serius, pengiriman kode yang belum diuji bukanlah hal yang paling bertanggung jawab yang dapat Anda lakukan dengan uang atasan Anda atau pelanggan Anda.

Memang benar bahwa beberapa kode memerlukan pengujian yang lebih ketat daripada kode lainnya dan agak konyol untuk berpura-pura sebaliknya. Bagaimana Anda ingin menjadi astronot di pesawat ulang-alik jika sistem menu yang menghubungkan Anda dengan sistem vital pada pesawat ulang-alik tidak diuji? Atau seorang karyawan di pabrik nuklir di mana sistem perangkat lunak yang memonitor suhu di reaktor tidak diuji? Di sisi lain, apakah sedikit kode yang menghasilkan laporan hanya-baca sederhana memerlukan truk kontainer penuh dengan dokumentasi dan seribu tes unit? Saya harap tidak. Hanya mengatakan ...

Craig
sumber
1
"kode yang lebih baik cenderung juga merupakan kode yang dapat diuji" Ini kuncinya. Membuatnya dapat diuji tidak membuatnya lebih baik. Membuatnya lebih baik sering membuatnya dapat diuji, dan tes sering memberi Anda informasi yang dapat Anda gunakan untuk membuatnya lebih baik, tetapi keberadaan tes tidak menyiratkan kualitas, dan ada pengecualian (jarang).
anaximander
1
Persis. Pertimbangkan kontrapositifnya. Jika itu kode yang tidak dapat diuji, itu tidak diuji. Jika tidak diuji, bagaimana Anda tahu apakah itu berfungsi atau tidak selain dalam situasi hidup?
pjc50
1
Semua pengujian membuktikan bahwa kode tersebut lulus tes. Kalau tidak, kode unit yang diuji akan bebas bug dan kami tahu bukan itu masalahnya.
wobbily_col
@anaximander Persis. Setidaknya ada kemungkinan bahwa keberadaan tes semata-mata merupakan kontraindikasi yang menghasilkan kode kualitas yang lebih buruk jika semua fokusnya hanya pada memeriksa kotak centang. "Setidaknya tujuh unit tes untuk setiap fungsi?" "Memeriksa." Tapi saya benar-benar percaya bahwa jika kode tersebut adalah kode kualitas, akan lebih mudah untuk diuji.
Craig
1
... tetapi mengeluarkan birokrasi dari pengujian dapat menjadi pemborosan total dan tidak menghasilkan informasi yang berguna atau hasil yang dapat dipercaya. Bagaimanapun juga; Saya yakin berharap seseorang telah menguji bug SSL Heartbleed , ya? atau bug kegagalan Apple goto ?
Craig
5

Namun bagi saya, sepertinya terlalu banyak menghabiskan waktu sebagai argumen.

Anda benar, dan dengan mengejek Anda dapat membuat kode dapat diuji dan menghindari waktu (pun niat tidak ditentukan). Kode contoh:

def time_of_day():
    return datetime.datetime.utcnow().strftime('%H:%M:%S')

Sekarang katakanlah Anda ingin menguji apa yang terjadi selama detik kabisat. Seperti yang Anda katakan, untuk menguji ini dengan cara yang berlebihan Anda harus mengubah kode (produksi):

def time_of_day(now=None):
    now = now if now is not None else datetime.datetime.utcnow()
    return now.strftime('%H:%M:%S')

Jika Python mendukung detik kabisat , kode tes akan terlihat seperti ini:

def test_handle_leap_second(self):
    actual = time_of_day(
        now=datetime.datetime(year=2015, month=6, day=30, hour=23, minute=59, second=60)
    expected = '23:59:60'
    self.assertEquals(actual, expected)

Anda dapat menguji ini, tetapi kode lebih kompleks dari yang diperlukan dan tes masih tidak dapat diandalkan menjalankan cabang kode yang sebagian besar kode produksi akan menggunakan (yaitu, tidak melewati nilai untuk now). Anda mengatasinya dengan menggunakan tiruan . Mulai dari kode produksi asli:

def time_of_day():
    return datetime.datetime.utcnow().strftime('%H:%M:%S')

Kode uji:

@unittest.patch('datetime.datetime.utcnow')
def test_handle_leap_second(self, utcnow_mock):
    utcnow_mock.return_value = datetime.datetime(
        year=2015, month=6, day=30, hour=23, minute=59, second=60)
    actual = time_of_day()
    expected = '23:59:60'
    self.assertEquals(actual, expected)

Ini memberikan beberapa manfaat:

  • Anda menguji ketergantungannya time_of_day secara independen .
  • Anda sedang menguji jalur kode yang sama dengan kode produksi.
  • Kode produksi sesederhana mungkin.

Di samping catatan, diharapkan bahwa kerangka kerja mengejek di masa depan akan membuat hal-hal seperti ini lebih mudah. Misalnya, karena Anda harus merujuk ke fungsi mengejek sebagai string, Anda tidak dapat dengan mudah membuat IDE mengubahnya secara otomatis ketika time_of_daymulai menggunakan sumber lain untuk waktu.

l0b0
sumber
FYI: argumen default Anda salah. Ini hanya akan didefinisikan sekali, jadi fungsi Anda akan selalu mengembalikan waktu pertama kali dievaluasi.
ahruss
4

Kualitas kode yang ditulis dengan baik adalah bahwa ia kuat untuk diubah . Artinya, ketika perubahan persyaratan datang, perubahan dalam kode harus proporsional. Ini adalah yang ideal (dan tidak selalu tercapai), tetapi menulis kode yang dapat diuji membantu membuat kita lebih dekat ke tujuan ini.

Mengapa itu membantu membuat kita lebih dekat? Dalam produksi, kode kami beroperasi dalam lingkungan produksi, termasuk mengintegrasikan dan berinteraksi dengan semua kode kami yang lain. Dalam pengujian unit, kami menyapu banyak lingkungan ini. Kode kami sekarang sedang kuat untuk diubah karena tes adalah perubahan . Kami menggunakan unit dengan cara yang berbeda, dengan input yang berbeda (mengolok-olok, input buruk yang mungkin tidak pernah benar-benar dilewati, dll) daripada kita akan menggunakannya dalam produksi.

Ini mempersiapkan kode kami untuk hari ketika perubahan terjadi di sistem kami. Katakanlah perhitungan waktu kita perlu waktu yang berbeda berdasarkan zona waktu. Sekarang kami memiliki kemampuan untuk lulus dalam waktu itu dan tidak harus membuat perubahan apa pun pada kode. Ketika kita tidak ingin melewatkan waktu dan ingin menggunakan waktu saat ini, kita bisa menggunakan argumen default. Kode kami kuat untuk diubah karena dapat diuji.

cbojar
sumber
4

Dari pengalaman saya, salah satu keputusan paling penting dan paling luas yang Anda buat ketika membangun sebuah program adalah bagaimana Anda memecah kode menjadi unit (di mana "unit" digunakan dalam arti luas). Jika Anda menggunakan bahasa OO berbasis kelas, Anda perlu memecah semua mekanisme internal yang digunakan untuk mengimplementasikan program ke beberapa kelas. Maka Anda perlu memecah kode masing-masing kelas menjadi beberapa metode. Dalam beberapa bahasa, pilihannya adalah bagaimana memecah kode Anda menjadi fungsi. Atau jika Anda melakukan hal SOA, Anda perlu memutuskan berapa banyak layanan yang akan Anda bangun dan apa yang akan masuk ke setiap layanan.

Kerusakan yang Anda pilih memiliki efek besar pada keseluruhan proses. Pilihan yang baik membuat kode lebih mudah untuk ditulis, dan menghasilkan lebih sedikit bug (bahkan sebelum Anda memulai pengujian dan debugging). Mereka membuatnya lebih mudah untuk berubah dan mempertahankan. Menariknya, ternyata begitu Anda menemukan kerusakan yang baik, biasanya juga lebih mudah untuk menguji daripada yang buruk.

Kenapa begitu? Saya rasa saya tidak bisa mengerti dan menjelaskan semua alasan. Tetapi salah satu alasannya adalah bahwa rincian yang baik selalu berarti memilih "ukuran butir" yang moderat untuk unit implementasi. Anda tidak ingin menjejalkan terlalu banyak fungsionalitas dan terlalu banyak logika ke dalam satu kelas / metode / fungsi / modul / dll. Ini membuat kode Anda lebih mudah dibaca dan lebih mudah ditulis, tetapi juga membuatnya lebih mudah untuk diuji.

Bukan hanya itu saja. Desain internal yang baik berarti bahwa perilaku yang diharapkan (input / output / dll) dari setiap unit implementasi dapat didefinisikan dengan jelas dan tepat. Ini penting untuk pengujian. Desain yang baik biasanya berarti bahwa setiap unit implementasi akan memiliki jumlah dependensi yang moderat. Itu membuat kode Anda lebih mudah dibaca dan dipahami orang lain, tetapi juga membuatnya lebih mudah untuk diuji. Alasannya berlanjut; mungkin orang lain dapat mengartikulasikan lebih banyak alasan yang saya tidak bisa.

Berkenaan dengan contoh dalam pertanyaan Anda, saya tidak berpikir bahwa "desain kode yang baik" sama dengan mengatakan bahwa semua dependensi eksternal (seperti ketergantungan pada jam sistem) harus selalu "disuntikkan". Itu mungkin ide yang bagus, tetapi ini adalah masalah terpisah dari apa yang saya jelaskan di sini dan saya tidak akan mempelajari pro dan kontra.

Kebetulan, bahkan jika Anda membuat panggilan langsung ke fungsi sistem yang mengembalikan waktu saat ini, bertindak pada sistem file, dan sebagainya, ini tidak berarti Anda tidak dapat menguji unit kode Anda secara terpisah. Caranya adalah dengan menggunakan versi khusus dari perpustakaan standar yang memungkinkan Anda untuk memalsukan nilai-nilai kembali fungsi sistem. Saya belum pernah melihat orang lain menyebutkan teknik ini, tetapi cukup sederhana untuk dilakukan dengan banyak bahasa dan platform pengembangan. (Semoga runtime bahasa Anda bersifat open-source dan mudah dibuat. Jika mengeksekusi kode Anda melibatkan langkah tautan, mudah-mudahan juga mudah untuk mengontrol perpustakaan mana yang terhubung dengannya.)

Singkatnya, kode yang dapat diuji tidak selalu merupakan kode "baik", tetapi kode "baik" biasanya dapat diuji.

Alex D
sumber
1

Jika Anda menggunakan prinsip-prinsip SOLID Anda akan berada di sisi yang baik, terutama jika memperpanjang ini dengan KISS , KERING , dan YAGNI .

Satu poin yang hilang bagi saya adalah titik kompleksitas suatu metode. Apakah ini metode pengambil / penyetel yang sederhana? Maka hanya menulis tes untuk memuaskan kerangka pengujian Anda akan membuang-buang waktu.

Jika ini adalah metode yang lebih kompleks di mana Anda memanipulasi data dan ingin memastikan bahwa itu akan berfungsi bahkan jika Anda harus mengubah logika internal, maka itu akan menjadi panggilan yang bagus untuk metode pengujian. Sering kali saya harus mengubah kode setelah beberapa hari / minggu / bulan, dan saya sangat senang memiliki test case. Ketika pertama kali mengembangkan metode saya mengujinya dengan metode tes, dan saya yakin itu akan berhasil. Setelah perubahan kode tes utama saya masih berfungsi. Jadi saya yakin perubahan saya tidak merusak kode lama dalam produksi.

Aspek lain dari tes menulis adalah menunjukkan kepada pengembang lain cara menggunakan metode Anda. Berkali-kali pengembang akan mencari contoh tentang cara menggunakan metode dan berapa nilai pengembaliannya.

Hanya dua sen saya .

BtD
sumber