Haruskah kita menguji semua metode kita?

62

Jadi hari ini saya berbicara dengan rekan tim saya tentang pengujian unit. Semuanya dimulai ketika dia bertanya kepada saya "hei, di mana tes untuk kelas itu, saya hanya melihat satu?". Seluruh kelas adalah manajer (atau layanan jika Anda lebih suka menyebutnya seperti itu) dan hampir semua metode hanya mendelegasikan barang ke DAO sehingga mirip dengan:

SomeClass getSomething(parameters) {
    return myDao.findSomethingBySomething(parameters);
}

Semacam boilerplate tanpa logika (atau setidaknya saya tidak menganggap delegasi sederhana seperti logika) tetapi boilerplate yang berguna dalam banyak kasus (pemisahan lapisan dll.). Dan kami melakukan diskusi yang cukup panjang apakah saya harus mengujinya atau tidak (saya pikir perlu disebutkan bahwa saya melakukan pengujian unit sepenuhnya terhadap DAO). Argumen utamanya adalah bahwa itu bukan TDD (jelas) dan bahwa seseorang mungkin ingin melihat tes untuk memeriksa apa metode ini (saya tidak tahu bagaimana itu bisa lebih jelas) atau bahwa di masa depan seseorang mungkin ingin mengubah implementasi dan tambahkan logika baru (atau lebih seperti "apa pun") ke dalamnya (dalam hal ini saya kira seseorang harus menguji logika itu ).

Ini membuat saya berpikir. Haruskah kita berusaha untuk% cakupan tes tertinggi? Atau apakah itu hanya seni demi seni? Saya sama sekali tidak melihat alasan di balik pengujian hal-hal seperti:

  • getter dan setter (kecuali mereka benar-benar memiliki beberapa logika di dalamnya)
  • kode "boilerplate"

Jelas tes untuk metode seperti itu (dengan mengolok-olok) akan memakan waktu kurang dari satu menit tapi saya kira itu masih membuang waktu dan satu milidetik lebih lama untuk setiap CI.

Apakah ada alasan rasional / tidak "mudah terbakar" mengapa seseorang harus menguji setiap baris kode (atau sebanyak mungkin)?

Zenzen
sumber
2
Saya masih mengambil keputusan tentang pertanyaan ini, tetapi inilah pembicaraan seseorang yang telah memutuskan jawabannya adalah "tidak". Ian Cooper: TDD, di mana semuanya salah Untuk merangkum pembicaraan hebat ini, Anda harus menguji luar-dalam dan menguji perilaku baru bukan metode baru.
Daniel Kaplan
Ini benar-benar pembicaraan yang hebat, harus dilihat, pembuka mata untuk banyak orang, saya suka itu. Tapi saya pikir jawabannya bukan "tidak". "Ya, tapi secara tidak langsung". Ian cooper berbicara tentang arsitektur heksagonal dan fitur pengujian / perilaku mengejek / mematikan port. Dalam hal ini port-port ini adalah milik DAO dan "manajer / layanan" ini diuji bukan dengan uji unit individual hanya untuk kelas ini tetapi dengan "uji unit" (unit dalam definisi Ian Cooper yang saya setujui sepenuhnya) yang menguji beberapa fitur di domain Anda yang menggunakan manajer / layanan ini.
AlfredoCasado
Ini akan tergantung pada sistem Anda sampai batas tertentu, jika Anda mengembangkan sistem dengan sertifikasi keselamatan tingkat sedang hingga tinggi, Anda harus mencakup semua metode terlepas dari hal-hal sepele
jk.

Jawaban:

49

Saya mengikuti aturan praktis Kent Beck:

Tes semua yang mungkin bisa pecah.

Tentu saja, itu subjektif sampai batas tertentu. Bagi saya, getter / setter sepele dan one-liner seperti milik Anda di atas biasanya tidak sepadan. Tapi sekali lagi, saya menghabiskan sebagian besar waktu saya menulis tes unit untuk kode warisan, hanya bermimpi tentang proyek TDD greenfield yang bagus ... Pada proyek seperti itu, aturannya berbeda. Dengan kode lawas, tujuan utamanya adalah untuk mencakup sebanyak mungkin tanah dengan upaya sesedikit mungkin, sehingga unit test cenderung lebih tinggi dan lebih kompleks, lebih mirip tes integrasi jika seseorang jago tentang terminologi. Dan ketika Anda berjuang untuk mendapatkan cakupan kode keseluruhan naik dari 0%, atau hanya berhasil meningkatkannya lebih dari 25%, unit test getter dan setter adalah yang paling dikhawatirkan.

