Pengujian vs Jangan Ulangi Diri Sendiri (KERING)

11

Mengapa mengulangi diri sendiri dengan menulis tes sangat dianjurkan?

Tampaknya tes pada dasarnya mengekspresikan hal yang sama dengan kode, dan karenanya merupakan duplikat (dalam konsep, bukan implementasi) dari kode. Bukankah target akhir KERING mencakup penghapusan semua kode uji?

John Tseng
sumber

Jawaban:

24

Saya percaya ini adalah kesalahpahaman dengan cara apa pun yang dapat saya pikirkan.

Kode uji yang menguji kode produksi sama sekali tidak sama. Saya akan menunjukkan dengan python:

def multiply(a, b):
    """Multiply ``a`` by ``b``"""
    return a*b

Maka tes sederhana adalah:

def test_multiply():
    assert multiply(4, 5) == 20

Kedua fungsi memiliki definisi yang sama tetapi keduanya melakukan hal yang sangat berbeda. Tidak ada kode duplikat di sini. ;-)

Itu juga terjadi bahwa orang menulis tes duplikat pada dasarnya memiliki satu pernyataan per fungsi tes. Ini kegilaan dan saya telah melihat orang-orang melakukan ini. Ini adalah praktik yang buruk.

def test_multiply_1_and_3():
    """Assert that a multiplication of 1 and 3 is 3."""
    assert multiply(1, 3) == 3

def test_multiply_1_and_7():
    """Assert that a multiplication of 1 and 7 is 7."""
    assert multiply(1, 7) == 7

def test_multiply_3_and_4():
    """Assert that a multiplication of 3 and 4 is 12."""
    assert multiply(3, 4) == 12

Bayangkan melakukan ini untuk 1000+ baris kode yang efektif. Alih-alih, Anda menguji berdasarkan per 'fitur':

def test_multiply_positive():
    """Assert that positive numbers can be multiplied."""
    assert multiply(1, 3) == 3
    assert multiply(1, 7) == 7
    assert multiply(3, 4) == 12

def test_multiply_negative():
    """Assert that negative numbers can be multiplied."""
    assert multiply(1, -3) == -3
    assert multiply(-1, -7) == 7
    assert multiply(-3, 4) == -12

Sekarang ketika fitur ditambahkan / dihapus saya hanya perlu mempertimbangkan untuk menambah / menghapus satu fungsi tes.

Anda mungkin telah memperhatikan saya belum menerapkan forloop. Ini karena mengulangi beberapa hal itu baik. Ketika saya akan menerapkan loop kode akan jauh lebih pendek. Tetapi ketika sebuah pernyataan gagal, itu bisa mengaburkan keluaran yang menampilkan pesan yang ambigu. Jika ini terjadi maka pengujian Anda akan kurang bermanfaat dan Anda akan memerlukan debugger untuk memeriksa kesalahan yang terjadi.

siebz0r
sumber
8
Satu pernyataan per pengujian secara teknis direkomendasikan karena itu berarti bahwa banyak masalah tidak akan muncul hanya sebagai satu kegagalan. Namun, dalam praktiknya, saya pikir agregasi pernyataan yang hati-hati mengurangi jumlah kode berulang dan saya hampir tidak pernah berpegang pada satu pernyataan per pedoman pengujian.
Rob Church
@ pink-diamond-square Saya melihat bahwa NUnit tidak berhenti menguji setelah pernyataan gagal (yang menurut saya aneh). Dalam kasus khusus itu memang lebih baik untuk memiliki satu pernyataan per tes. Jika kerangka kerja unit-testing tidak berhenti pengujian setelah pernyataan yang gagal beberapa pernyataan lebih baik.
siebz0r
3
NUnit tidak menghentikan seluruh rangkaian tes, tetapi satu tes tidak berhenti kecuali jika Anda mengambil langkah untuk mencegahnya (Anda dapat menangkap pengecualian yang dilemparnya, yang kadang-kadang berguna). Maksud saya pikir mereka membuat adalah bahwa jika Anda menulis tes yang mencakup lebih dari satu menegaskan Anda tidak akan mendapatkan semua informasi yang Anda butuhkan untuk memperbaiki masalah. Untuk mengerjakan contoh Anda, bayangkan fungsi perkalian ini tidak suka angka 3. Dalam hal ini, assert multiply(1,3)akan gagal tetapi Anda juga tidak akan mendapatkan laporan pengujian yang gagal assert multiply(3,4).
Rob Church
Saya hanya berpikir saya akan menaikkannya karena satu pernyataan per tes adalah, dari apa yang saya baca di dunia .net, "praktik yang baik" dan beberapa menegaskan adalah "penggunaan pragmatis". Itu terlihat sedikit berbeda dalam dokumentasi Python di mana contoh def test_shufflemelakukan dua menegaskan.
Rob Church
Saya setuju dan tidak setuju: D Ada pengulangan yang jelas di sini: assert multiply(*, *) == *sehingga Anda dapat mendefinisikan suatu assert_multiplyfungsi. Dalam skenario saat ini tidak masalah dengan jumlah baris dan keterbacaan, tetapi dengan tes yang lebih lama Anda dapat menggunakan kembali pernyataan yang rumit, perlengkapan, kode penghasil fixture, dll ... Saya tidak tahu apakah ini adalah praktik terbaik, tapi biasanya saya lakukan ini.
inf3rno
10

