Apakah kode duplikat lebih dapat ditoleransi dalam pengujian unit?

113

Saya merusak beberapa unit test beberapa waktu yang lalu ketika saya melakukan dan memperbaikinya untuk membuatnya lebih KERING - maksud dari setiap pengujian tidak lagi jelas. Tampaknya ada trade-off antara pembacaan dan pemeliharaan tes. Jika saya meninggalkan kode duplikat dalam pengujian unit, mereka lebih mudah dibaca, tetapi kemudian jika saya mengubah SUT , saya harus melacak dan mengubah setiap salinan kode yang digandakan.

Apakah Anda setuju bahwa pertukaran ini ada? Jika demikian, apakah Anda lebih suka pengujian Anda dapat dibaca, atau dipelihara?

Daryl Spitzer
sumber

Jawaban:

68

Kode duplikat adalah bau dalam kode uji unit seperti halnya kode lainnya. Jika Anda memiliki kode duplikat dalam pengujian, akan lebih sulit untuk memfaktorkan ulang kode implementasi karena Anda memiliki jumlah pengujian yang tidak proporsional untuk diperbarui. Tes akan membantu Anda refactor dengan percaya diri, daripada menjadi beban besar yang menghambat pekerjaan Anda pada kode yang sedang diuji.

Jika duplikasi dalam pengaturan fixture, pertimbangkan untuk menggunakan lebih banyak setUpmetode atau menyediakan Metode Penciptaan yang lebih (atau lebih fleksibel) .

Jika duplikasi ada dalam kode yang memanipulasi SUT, tanyakan pada diri Anda mengapa beberapa pengujian yang disebut "unit" melakukan fungsi yang sama persis.

Jika duplikasi ada dalam pernyataan, mungkin Anda memerlukan beberapa Pernyataan Khusus . Misalnya, jika beberapa pengujian memiliki serangkaian pernyataan seperti:

assertEqual('Joe', person.getFirstName())
assertEqual('Bloggs', person.getLastName())
assertEqual(23, person.getAge())

Maka mungkin Anda membutuhkan satu assertPersonEqualmetode, agar Anda bisa menulis assertPersonEqual(Person('Joe', 'Bloggs', 23), person). (Atau mungkin Anda hanya perlu membebani operator kesetaraan Person.)

Seperti yang Anda sebutkan, penting agar kode pengujian dapat dibaca. Secara khusus, penting agar tujuan pengujian jelas. Saya menemukan bahwa jika banyak tes terlihat sebagian besar sama, (misalnya tiga perempat garis sama atau hampir sama) sulit untuk menemukan dan mengenali perbedaan yang signifikan tanpa membaca dan membandingkannya dengan cermat. Jadi saya menemukan bahwa refactoring untuk menghapus duplikasi membantu keterbacaan, karena setiap baris dari setiap metode pengujian secara langsung relevan dengan tujuan pengujian. Itu jauh lebih berguna bagi pembaca daripada kombinasi acak dari garis yang secara langsung relevan, dan garis yang hanya berupa pelat standar.

Meskipun demikian, terkadang pengujian menggunakan situasi kompleks yang serupa tetapi masih sangat berbeda, dan sulit untuk menemukan cara yang baik untuk mengurangi duplikasi. Gunakan akal sehat: jika Anda merasa pengujian dapat dibaca dan memperjelas maksudnya, dan Anda merasa nyaman mungkin perlu memperbarui lebih dari jumlah pengujian yang secara teoritis minimal saat memfaktorkan ulang kode yang dipanggil oleh pengujian, maka terima ketidaksempurnaan tersebut dan lanjutkan ke sesuatu yang lebih produktif. Anda selalu dapat kembali dan memfaktorkan ulang pengujian nanti, saat inspirasi muncul!