OTOH dalam proyek TDD greenfield, mungkin lebih penting untuk menulis tes bahkan untuk metode tersebut. Terutama karena Anda telah menulis tes sebelum Anda mendapatkan kesempatan untuk mulai bertanya-tanya "apakah satu baris ini layak untuk tes khusus?". Dan setidaknya tes-tes ini sepele untuk ditulis dan cepat dijalankan, jadi itu bukan masalah besar.

Péter Török
sumber
Ah saya benar-benar lupa kutipan itu! Kira saya akan menggunakannya sebagai argumen utama saya karena terus terang - apa yang bisa pecah di sini? Tidak terlalu banyak. Satu-satunya hal yang dapat merusak adalah metode doa dan jika itu terjadi maka itu berarti sesuatu yang sangat buruk terjadi. Terima kasih!
Zenzen
5
@ Zenzen: "apa yang bisa pecah di sini? Tidak terlalu banyak." - Sehingga bisa pecah. Hanya kesalahan ketik kecil. Atau seseorang menambahkan beberapa kode. Atau mengacaukan ketergantungan. Saya benar-benar berpikir bahwa Beck akan mengklaim bahwa contoh utama Anda memenuhi syarat sebagai dapat ditembus. Getters dan setter, kurang begitu, meskipun saya telah menemukan diri saya dalam kesalahan copy / paste, bahkan kemudian. Pertanyaan sebenarnya adalah, jika terlalu sepele untuk menulis tes, mengapa tes itu ada?
pdr
1
Jumlah waktu yang Anda habiskan untuk memikirkannya sudah Anda bisa menulis tes. saya katakan menulis tes, jangan pergi ketika tidak menulis tes sebagai area abu-abu, lebih banyak jendela yang rusak akan muncul.
kett_chup
1
Saya akan menambahkan bahwa pengalaman umum saya adalah bahwa menguji getter dan setter agak berharga dalam jangka panjang, tetapi prioritas rendah. Alasan mengapa adalah karena mengapa ia memiliki peluang "nol" untuk menemukan bug sekarang, Anda tidak dapat menjamin bahwa pengembang lain tidak akan menambahkan sesuatu dalam tiga bulan ("hanya pernyataan sederhana jika") yang akan memiliki kesempatan untuk memecahkan bug. . Memiliki unit test di tempat menjaga itu. Pada saat yang sama, itu bukan prioritas yang terlalu tinggi, karena Anda tidak akan menemukan apa pun segera seperti itu.
dclements
7
Secara buta menguji segala sesuatu yang dapat merusak tidak masuk akal. Perlu ada strategi di mana komponen berisiko tinggi diuji terlebih dahulu.
CodeART
13

Ada beberapa jenis pengujian unit:

  • Berbasis negara. Anda bertindak dan kemudian menyatakan melawan objek. Misalnya saya melakukan deposit. Saya kemudian memeriksa untuk melihat apakah saldo telah meningkat.
  • Nilai pengembalian berdasarkan. Anda bertindak dan menegaskan terhadap nilai pengembalian.
  • Berbasis interaksi. Anda memverifikasi bahwa objek Anda disebut objek lain. Ini sepertinya adalah apa yang Anda lakukan dalam contoh Anda.

Jika Anda menulis tes Anda terlebih dahulu, itu akan lebih masuk akal - seperti yang Anda harapkan untuk memanggil lapisan akses data. Tes akan gagal pada awalnya. Anda kemudian akan menulis kode produksi untuk lulus ujian.