Tampaknya tes pada dasarnya mengekspresikan hal yang sama dengan kode, dan karenanya merupakan duplikat

Tidak, ini tidak benar.

Tes memiliki tujuan yang berbeda dari implementasi Anda:

  • Tes memastikan bahwa implementasi Anda berfungsi.
  • Mereka berfungsi sebagai dokumentasi: Dengan melihat tes, Anda melihat kontrak yang harus dipenuhi kode Anda, yaitu input mana yang mengembalikan output apa, kasus khusus apa, dll.
  • Selain itu, tes Anda menjamin bahwa saat Anda menambahkan fitur baru, fungsionalitas Anda yang ada tidak rusak.
Uooo
sumber
4

Tidak. KERING adalah tentang menulis kode sekali saja untuk melakukan tugas tertentu, tes adalah validasi bahwa tugas tersebut dilakukan dengan benar. Ini agak mirip dengan algoritma pemungutan suara, di mana jelas menggunakan kode yang sama akan sia-sia.

jmoreno
sumber
2

Bukankah target akhir KERING mencakup penghapusan semua kode uji?

Tidak, target akhir KERING sebenarnya berarti penghapusan semua kode produksi .

Jika pengujian kami dapat menjadi spesifikasi sempurna dari apa yang kami ingin sistem lakukan, kami hanya perlu membuat kode produksi yang sesuai (atau binari) secara otomatis, secara efektif menghapus basis kode produksi per se.

Inilah sebenarnya yang didekati oleh pendekatan yang ingin dicapai oleh arsitektur model-didorong - satu sumber kebenaran yang dirancang manusia dari mana segala sesuatu berasal dari perhitungan.

Saya tidak berpikir kebalikannya (menyingkirkan semua tes) diinginkan karena:

  • Anda harus menyelesaikan ketidaksesuaian impedansi antara implementasi dan spesifikasi. Kode produksi dapat menyampaikan niat sampai tingkat tertentu, tetapi tidak akan pernah semudah untuk beralasan tentang tes yang diungkapkan dengan baik. Kita manusia membutuhkan pandangan yang lebih tinggi tentang mengapa kita membangun sesuatu. Bahkan jika Anda tidak melakukan tes karena KERING, spesifikasi mungkin harus ditulis dalam dokumen, yang jelas merupakan binatang yang lebih berbahaya dalam hal ketidakcocokan impedansi dan desinkronisasi kode jika Anda bertanya kepada saya.
  • Sementara kode produksi dapat diturunkan dengan mudah dari spesifikasi yang dapat dieksekusi yang benar (dengan asumsi waktu yang cukup), test suite jauh lebih sulit untuk dibuat kembali dari kode akhir program. Spesifikasi tidak muncul dengan jelas hanya dengan melihat kode, karena interaksi antara unit kode saat runtime sulit dilakukan. Inilah sebabnya mengapa kami mengalami kesulitan berurusan dengan aplikasi warisan testless. Dengan kata lain: jika Anda ingin aplikasi Anda bertahan lebih dari beberapa bulan, Anda mungkin akan lebih baik kehilangan hard drive yang meng-host basis kode produksi Anda daripada yang ada di tempat test suite Anda.
  • Jauh lebih mudah untuk memperkenalkan bug secara tidak sengaja dalam kode produksi daripada dalam kode pengujian. Dan karena kode produksi tidak memverifikasi sendiri (meskipun ini dapat didekati dengan Desain dengan Kontrak atau sistem tipe yang lebih kaya), kita masih memerlukan beberapa program eksternal untuk mengujinya dan memperingatkan kita jika terjadi regresi.
