Apakah kita seharusnya menulis tes untuk getter dan setter kita atau apakah itu berlebihan?
Apakah kita seharusnya menulis tes untuk getter dan setter kita atau apakah itu berlebihan?
Jawaban:
Saya akan mengatakan tidak.
@ Akan mengatakan Anda harus bertujuan untuk cakupan kode 100%, tetapi menurut saya itu adalah gangguan yang berbahaya. Anda dapat menulis unit test yang memiliki cakupan 100%, namun tidak menguji apa pun.
Tes unit ada untuk menguji perilaku kode Anda, dengan cara yang ekspresif dan bermakna, dan getter / setter hanya sarana untuk mencapai tujuan. Jika Anda menguji menggunakan getter / setter untuk mencapai tujuan mereka menguji fungsionalitas "nyata", maka itu cukup baik.
Jika, di sisi lain, getter dan setter Anda melakukan lebih dari sekedar mendapatkan dan mengatur (yaitu mereka metode yang benar-benar rumit), maka ya, mereka harus diuji. Tapi jangan menulis unit test case hanya untuk menguji rajin atau rajin, itu buang-buang waktu.
Roy Osherove dalam bukunya yang terkenal 'The Art Of Unit Testing' mengatakan:
Properti (getter / setter di Jawa) adalah contoh kode yang baik yang biasanya tidak mengandung logika apa pun, dan tidak memerlukan pengujian. Tapi hati-hati: setelah Anda menambahkan cek di dalam properti, Anda ingin memastikan bahwa logika sedang diuji.
Catatan : Jawaban ini terus bertambah, meskipun berpotensi saran yang buruk. Untuk memahami alasannya, lihat adik perempuannya di bawah ini.
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 Anda tidak maka tidak ada jawaban yang masuk akal.
TDD sering dikutip memiliki manfaat utama bagimu.
Sebagai pemrogram, 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 kontraktual 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.
Salah satu masalah utama dalam dunia pengembangan sistem adalah tidak adanya round-trip engineering 1 - proses pengembangan sistem terpecah menjadi sub-proses yang terpotong-potong, yang 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.
Ini adalah bagian dokumentasi TDD yang memastikan bahwa spesifikasi sistem dan kodenya selalu konsisten.
Dalam TDD kami menulis tes penerimaan gagal pertama, baru kemudian menulis kode yang membiarkan mereka lulus.
Di dalam BDD level 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. Namun , cara untuk melakukan ini belum tentu dengan menguji setiap metode secara terpisah, lihat jawaban saya yang lain untuk lebih lanjut.
Jelas, jika Anda menulis kode terlebih dahulu, jawabannya mungkin tidak begitu jelas.
tl; dr: Ya Anda harus , dan dengan OpenPojo itu sepele.
Anda harus melakukan validasi pada getter dan setter Anda sehingga Anda harus mengujinya. Misalnya, setMom(Person p)
tidak boleh membiarkan siapa pun yang lebih muda dari diri mereka sebagai ibu mereka.
Bahkan jika Anda tidak melakukan semua itu sekarang, kemungkinan Anda akan di masa depan, maka ini akan baik untuk analisis regresi. Jika Anda ingin mengizinkan ibu yang null
memiliki Anda harus melakukan tes untuk itu jika seseorang mengubah itu nanti, ini akan memperkuat asumsi Anda.
Bug umum adalah di void setFoo( Object foo ){ foo = foo; }
mana seharusnya void setFoo( Object foo ){ this.foo = foo; }
. (Dalam kasus pertama foo
yang sedang ditulis untuk adalah parameter tidak dalam foo
lapangan pada objek ).
Jika Anda mengembalikan array atau koleksi, Anda harus menguji apakah pengambil akan melakukan salinan defensif dari data yang diteruskan ke setter sebelum kembali.
Kalau tidak, jika Anda memiliki setter / getter paling dasar maka unit-test mereka akan menambahkan mungkin sekitar 10 menit paling banyak per-objek, jadi apa kerugiannya? Jika Anda menambahkan perilaku Anda sudah memiliki tes kerangka dan Anda mendapatkan pengujian regresi ini secara gratis. Jika Anda menggunakan Java, Anda tidak punya alasan karena ada OpenPojo . Ada seperangkat aturan yang dapat Anda aktifkan dan kemudian memindai seluruh proyek Anda dengan mereka untuk memastikan mereka diterapkan secara konsisten dalam kode Anda.
Dari contoh mereka :
final PojoValidator pojoValidator = new PojoValidator();
//create rules
pojoValidator.addRule( new NoPublicFieldsRule () );
pojoValidator.addRule( new NoPrimitivesRule () );
pojoValidator.addRule( new GetterMustExistRule () );
pojoValidator.addRule( new SetterMustExistRule () );
//create testers
pojoValidator.addTester( new DefaultValuesNullTester () );
pojoValidator.addTester( new SetterTester () );
pojoValidator.addTester( new GetterTester () );
//test all the classes
for( PojoClass pojoClass : PojoClassFactory.getPojoClasses( "net.initech.app", new FilterPackageInfo() ) )
pojoValidator.runValidation( pojoClass );
Izinkan saya menguraikan:
Dari Bekerja secara efektif dengan kode lawas 1 :
Istilah unit test memiliki sejarah panjang dalam pengembangan perangkat lunak. Umum untuk sebagian besar konsepsi unit test adalah gagasan bahwa mereka adalah tes dalam isolasi masing-masing komponen perangkat lunak. Apa itu komponen? Definisi bervariasi, tetapi dalam pengujian unit, kami biasanya berkaitan dengan unit perilaku paling atom dari suatu sistem. Dalam kode prosedural, unit sering berfungsi. Dalam kode berorientasi objek, unit adalah kelas.
Perhatikan bahwa dengan OOP, di mana Anda menemukan getter dan setter, unit adalah kelas , tidak harus metode individual .
Semua persyaratan dan tes mengikuti bentuk logika Hoare :
{P} C {Q}
Dimana:
{P}
adalah prasyarat ( diberikan )C
adalah kondisi pemicu ( kapan ){Q}
adalah kondisi akhir ( lalu )Kemudian muncul pepatah:
Uji perilaku, bukan implementasi
Ini berarti bahwa Anda tidak boleh menguji bagaimana C
mencapai kondisi pasca, Anda harus memeriksa {Q}
apakah hasilnya C
.
Ketika datang ke OOP, C
adalah kelas. Jadi Anda tidak harus menguji efek internal, hanya efek eksternal.
Getters dan setters mungkin melibatkan beberapa logika, tetapi selama logika ini tidak memiliki efek eksternal - membuat mereka bean accessors 2 ) tes harus melihat ke dalam objek dan dengan itu tidak hanya melanggar enkapsulasi tetapi juga tes untuk implementasi.
Jadi, Anda tidak harus menguji pembuat dan pembuat bean secara terpisah. Ini buruk:
Describe 'LineItem class'
Describe 'setVAT()'
it 'should store the VAT rate'
lineItem = new LineItem()
lineItem.setVAT( 0.5 )
expect( lineItem.vat ).toBe( 0.5 )
Meskipun jika setVAT
akan melempar pengecualian, tes yang sesuai akan tepat karena sekarang ada efek eksternal.
Hampir tidak ada gunanya mengubah keadaan internal suatu objek jika perubahan tersebut tidak memiliki efek di luar, bahkan jika efek tersebut datang kemudian.
Jadi tes untuk setter dan getter harus memperhatikan efek eksternal dari metode ini, bukan yang internal.
Sebagai contoh:
Describe 'LineItem class'
Describe 'getGross()'
it 'should return the net time the VAT'
lineItem = new LineItem()
lineItem.setNet( 100 )
lineItem.setVAT( 0.5 )
expect( lineItem.getGross() ).toBe( 150 )
Anda mungkin berpikir sendiri:
Tunggu sebentar, kami sedang menguji di
getGross()
sini bukansetVAT()
.
Tetapi jika setVAT()
kerusakan itu tes harus gagal semua sama.
1 Feathers, M., 2004. Bekerja secara efektif dengan kode warisan. Prentice Hall Professional.
2 Martin, RC, 2009. Clean code: buku pegangan keahlian pengerjaan perangkat lunak tangkas. Pendidikan Pearson.
Sementara ada alasan yang dibenarkan untuk Properti, ada kepercayaan Desain Berorientasi Objek umum bahwa mengekspos negara anggota melalui Properti adalah desain yang buruk. Artikel Robert Martin tentang Prinsip Tertutup Terbuka memperluas ini dengan menyatakan bahwa Properti mendorong penggandaan dan karenanya membatasi kemampuan untuk menutup kelas dari modifikasi - jika Anda memodifikasi properti, semua konsumen kelas perlu mengubah juga. Dia memenuhi syarat bahwa mengekspos variabel anggota tidak harus desain yang buruk, mungkin hanya gaya yang buruk. Namun, jika properti hanya baca, ada sedikit peluang penyalahgunaan dan efek samping.
Pendekatan terbaik yang dapat saya berikan untuk pengujian unit (dan ini mungkin aneh) adalah membuat sebanyak mungkin properti terlindungi atau internal. Ini akan mencegah penggandengan saat mengecilkan menulis tes konyol untuk getter dan setter.
Ada alasan yang jelas di mana Properti baca / tulis harus digunakan, seperti properti ViewModel yang terikat pada bidang input, dll.
Secara lebih praktis, tes unit harus mendorong fungsionalitas melalui metode publik. Jika kode yang Anda uji menggunakan properti itu, Anda mendapatkan cakupan kode secara gratis. Jika ternyata properti ini tidak pernah disorot oleh cakupan kode, ada kemungkinan yang sangat kuat bahwa:
Jika Anda menulis tes untuk getter dan setter, Anda mendapatkan sense of coverage yang salah dan tidak akan dapat menentukan apakah properti benar-benar digunakan oleh perilaku fungsional.
Jika kompleksitas siklis dari pengambil dan / atau penyetel adalah 1 (yang biasanya), maka jawabannya tidak, Anda tidak harus.
Jadi kecuali Anda memiliki SLA yang membutuhkan 100% cakupan kode, jangan repot-repot, dan fokus pada pengujian aspek penting dari perangkat lunak Anda.
PS Ingatlah untuk membedakan getter dan setter, bahkan dalam bahasa seperti C # di mana properti mungkin tampak seperti hal yang sama. Kompleksitas setter bisa lebih tinggi daripada pengambil, dan dengan demikian memvalidasi tes-unit.
Pandangan lucu, namun bijak: Jalan Testivus
"Tulis tes yang kamu bisa hari ini"
Pengujian getter / setter mungkin berlebihan jika Anda seorang penguji yang berpengalaman dan ini adalah proyek kecil. Namun, jika Anda baru saja mulai belajar cara menguji unit atau getter / setter ini mungkin mengandung logika (seperti setMom()
contoh @ ArtB ) maka akan lebih baik untuk menulis tes.
Ini sebenarnya telah menjadi topik terbaru antara tim saya dan saya. Kami mengambil cakupan kode 80%. Tim saya berpendapat bahwa getter dan setter otomatis diimplementasikan, dan kompiler menghasilkan beberapa kode dasar di belakang layar. Dalam hal ini, mengingat kode yang dihasilkan tidak mengganggu, tidak masuk akal untuk menguji kode yang dibuat oleh kompiler untuk Anda. Kami juga memiliki diskusi tentang metode async dan dalam hal ini kompiler menghasilkan sejumlah kode di belakang layar. Ini adalah kasus yang berbeda dan sesuatu yang kami uji. Jawaban panjang pendek, bawa bersama tim Anda dan putuskan sendiri apakah layak untuk diuji.
Juga, jika Anda menggunakan laporan cakupan kode seperti kami, satu hal yang dapat Anda lakukan adalah menambahkan atribut [ExcludeFromCodeCoverage]. Solusi kami adalah menggunakan ini untuk model yang hanya memiliki properti menggunakan getter dan setter, atau di properti itu sendiri. Dengan begitu hal itu tidak akan mempengaruhi% cakupan kode total ketika laporan cakupan kode dijalankan, dengan asumsi itulah yang Anda gunakan untuk menghitung persentase cakupan kode Anda. Selamat mencoba!
Dalam cakupan kode pendapat saya adalah cara yang baik untuk melihat apakah Anda melewatkan fungsi yang harus Anda liput.
Ketika Anda memeriksa cakupan secara manual dengan pewarnaan yang cantik maka dapat diperdebatkan bahwa getter dan setter biasa tidak perlu diuji (meskipun saya selalu melakukannya).
Ketika Anda hanya memeriksa persentase cakupan kode pada proyek Anda, maka persentase cakupan tes seperti 80% tidak ada artinya. Anda dapat menguji semua bagian yang tidak logis dan melupakan beberapa bagian penting. Dalam hal ini hanya 100% berarti bahwa Anda telah menguji semua kode vital Anda (dan semua kode non-logis juga). Begitu 99,9% Anda tahu bahwa Anda telah melupakan sesuatu.
Omong-omong: Cakupan kode adalah pemeriksaan terakhir untuk melihat apakah Anda telah (sepenuhnya) menguji suatu kelas. Tetapi cakupan kode 100% tidak selalu berarti bahwa Anda telah benar-benar menguji semua fungsionalitas kelas. Jadi unit test harus selalu dilaksanakan mengikuti logika kelas. Pada akhirnya Anda menjalankan cakupan untuk melihat apakah Anda lupa sesuatu. Ketika Anda melakukannya dengan benar, Anda menekan 100% pertama kalinya.
Satu hal lagi: Ketika baru-baru ini bekerja di sebuah bank besar di Belanda saya perhatikan bahwa Sonar menunjukkan cakupan kode 100%. Namun, saya tahu ada sesuatu yang hilang. Memeriksa persentase cakupan kode per file, ini menunjukkan file dengan persentase lebih rendah. Seluruh persentase basis kode adalah sebesar itu sehingga satu file tidak membuat persentase ditampilkan sebagai 99,9%. Jadi, Anda mungkin ingin melihat keluar untuk ini ...
Saya melakukan sedikit analisis tentang cakupan yang dicapai dalam kode JUnit itu sendiri .
Salah satu kategori kode terbuka adalah "terlalu mudah untuk diuji" . Ini termasuk getter dan setter sederhana, yang tidak diuji oleh pengembang JUnit .
Di sisi lain, JUnit tidak memiliki metode (non-usang) lebih dari 3 baris yang tidak dicakup oleh tes apa pun.
Saya akan mengatakan: YA Kesalahan dalam metode pengambil / penyetel dapat diam-diam menyelinap masuk dan menyebabkan beberapa bug jelek.
Saya telah menulis lib untuk membuat ini dan beberapa tes lainnya lebih mudah. Satu-satunya hal yang harus Anda tulis dalam tes JUnit Anda adalah ini:
assertTrue(executor.execute(Example.class, Arrays.asList( new DefensiveCopyingCheck(),
new EmptyCollectionCheck(), new GetterIsSetterCheck(),
new HashcodeAndEqualsCheck(), new PublicVariableCheck())));
Ya, terutama jika item yang didapat adalah objek kelas yang disubclass dari kelas abstrak. IDE Anda mungkin atau mungkin tidak mengingatkan Anda bahwa properti tertentu belum diinisialisasi.
Dan kemudian beberapa tes yang tampaknya tidak berhubungan crash dengan NullPointerException
dan Anda perlu beberapa saat untuk mengetahui bahwa properti gettable sebenarnya tidak ada di sana untuk mendapatkan di tempat pertama.
Meskipun itu masih tidak akan seburuk menemukan masalah dalam produksi.
Mungkin ide yang bagus untuk memastikan semua kelas abstrak Anda memiliki konstruktor. Jika tidak, tes dari pengambil dapat mengingatkan Anda untuk masalah di sana.
Adapun getter dan setter primitif, pertanyaannya mungkin: Apakah saya menguji program saya atau saya menguji JVM atau CLR? Secara umum, JVM tidak perlu diuji.