Idealnya Anda harus menguji kode logis, tetapi interaksi (objek yang memanggil objek lain) sama pentingnya. Dalam kasus Anda, saya akan melakukannya

  • Periksa apakah saya memanggil lapisan akses data dengan parameter tepat yang telah dilewati.
  • Periksa apakah itu dipanggil hanya sekali.
  • Periksa apakah saya mengembalikan persis apa yang telah diberikan kepada saya oleh lapisan akses data. Kalau tidak, saya mungkin juga mengembalikan null.

Saat ini tidak ada logika di sana, tetapi tidak akan selalu demikian.

Namun, jika Anda yakin bahwa tidak akan ada logika dalam metode ini dan kemungkinan akan tetap sama, daripada saya akan mempertimbangkan memanggil lapisan akses data langsung dari konsumen. Saya akan melakukan ini hanya jika anggota tim lainnya ada di halaman yang sama. Anda tidak ingin mengirim pesan yang salah ke tim dengan mengatakan "Hai teman-teman, boleh saja mengabaikan lapisan domain, panggil saja lapisan akses data secara langsung".

Saya juga akan berkonsentrasi pada pengujian komponen lain jika ada tes integrasi untuk metode ini. Saya belum melihat perusahaan dengan tes integrasi yang solid.

Setelah mengatakan semua ini - saya tidak akan menguji semuanya secara membabi buta. Saya akan membangun hot spot (komponen dengan kompleksitas tinggi dan risiko pecah tinggi). Saya kemudian akan berkonsentrasi pada komponen-komponen ini. Tidak ada gunanya memiliki basis kode di mana 90% basis kode cukup mudah dan dicakup oleh unit test, ketika sisanya 10% merupakan logika inti dari sistem dan mereka tidak tercakup oleh unit test karena kompleksitasnya.

Akhirnya, apa manfaat dari pengujian metode ini? Apa implikasinya jika ini tidak berhasil? Apakah itu bencana? Jangan berusaha untuk mendapatkan cakupan kode yang tinggi. Cakupan kode harus merupakan produk sampingan dari unit test yang baik. Misalnya Anda dapat menulis satu tes yang akan berjalan di pohon dan memberi Anda cakupan 100% dari metode ini, atau Anda dapat menulis tiga tes unit yang juga akan memberi Anda cakupan 100%. Perbedaannya adalah bahwa dengan menulis tiga tes Anda menguji kasus tepi, bukan hanya berjalan pohon.

CodeART
sumber
Mengapa Anda memeriksa bahwa DAL Anda hanya dipanggil sekali?
Marjan Venema
9

Inilah cara yang baik untuk memikirkan kualitas perangkat lunak Anda:

  1. pengecekan tipe menangani bagian dari masalah.
  2. pengujian akan menangani sisanya

Untuk fungsi boilerplate dan sepele, Anda dapat mengandalkan pemeriksaan tipe untuk melakukan pekerjaannya, dan untuk yang lain, Anda memang perlu uji kasus.

tp1
sumber
Tentu saja pengecekan tipe hanya berfungsi jika Anda menggunakan tipe spesifik dalam kode Anda, dan Anda sedang menggunakan bahasa yang dikompilasi atau Anda memastikan bahwa pemeriksaan analisis statis sering dijalankan, misalnya sebagai bagian dari CI.
bdsl
6

Menurut pendapat saya, kompleksitas siklomatik adalah parameter. Jika suatu metode tidak cukup kompleks (seperti getter dan setter). Tidak diperlukan pengujian unit. Tingkat Kompleksitas Siklus McCabe harus lebih dari 1. Kata lain harus ada minimum 1 pernyataan blok.

Fırat KÜÇÜK
sumber
Ingat beberapa getter atau setter memiliki efek samping (meskipun tidak disarankan dan dianggap praktik buruk dalam kebanyakan kasus), jadi perubahan dalam kode sumber Anda juga dapat mempengaruhinya.
Andrzej Bobak
3

YA tegas dengan TDD (dan dengan beberapa pengecualian)

Baik kontroversial, tapi saya berpendapat bahwa siapa pun yang menjawab 'tidak' untuk pertanyaan ini kehilangan konsep dasar TDD.

Bagi saya, jawabannya adalah ya jika Anda mengikuti TDD. Jika tidak maka tidak ada jawaban yang masuk akal.

DDD dalam TDD