spiv
sumber
30
"Kode duplikat adalah bau dalam kode pengujian unit sama seperti di kode lain." Tidak. "Jika Anda memiliki kode duplikat dalam pengujian, akan lebih sulit untuk memfaktorkan ulang kode implementasi karena Anda memiliki jumlah pengujian yang tidak proporsional untuk diupdate." Ini terjadi karena Anda menguji API pribadi, bukan API publik.
15
Tetapi untuk mencegah kode duplikat dalam pengujian unit, Anda biasanya perlu memperkenalkan logika baru. Saya tidak berpikir unit test harus mengandung logika karena Anda akan membutuhkan unit test dari unit test.
Petr Peller
@ user11617 harap definisikan "API pribadi" dan "Api publik". Dalam pemahaman saya, Api publik adalah Api yang terlihat oleh konsumen dunia luar / pihak ketiga dan secara eksplisit diversi melalui SemVer atau serupa, yang lainnya bersifat pribadi. Dengan definisi ini, hampir semua pengujian Unit menguji "API pribadi" dan karenanya lebih sensitif terhadap duplikasi kode, yang menurut saya benar.
KolA
@KolA "Publik" tidak berarti konsumen pihak ketiga - ini bukan API web. API publik suatu kelas merujuk pada metode yang dimaksudkan untuk digunakan oleh kode klien (yang biasanya tidak / tidak boleh banyak berubah) - umumnya metode "publik". API pribadi mengacu pada logika dan metode yang digunakan secara internal. Ini tidak boleh diakses dari luar kelas. Inilah salah satu alasan mengapa penting untuk mengenkapsulasi logika dengan benar di kelas menggunakan pengubah akses atau konvensi dalam bahasa yang digunakan.
Nathan
@Nathan semua paket library / dll / nuget memiliki konsumen pihak ke-3, tidak harus berupa api web. Apa yang saya maksud adalah bahwa sangat umum untuk mendeklarasikan kelas dan anggota publik yang tidak seharusnya digunakan secara langsung oleh konsumen perpustakaan (atau paling baik menjadikannya perakitan internal dan anotasi dengan InternalsVisibleToAttribute) hanya untuk memungkinkan pengujian unit menjangkau mereka secara langsung. Ini mengarah pada banyak tes ditambah dengan implementasi dan membuatnya lebih menjadi beban daripada keuntungan
KolA
186

Keterbacaan lebih penting untuk tes. Jika tes gagal, Anda ingin masalahnya jelas. Pengembang tidak harus melewati banyak kode pengujian yang sangat difaktorkan untuk menentukan dengan tepat apa yang gagal. Anda tidak ingin kode pengujian menjadi begitu rumit sehingga Anda perlu menulis unit-test-test.

Namun, menghilangkan duplikasi biasanya merupakan hal yang baik, selama itu tidak mengaburkan apapun, dan menghilangkan duplikasi dalam pengujian Anda dapat menghasilkan API yang lebih baik. Pastikan Anda tidak melewati titik pengembalian yang semakin berkurang.

Kristopher Johnson
sumber
xUnit dan lainnya berisi argumen 'pesan' dalam panggilan pernyataan. Ide bagus untuk meletakkan frasa yang bermakna agar developer dapat dengan cepat menemukan hasil tes yang gagal.
seand
1
@seand Anda dapat mencoba menjelaskan apa yang diperiksa oleh assert Anda, tetapi jika assert tersebut gagal dan berisi kode yang agak tidak jelas, maka pengembang harus pergi dan melepaskannya. IMO Lebih penting memiliki kode untuk mendeskripsikan diri sendiri di sana.
IgorK
1
@Kopher,? Mengapa diposting ini wiki komunitas?
Pacerier
@Perier Saya tidak tahu. Dulu ada aturan rumit tentang hal-hal yang secara otomatis menjadi wiki komunitas.
Kristopher Johnson
Untuk keterbacaan laporan lebih penting daripada tes, khususnya ketika melakukan integrasi atau tes ujung ke ujung, skenario bisa cukup kompleks untuk menghindari navigasi sedikit, tidak apa-apa untuk menemukan kegagalan tetapi sekali lagi bagi saya kegagalan dalam laporan harus jelaskan masalahnya dengan cukup baik.
Anirudh
47