guillaume31
sumber
1

Karena terkadang mengulangi diri sendiri tidak apa-apa. Tidak satu pun dari prinsip-prinsip ini dimaksudkan untuk diambil dalam setiap keadaan tanpa pertanyaan atau konteks. Kadang-kadang saya memiliki tes tertulis terhadap versi algoritma yang naif (dan lambat), yang merupakan pelanggaran DRY yang cukup jelas, tetapi jelas bermanfaat.

U2EF1
sumber
1

Karena pengujian unit adalah tentang membuat perubahan yang tidak disengaja lebih sulit, kadang-kadang dapat membuat perubahan yang disengaja lebih sulit juga. Fakta ini memang terkait dengan prinsip KERING.

Misalnya, jika Anda memiliki fungsi MyFunctionyang disebut dalam kode produksi hanya di satu tempat, dan Anda menulis 20 unit tes untuk itu, Anda dapat dengan mudah memiliki 21 tempat di kode Anda di mana fungsi itu dipanggil. Sekarang, ketika Anda harus mengubah tanda tangan MyFunction, atau semantik, atau keduanya (karena beberapa persyaratan berubah), Anda memiliki 21 tempat untuk berubah, bukan hanya satu. Dan alasannya memang melanggar prinsip KERING: Anda mengulangi (setidaknya) panggilan fungsi yang sama hingga MyFunction21 kali.

Pendekatan yang benar untuk kasus seperti itu adalah menerapkan prinsip KERING ke kode pengujian Anda juga: ketika menulis 20 unit tes, merangkum panggilan ke MyFunctiondalam unit test Anda hanya dalam beberapa fungsi pembantu (idealnya hanya satu), yang digunakan oleh 20 unit tes. Idealnya, Anda hanya memiliki dua tempat dalam pemanggilan kode Anda MyFunction: satu dari kode produksi Anda, dan satu dari Anda unit test. Jadi, ketika Anda harus mengubah tanda tangan MyFunctionnanti, Anda hanya akan memiliki beberapa tempat untuk berubah dalam tes Anda.

"Beberapa tempat" masih lebih dari "satu tempat" (apa yang Anda dapatkan tanpa tes unit sama sekali), tetapi keuntungan memiliki tes unit harus jauh lebih besar daripada keuntungan memiliki lebih sedikit kode untuk diubah (jika tidak, Anda melakukan pengujian unit secara lengkap salah).

Doc Brown
sumber
0

Salah satu tantangan terbesar untuk membangun perangkat lunak adalah untuk menangkap persyaratan; itu untuk menjawab pertanyaan, "apa yang harus dilakukan perangkat lunak ini?" Perangkat lunak membutuhkan persyaratan yang tepat untuk secara akurat mendefinisikan apa yang perlu dilakukan sistem, tetapi mereka yang mendefinisikan kebutuhan untuk sistem dan proyek perangkat lunak sering kali menyertakan orang yang tidak memiliki latar belakang perangkat lunak atau formal (matematika). Kurangnya ketelitian dalam definisi persyaratan memaksa pengembangan perangkat lunak untuk menemukan cara untuk memvalidasi perangkat lunak sesuai persyaratan.

Tim pengembang menemukan diri mereka menerjemahkan deskripsi sehari-hari untuk proyek menjadi persyaratan yang lebih ketat. Disiplin pengujian telah bersatu sebagai titik pemeriksaan untuk pengembangan perangkat lunak, untuk menjembatani kesenjangan antara apa yang pelanggan katakan mereka inginkan, dan perangkat lunak apa yang mengerti yang mereka inginkan. Baik pengembang perangkat lunak dan tim kualitas / pengujian membentuk pemahaman tentang spesifikasi (informal), dan masing-masing (secara mandiri) menulis perangkat lunak atau tes untuk memastikan bahwa pemahaman mereka sesuai. Menambahkan orang lain untuk memahami persyaratan (tidak tepat) menambahkan pertanyaan dan perspektif yang berbeda untuk lebih mengasah ketepatan persyaratan.