TDD sering dikutip memiliki manfaat utama bagimu.

  • Pertahanan
    • Memastikan kode dapat berubah tetapi tidak perilakunya .
    • Ini memungkinkan praktik refactoring yang sangat penting .
    • Anda mendapatkan TDD ini atau tidak.
  • Desain
    • Anda menentukan apa yang harus dilakukan sesuatu, bagaimana seharusnya berperilaku sebelum menerapkannya .
    • Ini sering berarti keputusan implementasi yang lebih terinformasi .
  • Dokumentasi
    • Test suite harus berfungsi sebagai dokumentasi spesifikasi (persyaratan).
    • Menggunakan tes untuk tujuan seperti itu berarti bahwa dokumentasi dan implementasi selalu dalam keadaan konsisten - perubahan ke satu berarti perubahan ke yang lain. Bandingkan dengan menjaga persyaratan dan desain pada dokumen kata yang terpisah.

Pisahkan tanggung jawab dari implementasi

Sebagai programmer, sangat menggoda untuk menganggap atribut sebagai sesuatu yang penting dan getter dan setter sebagai semacam overhead.

Tetapi atribut adalah detail implementasi, sementara setter dan getter adalah antarmuka kontrak yang benar-benar membuat program bekerja.

Jauh lebih penting untuk mengeja bahwa objek harus:

Izinkan kliennya mengubah kondisinya

dan

Izinkan kliennya menanyakan kuasanya

lalu bagaimana keadaan ini sebenarnya disimpan (yang atributnya paling umum, tetapi bukan satu-satunya cara).

Tes seperti

(The Painter class) should store the provided colour

Penting untuk bagian dokumentasi TDD.

Fakta bahwa implementasi akhirnya adalah sepele (atribut) dan tidak membawa manfaat pertahanan seharusnya tidak diketahui oleh Anda ketika Anda menulis tes.

Kurangnya rekayasa bolak-balik ...

Salah satu masalah utama dalam dunia pengembangan sistem adalah tidak adanya round-trip engineering 1 - proses pengembangan suatu sistem terpecah menjadi sub-proses yang terpotong-potong, yang artefak-artefaknya (dokumentasi, kode) sering tidak konsisten.

1 Brodie, Michael L. "John Mylopoulos: menjahit benih pemodelan konseptual." Pemodelan Konseptual: Yayasan dan Aplikasi. Springer Berlin Heidelberg, 2009. 1-9.

... dan bagaimana TDD memecahkannya

Ini adalah bagian dokumentasi TDD yang memastikan bahwa spesifikasi sistem dan kodenya selalu konsisten.

Desain dulu, implementasikan nanti

Dalam TDD kami menulis tes penerimaan gagal pertama, baru kemudian menulis kode yang membiarkan mereka lulus.

Di dalam BDD tingkat yang lebih tinggi, kita menulis skenario terlebih dahulu, lalu membuatnya lewat.

Mengapa Anda harus mengecualikan setter dan pengambil?

Secara teori, sangat mungkin dalam TDD untuk satu orang untuk menulis tes, dan yang lain untuk mengimplementasikan kode yang membuatnya lulus.

Jadi tanyakan pada diri sendiri:

Haruskah orang yang menulis tes untuk kelas menyebutkan getter dan setter.

Karena getter dan setter adalah antarmuka publik ke kelas, jawabannya jelas ya , atau tidak akan ada cara untuk mengatur atau menanyakan keadaan suatu objek.

Jelas, jika Anda menulis kode terlebih dahulu, jawabannya mungkin tidak begitu jelas.

Pengecualian

Ada beberapa pengecualian yang jelas untuk aturan ini - fungsi yang jelas detail implementasi dan jelas bukan bagian dari desain sistem.

Misalnya, metode lokal 'B ()':

function A() {

    // B() will be called here    

    function B() {
        ...
    }
} 

Atau fungsi pribadi di square()sini:

class Something {
private:
    square() {...}
public:
    addAndSquare() {...}
    substractAndSquare() {...}
}

Atau fungsi lain yang bukan bagian dari publicantarmuka yang perlu ejaan dalam desain komponen sistem.

Izhaki
sumber
1