Kode implementasi dan pengujian adalah hewan yang berbeda dan aturan pemfaktoran berlaku berbeda untuk mereka.

Kode atau struktur duplikat selalu menjadi bau dalam kode implementasi. Saat Anda mulai menerapkan boilerplate, Anda perlu merevisi abstraksi Anda.

Di sisi lain, kode pengujian harus mempertahankan tingkat duplikasi. Duplikasi dalam kode pengujian mencapai dua tujuan:

  • Menjaga tes tetap dipisahkan. Penggandengan pengujian yang berlebihan dapat mempersulit pengubahan pengujian gagal tunggal yang perlu diperbarui karena kontrak telah berubah.
  • Menjaga agar tes tetap bermakna dalam isolasi. Ketika satu pengujian gagal, harus cukup mudah untuk mengetahui dengan tepat apa yang diuji.

Saya cenderung mengabaikan duplikasi sepele dalam kode pengujian selama setiap metode pengujian tetap lebih pendek dari sekitar 20 baris. Saya suka ketika ritme setup-run-verifikasi terlihat jelas dalam metode pengujian.

Saat duplikasi merayap di bagian "verifikasi" pada pengujian, sering kali bermanfaat untuk menentukan metode pernyataan kustom. Tentu saja, metode-metode tersebut masih harus menguji relasi yang teridentifikasi dengan jelas yang dapat diperlihatkan dalam nama metode: assertPegFitsInHole-> baik, assertPegIsGood-> buruk.

Ketika metode pengujian bertambah panjang dan berulang, terkadang saya merasa berguna untuk menentukan template pengujian fill-in-the-blanks yang mengambil beberapa parameter. Kemudian metode pengujian yang sebenarnya direduksi menjadi panggilan ke metode template dengan parameter yang sesuai.

Adapun banyak hal dalam pemrograman dan pengujian, tidak ada jawaban yang jelas. Anda perlu mengembangkan selera, dan cara terbaik untuk melakukannya adalah dengan membuat kesalahan.

ddaa
sumber
8

Saya setuju. Pertukaran itu ada tetapi berbeda di tempat yang berbeda.

Saya lebih cenderung memfaktor ulang kode duplikat untuk menyiapkan status. Tapi kecil kemungkinannya untuk merefaktor bagian dari pengujian yang benar-benar melatih kode. Yang mengatakan, jika menjalankan kode selalu membutuhkan beberapa baris kode maka saya mungkin berpikir itu adalah bau dan refactor kode sebenarnya yang diuji. Dan itu akan meningkatkan keterbacaan dan pemeliharaan kode dan tes.

stucampbell
sumber
Saya pikir ini adalah ide yang bagus. Jika Anda memiliki banyak duplikasi, lihat apakah Anda dapat memfaktor ulang untuk membuat "perlengkapan uji" umum di mana banyak pengujian dapat dijalankan. Ini akan menghilangkan duplikat setup / kode pembongkaran.
Outlaw Programmer
8

Anda dapat mengurangi pengulangan menggunakan beberapa jenis metode utilitas pengujian .

Saya lebih toleran terhadap pengulangan dalam kode pengujian daripada dalam kode produksi, tetapi saya terkadang frustrasi karenanya. Ketika Anda mengubah desain kelas dan Anda harus kembali dan mengubah 10 metode pengujian berbeda yang semuanya melakukan langkah-langkah penyiapan yang sama, itu membuat frustrasi.

Don Kirkby
sumber
6

Jay Fields menciptakan frasa bahwa "DSL harus DAMP, bukan KERING", di mana DAMP berarti frasa deskriptif dan bermakna . Saya pikir hal yang sama juga berlaku untuk tes. Jelas, terlalu banyak duplikasi itu buruk. Tetapi menghapus duplikasi dengan cara apa pun bahkan lebih buruk. Pengujian harus bertindak sebagai spesifikasi yang mengungkapkan maksud. Jika, misalnya, Anda menentukan fitur yang sama dari beberapa sudut yang berbeda, maka diharapkan ada sejumlah duplikasi.