Karena selalu ada pengujian penerimaan, wajar untuk memperluas peran pengujian untuk menulis pengujian otomatis dan unit. Masalahnya adalah itu berarti merekrut programmer untuk melakukan pengujian, dan dengan demikian Anda mempersempit perspektif dari jaminan kualitas untuk programmer melakukan pengujian.

Itu semua mengatakan, Anda mungkin melakukan pengujian yang salah jika tes Anda sedikit berbeda dari program yang sebenarnya. Saran Msdy adalah lebih fokus pada apa yang ada di tes, dan lebih sedikit pada bagaimana.

Ironisnya adalah bahwa daripada menangkap spesifikasi formal persyaratan dari deskripsi sehari-hari, industri telah memilih untuk menerapkan tes titik sebagai kode untuk mengotomatisasi pengujian. Daripada menghasilkan persyaratan formal yang perangkat lunak dapat dibangun untuk menjawab, pendekatan yang diambil adalah untuk menguji beberapa poin, daripada pendekatan membangun perangkat lunak menggunakan logika formal. Ini adalah kompromi, tetapi telah cukup efektif dan relatif berhasil.

ChuckCottrill
sumber
0

Jika Anda merasa kode pengujian Anda terlalu mirip dengan kode implementasi Anda, ini mungkin merupakan indikasi bahwa Anda terlalu banyak menggunakan kerangka kerja mengejek. Pengujian berbasis mock pada level yang terlalu rendah dapat berakhir dengan pengaturan pengujian yang sangat mirip dengan metode yang sedang diuji. Cobalah untuk menulis tes tingkat yang lebih tinggi yang cenderung rusak jika Anda mengubah implementasi Anda (saya tahu ini bisa sulit, tetapi jika Anda bisa mengelolanya Anda akan memiliki test suite yang lebih berguna sebagai hasilnya).

Jules
sumber
0

Tes unit tidak boleh menyertakan duplikasi kode yang sedang diuji, seperti yang telah dicatat.

Saya akan menambahkan, bahwa, unit test biasanya tidak KERING seperti kode "produksi", karena setup cenderung serupa (tetapi tidak identik) di tes ... terutama jika Anda memiliki sejumlah besar dependensi yang Anda mengejek / berpura-pura.
Tentu saja mungkin untuk memperbaiki hal semacam ini menjadi metode pengaturan umum (atau serangkaian metode pengaturan) ... tetapi saya telah menemukan bahwa metode pengaturan tersebut cenderung memiliki daftar parameter yang panjang dan agak rapuh.

Jadi bersikaplah pragmatis. Jika Anda dapat mengkonsolidasikan kode pengaturan tanpa mengurangi perawatan, dengan cara apa pun melakukannya. Tetapi jika alternatifnya adalah serangkaian metode pengaturan yang kompleks dan rapuh, sedikit pengulangan dalam metode pengujian Anda OK.

Seorang penginjil lokal TDD / BDD menyatakan seperti ini:
"Kode produksi Anda harus KERING. Tapi tidak apa-apa untuk pengujian Anda menjadi 'lembab'."

David
sumber
0

Tampaknya tes pada dasarnya mengekspresikan hal yang sama dengan kode, dan karenanya merupakan duplikat (dalam konsep, bukan implementasi) dari kode.

Ini tidak benar, tes menggambarkan use case, sedangkan kode menggambarkan algoritma yang melewati use case, jadi mana yang lebih umum. Dengan TDD Anda mulai dengan menulis use case (mungkin berdasarkan kisah pengguna) dan setelah itu Anda mengimplementasikan kode yang diperlukan untuk melewati use case ini. Jadi Anda menulis tes kecil, sepotong kecil kode, dan setelah itu Anda refactor jika perlu untuk menyingkirkan pengulangan. Begitulah cara kerjanya.

Dengan tes bisa ada pengulangan juga. Misalnya Anda dapat menggunakan kembali perlengkapan, kode penghasil fixture, pernyataan rumit, dll ... Saya biasanya melakukan ini, untuk mencegah bug dalam tes, tetapi saya biasanya lupa untuk menguji dulu apakah tes benar-benar gagal, dan itu dapat benar-benar merusak hari. , ketika Anda mencari bug dalam kode selama setengah jam dan tes salah ... xD

inf3rno
sumber