Ketika dihadapkan dengan pertanyaan filosofis, kembalilah ke persyaratan mengemudi.

Apakah tujuan Anda untuk menghasilkan perangkat lunak yang cukup andal dengan biaya kompetitif?

Atau apakah itu untuk menghasilkan perangkat lunak dengan keandalan setinggi mungkin hampir terlepas dari biaya?

Sampai pada suatu titik, dua tujuan kualitas dan pengembangan kecepatan / penyelarasan biaya: Anda menghabiskan lebih sedikit waktu menulis tes daripada memperbaiki cacat.

Tapi di luar itu, mereka tidak. Tidak terlalu sulit untuk mencapai, katakanlah, satu bug yang dilaporkan per pengembang per bulan. Membagi dua menjadi satu per dua bulan hanya mengeluarkan anggaran mungkin satu atau dua hari, dan banyak pengujian tambahan mungkin tidak akan mengurangi separuh tingkat cacat Anda. Jadi itu bukan lagi win-win yang sederhana; Anda harus membenarkannya berdasarkan biaya cacat kepada pelanggan.

Biaya ini akan bervariasi (dan, jika Anda ingin menjadi jahat, demikian juga kemampuan mereka untuk menegakkan biaya itu kembali kepada Anda, baik melalui pasar atau gugatan). Anda tidak ingin menjadi jahat, jadi Anda menghitung semua biaya itu kembali; terkadang beberapa tes masih secara global membuat dunia lebih miskin dengan keberadaan mereka.

Singkatnya, jika Anda mencoba secara membuta menerapkan standar yang sama ke situs web in-house sebagai perangkat lunak penerbangan penumpang pesawat, Anda akan berakhir entah dari bisnis, atau di penjara.

soru
sumber
0

Jawaban Anda tentang ini tergantung pada filosofi Anda (percaya itu adalah Chicago vs London? Saya yakin seseorang akan mencarinya). Juri masih melakukan ini pada pendekatan yang paling efektif waktu (karena, bagaimanapun juga itu penggerak terbesar dari ini - lebih sedikit waktu yang dihabiskan untuk perbaikan).

Beberapa pendekatan mengatakan hanya menguji antarmuka publik, yang lain mengatakan menguji urutan setiap panggilan fungsi di setiap fungsi. Banyak perang suci telah terjadi. Saran saya adalah mencoba kedua pendekatan. Pilih satu unit kode dan lakukan seperti X, dan yang lain seperti Y. Setelah beberapa bulan pengujian dan integrasi kembali dan lihat mana yang sesuai dengan kebutuhan Anda dengan lebih baik.

segera
sumber
0

Itu pertanyaan yang sulit.

Sebenarnya saya akan mengatakan bahwa itu tidak perlu. Anda lebih baik menulis unit gaya BDD dan tes tingkat sistem yang memastikan fungsi persyaratan bisnis sebagaimana dimaksud dalam skenario positif dan negatif.

Yang mengatakan jika metode Anda tidak dicakup oleh kasus uji ini maka Anda harus mempertanyakan mengapa itu ada di tempat pertama dan jika diperlukan, atau jika ada persyaratan tersembunyi dalam kode yang tidak tercermin dalam dokumentasi atau cerita pengguna Anda yang harus dikodekan dalam kasus uji gaya BDD.

Secara pribadi saya ingin menjaga cakupan dengan garis di sekitar 85-95% dan gerbang check-in ke arus utama untuk memastikan cakupan tes unit yang ada per baris mencapai tingkat ini untuk semua file kode dan tidak ada file yang dibuka.

Dengan asumsi praktik pengujian terbaik sedang diikuti ini memberikan banyak cakupan tanpa memaksa pengembang membuang waktu mencoba mencari cara untuk mendapatkan cakupan tambahan pada kode latihan atau kode sepele hanya demi cakupan.

Keith Membawa
sumber
-1

Masalahnya adalah pertanyaan itu sendiri, Anda tidak perlu menguji semua "methdos" atau semua "kelas" yang Anda butuhkan untuk menguji semua fitur sistem Anda.