Jörg W Mittag
sumber
3

I LOVE rspec karena ini:

Ada 2 hal untuk membantu -

  • kelompok contoh bersama untuk menguji perilaku umum.
    Anda dapat menentukan serangkaian pengujian, lalu 'memasukkan' set tersebut ke dalam pengujian Anda yang sebenarnya.

  • konteks bersarang.
    Anda pada dasarnya dapat memiliki metode 'setup' dan 'teardown' untuk subset tertentu dari pengujian Anda, tidak hanya setiap orang di kelas.

Semakin cepat .NET / Java / framework pengujian lainnya mengadopsi metode ini, semakin baik (atau Anda dapat menggunakan IronRuby atau JRuby untuk menulis pengujian Anda, yang menurut saya pribadi adalah opsi yang lebih baik)

Orion Edwards
sumber
3

Saya merasa bahwa kode pengujian memerlukan tingkat rekayasa serupa yang biasanya diterapkan pada kode produksi. Pasti ada argumen yang mendukung keterbacaan dan saya setuju itu penting.

Namun, menurut pengalaman saya, saya menemukan bahwa tes yang difaktorkan dengan baik lebih mudah dibaca dan dipahami. Jika ada 5 pengujian yang masing-masing terlihat sama kecuali untuk satu variabel yang diubah dan pernyataan di bagian akhir, akan sangat sulit untuk menemukan item yang berbeda tersebut. Demikian pula, jika difaktorkan sehingga hanya variabel yang berubah yang terlihat dan pernyataannya, maka mudah untuk mengetahui apa yang segera dilakukan pengujian.

Menemukan tingkat abstraksi yang tepat saat pengujian bisa jadi sulit dan saya rasa itu layak dilakukan.

Kevin London
sumber
2

Saya tidak berpikir ada hubungan antara kode yang lebih digandakan dan dapat dibaca. Saya pikir kode pengujian Anda harus sebagus kode Anda yang lain. Kode yang tidak berulang lebih mudah dibaca daripada kode yang digandakan jika dilakukan dengan baik.

Paco
sumber
2

Idealnya, pengujian unit tidak boleh banyak berubah setelah ditulis jadi saya akan condong ke arah keterbacaan.

Membuat pengujian unit menjadi sesederhana mungkin juga membantu menjaga pengujian tetap fokus pada fungsi spesifik yang mereka targetkan.

Karena itu, saya cenderung mencoba dan menggunakan kembali potongan kode tertentu yang akhirnya saya gunakan berulang kali, seperti kode penyiapan yang persis sama di seluruh rangkaian pengujian.

17 dari 26
sumber
2

"memfaktorkan ulang mereka agar lebih KERING - maksud dari setiap tes tidak lagi jelas"

Sepertinya Anda kesulitan melakukan refactoring. Saya hanya menebak-nebak, tetapi jika hasilnya kurang jelas, bukankah itu berarti Anda masih memiliki lebih banyak pekerjaan yang harus dilakukan sehingga Anda memiliki tes yang cukup elegan yang sangat jelas?

Itulah mengapa pengujian merupakan subkelas dari UnitTest - sehingga Anda dapat merancang rangkaian pengujian yang tepat, mudah divalidasi, dan jelas.

Di masa lalu kami memiliki alat pengujian yang menggunakan bahasa pemrograman yang berbeda. Sulit (atau tidak mungkin) untuk mendesain menyenangkan, mudah dikerjakan dengan tes.

Anda memiliki kekuatan penuh - bahasa apa pun yang Anda gunakan - Python, Java, C # - jadi gunakan bahasa itu dengan baik. Anda dapat memperoleh kode pengujian yang bagus dan jelas dan tidak terlalu berlebihan. Tidak ada trade-off.

S. Lott
sumber