Pemikiran utamanya adalah segi fitur / perilaku alih-alih berpikir dalam hal metode dan kelas. Tentu saja metode ini ada di sini untuk memberikan dukungan untuk satu atau lebih fitur, pada akhirnya semua kode Anda diuji, setidaknya semua masalah kode dalam basis kode Anda.

Dalam skenario Anda, mungkin kelas "manajer" ini berlebihan atau tidak perlu (seperti semua kelas dengan nama yang berisi kata "manajer"), atau mungkin tidak, tetapi tampaknya seperti detail implementasi, mungkin kelas ini tidak pantas satu unit Tes karena kelas ini tidak memiliki logika bisnis yang relevan. Mungkin Anda memerlukan kelas ini agar beberapa fitur berfungsi, tes untuk fitur ini mencakup kelas ini, dengan cara ini Anda dapat melakukan refactor pada kelas ini dan melakukan tes yang memverifikasi bahwa hal yang penting, fitur Anda, masih berfungsi setelah refactor.

Pikirkan fitur / perilaku tidak dalam kelas metode, saya tidak bisa mengulangi ini cukup kali.

AlfredoCasado
sumber
-4

Ini membuat saya berpikir. Haruskah kita berusaha untuk% cakupan tes tertinggi?

Ya, idealnya 100%, tetapi beberapa hal tidak dapat diuji unit.

getter dan setter (kecuali mereka benar-benar memiliki beberapa logika di dalamnya)

Getters / Setters itu bodoh - hanya saja jangan menggunakannya. Alih-alih, masukkan variabel anggota Anda ke bagian publik.

kode "boilerplate"

Dapatkan kode umum keluar, dan unit mengujinya. Itu harus sesederhana itu.

Apakah ada alasan rasional / tidak "mudah terbakar" mengapa seseorang harus menguji setiap baris kode (atau sebanyak mungkin)?

Dengan tidak melakukannya, Anda mungkin kehilangan beberapa bug yang sangat jelas. Tes unit seperti jaring yang aman untuk menangkap bug jenis tertentu, dan Anda harus menggunakannya sebanyak mungkin.

Dan hal terakhir: Saya sedang mengerjakan proyek di mana orang tidak mau membuang waktu untuk menulis tes unit untuk "kode sederhana", tetapi kemudian mereka memutuskan untuk tidak menulis sama sekali. Pada akhirnya, bagian dari kode itu berubah menjadi bola lumpur besar .

BЈовић
sumber
Baiklah mari kita luruskan satu hal: saya tidak bermaksud saya tidak menggunakan tes TDD / write. Justru sebaliknya. Saya tahu bahwa tes mungkin menemukan bug yang tidak saya pikirkan, tetapi apa yang harus diuji di sini? Saya hanya berpikir bahwa metode semacam itu adalah salah satu yang "tidak dapat diuji". Seperti yang dikatakan Péter Török (mengutip Kent Beck), Anda harus menguji hal-hal yang dapat merusak. Apa yang mungkin pecah di sini? Tidak terlalu banyak (hanya ada delegasi sederhana dalam metode ini). Saya BISA menulis unit test tetapi itu hanya akan memiliki tiruan dari DAO dan tegas, tidak banyak pengujian. Adapun getter / setter beberapa kerangka kerja membutuhkannya.
Zenzen
1
Juga, karena saya tidak menyadarinya "Dapatkan kode umum keluar, dan unit mengujinya. Itu harus sesederhana itu.". Bagaimana apanya? Ini adalah kelas layanan (dalam lapisan layanan antara GUI dan DAO), ini umum untuk seluruh aplikasi. Tidak dapat membuatnya lebih generik (karena menerima beberapa parameter dan memanggil metode tertentu di DAO). Satu-satunya alasannya adalah untuk mematuhi arsitektur aplikasi yang berlapis sehingga GUI tidak akan memanggil DAO secara langsung.
Zenzen
20
-1 untuk "Getters / Setters itu bodoh - hanya saja jangan menggunakannya. Sebaliknya, letakkan variabel anggota Anda ke bagian publik." - Sangat salah. Ini telah dibahas beberapa kali di SO . Menggunakan bidang publik di mana-mana sebenarnya lebih buruk bahkan daripada menggunakan getter dan setter di mana-mana.
Péter Török