Bagaimana tepatnya tes unit harus ditulis tanpa mengejek secara luas?

80

Seperti yang saya mengerti, titik unit test adalah untuk menguji unit kode secara terpisah . Ini berarti, bahwa:

  1. Mereka tidak boleh dipecah oleh perubahan kode yang tidak terkait di tempat lain dalam basis kode.
  2. Hanya satu tes unit yang harus dipecahkan oleh bug di unit yang diuji, yang bertentangan dengan tes integrasi (yang mungkin pecah dalam tumpukan).

Semua ini menyiratkan, bahwa setiap ketergantungan luar dari unit yang diuji, harus dipermainkan. Dan maksud saya semua dependensi luar , tidak hanya "lapisan luar" seperti jaringan, sistem file, database, dll.

Ini mengarah pada kesimpulan logis, bahwa hampir setiap unit tes perlu diejek . Di sisi lain, pencarian Google cepat tentang mengejek mengungkapkan banyak artikel yang mengklaim bahwa "mengejek adalah bau kode" dan sebagian besar (walaupun tidak sepenuhnya) harus dihindari.

Sekarang, ke pertanyaan.

  1. Bagaimana seharusnya tes unit ditulis dengan benar?
  2. Di mana tepatnya garis antara mereka dan tes integrasi berada?

Perbarui 1

Silakan pertimbangkan kode pseudo berikut:

class Person {
    constructor(calculator) {}

    calculate(a, b) {
        const sum = this.calculator.add(a, b);

        // do some other stuff with the `sum`
    }
}

Dapatkah tes yang menguji Person.calculatemetode tanpa mengejek Calculatorketergantungan (diberikan, bahwa Calculatorkelas ringan yang tidak mengakses "dunia luar") dianggap sebagai unit test?

Alexander Lomia
sumber
10
Bagian dari ini hanya pengalaman desain yang akan datang seiring waktu. Anda akan belajar bagaimana menyusun komponen Anda sehingga mereka tidak memiliki banyak kesulitan untuk mengejek ketergantungan. Ini berarti testabilitas harus menjadi tujuan desain sekunder dari perangkat lunak apa pun. Sasaran ini jauh lebih mudah dipenuhi jika tes ditulis sebelum atau bersama-sama dengan kode, misalnya menggunakan TDD dan / atau BDD.
amon
33
Letakkan tes yang berjalan cepat dan andal ke dalam satu folder. Letakkan tes yang lambat dan berpotensi rapuh ke yang lain. Jalankan tes di folder pertama sesering mungkin (secara harfiah setiap kali Anda berhenti mengetik dan kompilasi kode adalah yang ideal, tetapi tidak semua lingkungan dev mendukung ini). Jalankan tes lebih lambat lebih jarang (ketika Anda memiliki coffee break misalnya). Jangan khawatir tentang nama unit dan integrasi. Panggil mereka dengan cepat dan lambat jika Anda mau. Itu tidak masalah.
David Arno
6
"Bahwa hampir setiap unit tes perlu diejek" Ya, jadi? "pencarian Google cepat tentang mengejek mengungkapkan banyak artikel yang mengklaim bahwa" mengejek adalah bau kode "" mereka salah .
Michael
13
@Michael Cukup dengan menyatakan 'yeah, so' dan menyatakan pandangan yang berlawanan salah bukanlah cara yang bagus untuk mendekati subjek yang kontroversial seperti ini. Mungkin menulis jawaban dan menguraikan mengapa Anda berpikir mengejek harus ada di mana-mana, dan mungkin mengapa Anda berpikir 'banyak artikel' secara inheren salah?
AJFaraday
6
Karena Anda tidak memberikan kutipan untuk "mengejek adalah bau kode", saya hanya bisa menebak bahwa Anda salah menafsirkan apa yang Anda baca. Mengejek bukanlah bau kode. Kebutuhan untuk menggunakan refleksi atau shenanigans lainnya untuk menyuntikkan ejekan Anda adalah bau kode. Kesulitan mengejek berbanding terbalik dengan kualitas desain API Anda. Jika Anda dapat menulis tes unit sederhana langsung yang hanya mengolok-olok konstruktor, Anda melakukannya dengan benar.
TKK

Jawaban:

59

titik unit test adalah untuk menguji unit kode secara terpisah.

Martin Fowler pada Unit Test

Pengujian unit sering dibicarakan dalam pengembangan perangkat lunak, dan merupakan istilah yang saya kenal selama program menulis sepanjang waktu saya. Namun, seperti kebanyakan terminologi pengembangan perangkat lunak, istilah ini sangat tidak jelas, dan saya melihat kerancuan sering terjadi ketika orang berpikir bahwa itu lebih tepat daripada yang sebenarnya.

Apa yang ditulis Kent Beck dalam Test Driven Development, By Example

Saya menyebutnya "tes unit", tetapi tidak cocok dengan definisi tes unit yang diterima dengan baik

Klaim "titik unit test" yang diberikan adalah "akan sangat bergantung pada definisi" unit test "apa yang dipertimbangkan.

Jika perspektif Anda adalah bahwa program Anda terdiri dari banyak unit kecil yang saling bergantung satu sama lain, dan jika Anda membatasi diri pada gaya yang menguji setiap unit secara terpisah, maka banyak tes ganda merupakan kesimpulan yang tidak dapat dihindari.

Saran yang bertentangan yang Anda lihat berasal dari orang-orang yang beroperasi di bawah serangkaian asumsi yang berbeda.

Misalnya, jika Anda menulis tes untuk mendukung pengembang selama proses refactoring, dan membagi satu unit menjadi dua adalah refactoring yang harus didukung, maka sesuatu harus diberikan. Mungkin tes semacam ini membutuhkan nama yang berbeda? Atau mungkin kita membutuhkan pemahaman yang berbeda tentang "unit".

Anda mungkin ingin membandingkan:

Dapatkah tes yang menguji metode Person.calculate tanpa mengejek ketergantungan Kalkulator (mengingat, bahwa Kalkulator adalah kelas ringan yang tidak mengakses "dunia luar") dianggap sebagai unit test?

Saya pikir itu pertanyaan yang salah untuk ditanyakan; lagi-lagi argumen tentang label , ketika saya percaya apa yang sebenarnya kita pedulikan adalah properti .

Ketika saya memperkenalkan perubahan pada kode, saya tidak peduli tentang isolasi tes - saya sudah tahu bahwa "kesalahan" ada di suatu tempat di tumpukan saat ini dari suntingan yang belum diverifikasi. Jika saya sering menjalankan tes, maka saya membatasi kedalaman tumpukan itu, dan menemukan kesalahan itu sepele (dalam kasus ekstrim, tes dijalankan setelah setiap edit - kedalaman maksimal tumpukan adalah satu). Tetapi menjalankan tes bukanlah tujuannya - ini adalah gangguan - jadi ada nilai dalam mengurangi dampak gangguan. Salah satu cara untuk mengurangi gangguan adalah untuk memastikan bahwa tes cepat ( Gary Bernhardt menyarankan 300 ms , tapi saya belum menemukan cara melakukannya dalam keadaan saya).

Jika memohon Calculator::addtidak secara signifikan meningkatkan waktu yang diperlukan untuk menjalankan tes (atau properti penting lainnya untuk kasus penggunaan ini), maka saya tidak akan repot menggunakan uji ganda - itu tidak memberikan manfaat yang lebih besar daripada biaya .

Perhatikan dua asumsi di sini: manusia sebagai bagian dari evaluasi biaya, dan tumpukan pendek dari perubahan yang belum diverifikasi dalam evaluasi manfaat. Dalam keadaan di mana kondisi tersebut tidak berlaku, nilai "isolasi" berubah sedikit.

Lihat juga Hot Lava , oleh Harry Percival.

VoiceOfUnreason
sumber
5
Satu hal yang dilakukan oleh mengejek adalah membuktikan bahwa kalkulator dapat diejek, yaitu bahwa desain tidak memasangkan orang dan kalkulator (meskipun ini juga dapat diperiksa dengan cara lain)
jk.
40

Bagaimana tepatnya tes unit harus ditulis tanpa mengejek secara luas?

Dengan meminimalkan efek samping dalam kode Anda.

Mengambil contoh kode Anda, jika calculatormisalnya berbicara ke API web, maka Anda dapat membuat tes rapuh yang mengandalkan kemampuan untuk berinteraksi dengan API web itu, atau Anda membuat tiruannya. Namun, jika ini merupakan fungsi perhitungan yang deterministik dan bebas-negara, maka Anda tidak (dan tidak seharusnya) mengejeknya. Jika ya, Anda berisiko tiruan Anda berperilaku berbeda terhadap kode asli, yang mengarah ke bug dalam pengujian Anda.

Mock hanya diperlukan untuk kode yang membaca / menulis ke sistem file, database, URL titik akhir dll; yang tergantung pada lingkungan tempat Anda menjalankan; atau yang sangat stateful dan non-deterministik. Jadi, jika Anda menjaga agar bagian-bagian kode menjadi minimum dan menyembunyikannya di belakang abstraksi, maka mereka mudah untuk diejek dan sisa kode Anda menghindari perlunya mengejek.

Untuk poin kode yang memiliki efek samping, ada baiknya menulis tes yang mengejek dan tes yang tidak. Yang terakhir perlu perawatan karena mereka akan rapuh dan mungkin lambat. Jadi, Anda mungkin ingin hanya menjalankannya semalaman di server CI, daripada setiap kali Anda menyimpan dan membangun kode Anda. Tes sebelumnya harus dijalankan sesering mungkin. Seperti apakah setiap tes kemudian tes unit atau integrasi menjadi akademik dan menghindari "perang api" atas apa yang merupakan dan bukan tes unit.

David Arno
sumber
8
Ini adalah jawaban yang benar baik dalam praktik maupun dalam hal menghindari debat semantik yang tidak berarti.
Jared Smith
Apakah Anda memiliki contoh basis kode sumber terbuka non-sepele yang menggunakan gaya seperti itu dan masih mendapatkan cakupan pengujian yang baik?
Joeri Sebrechts
4
@ JoeriSebrechts setiap satu FP? Contoh
Jared Smith
Tidak cukup apa yang saya cari, karena itu hanya kumpulan fungsi yang independen satu sama lain, bukan sistem fungsi yang saling memanggil. Bagaimana Anda menyiasati harus membangun argumen kompleks ke fungsi untuk tujuan mengujinya, jika fungsi itu adalah salah satu yang tingkat atas? Misalnya loop inti dari sebuah game.
Joeri Sebrechts
1
@ JoeriSebrechts hmm, entah saya salah paham apa yang Anda inginkan, atau Anda tidak menggali cukup dalam contoh yang saya berikan. Fungsi ramda menggunakan panggilan internal di semua tempat di sumbernya (mis R.equals.). Karena ini sebagian besar fungsi murni, mereka umumnya tidak dipermainkan dalam tes.
Jared Smith
31

Pertanyaan-pertanyaan ini sangat berbeda dalam kesulitannya. Mari kita ambil pertanyaan 2 dulu.

Tes unit dan tes integrasi dipisahkan dengan jelas. Tes unit menguji satu unit (metode atau kelas) dan menggunakan unit lain hanya sebanyak yang diperlukan untuk mencapai tujuan itu. Mengejek mungkin diperlukan, tetapi itu bukan titik ujian. Tes integrasi menguji interaksi antara unit aktual yang berbeda . Perbedaan ini adalah seluruh alasan mengapa kita membutuhkan pengujian unit dan integrasi - jika seseorang melakukan pekerjaan yang cukup baik, kita tidak akan melakukannya, tetapi ternyata biasanya lebih efisien menggunakan dua alat khusus daripada satu alat umum .

Sekarang untuk pertanyaan penting: Bagaimana seharusnya Anda menguji unit? Seperti dikatakan di atas, unit test harus membangun struktur tambahan hanya sejauh yang diperlukan . Seringkali lebih mudah untuk menggunakan database mock dari database yang nyata atau bahkan setiap database yang nyata. Namun, mengejek itu sendiri tidak memiliki nilai. Jika sering terjadi itu sebenarnya lebih mudah untuk menggunakan komponen aktual dari lapisan lain sebagai input untuk uji unit tingkat menengah. Jika demikian, jangan ragu untuk menggunakannya.

Banyak praktisi takut bahwa jika tes unit B menggunakan kembali kelas yang sudah diuji oleh tes unit A, maka cacat pada unit A menyebabkan kegagalan tes di banyak tempat. Saya menganggap ini bukan masalah: test suite harus berhasil 100% untuk memberi Anda kepastian yang Anda butuhkan, jadi bukan masalah besar untuk memiliki terlalu banyak kegagalan - lagi pula, Anda memang memiliki cacat. Satu-satunya masalah kritis adalah jika cacat memicu terlalu sedikit kegagalan.

Karena itu, jangan membuat agama mengejek. Ini adalah cara, bukan tujuan, jadi jika Anda bisa menghindari upaya ekstra, Anda harus melakukannya.

Kilian Foth
sumber
4
The only critical problem would be if a defect triggered too few failures.ini adalah salah satu kelemahan dari mengejek. Kita harus "memprogram" perilaku yang diharapkan jadi, kita mungkin gagal melakukannya, menyebabkan tes kita berakhir sebagai "false positive". Tetapi mengejek adalah teknik yang sangat berguna untuk mencapai determinisme (kondisi pengujian yang paling penting). Saya menggunakannya di semua proyek saya bila memungkinkan. Mereka juga menunjukkan kepada saya ketika integrasi terlalu kompleks atau ketergantungan terlalu ketat.
Laiv
1
Jika unit yang sedang diuji menggunakan unit lain, bukankah itu benar-benar tes integrasi? Karena pada dasarnya unit ini akan menguji interaksi antara unit-unit tersebut, persis seperti tes integrasi.
Alexander Lomia
11
@AlexanderLomia: Apa yang Anda sebut unit? Apakah Anda akan memanggil 'String' sebagai unit juga? Ya, tapi saya tidak akan bermimpi mengejeknya.
Bart van Ingen Schenau
5
" Tes unit dan tes integrasi jelas dipisahkan. Tes unit menguji satu unit (metode atau kelas) dan menggunakan unit lain hanya sebanyak yang diperlukan untuk mencapai tujuan itu ". Inilah intinya. Itu definisi Anda tentang unit test. Milik saya sangat berbeda. Jadi perbedaan di antara mereka hanya "dipisahkan dengan jelas" untuk definisi yang diberikan tetapi pemisahan bervariasi antara definisi.
David Arno
4
@ Voo Setelah bekerja dengan basis kode seperti itu, sementara itu bisa menjadi gangguan untuk menemukan masalah asli (terutama jika arsitektur telah melukis hal-hal yang akan Anda gunakan untuk debug itu), saya masih memiliki lebih banyak masalah dari mengejek yang menyebabkan tes untuk tetap bekerja setelah kode yang mereka gunakan untuk tes telah rusak.
James_pic
6

OK, jadi untuk menjawab pertanyaan Anda secara langsung:

Bagaimana seharusnya tes unit ditulis dengan benar?

Seperti yang Anda katakan, Anda harus mengejek dependensi dan hanya menguji unit yang dimaksud.

Di mana tepatnya garis antara mereka dan tes integrasi terletak?

Tes Integrasi adalah tes unit di mana dependensi Anda tidak diejek.

Bisakah tes yang menguji metode Person.calculate tanpa mengejek Kalkulator dianggap sebagai unit test?

Tidak. Anda harus menyuntikkan dependensi kalkulator ke dalam kode ini dan Anda memiliki pilihan antara versi tiruan atau yang asli. Jika Anda menggunakan yang diejek itu adalah unit test, jika Anda menggunakan yang asli itu tes integrasi.

Namun, sebuah peringatan. apakah Anda benar-benar peduli apa yang menurut orang tes Anda seharusnya disebut?

Tetapi pertanyaan sebenarnya Anda tampaknya adalah ini:

pencarian Google cepat tentang mengejek mengungkapkan banyak artikel yang mengklaim bahwa "mengejek adalah bau kode" dan sebagian besar (walaupun tidak sepenuhnya) harus dihindari.

Saya pikir masalahnya di sini adalah bahwa banyak orang menggunakan tiruan untuk sepenuhnya menciptakan kembali dependensi. Misalnya saya dapat mengejek kalkulator dalam contoh Anda sebagai

public class MockCalc : ICalculator
{
     public Add(int a, int b) { return 4; }
}

Saya tidak akan melakukan sesuatu seperti:

myMock = Mock<ICalculator>().Add((a,b) => {return a + b;})
myPerson.Calculate()
Assert.WasCalled(myMock.Add());

Saya berpendapat bahwa, itu akan "menguji mock saya" atau "menguji implementasi". Saya akan mengatakan " Jangan menulis Mock! * Seperti itu".

Orang lain akan tidak setuju dengan saya, kami akan memulai perang api besar di blog kami tentang Cara terbaik untuk Mock, yang benar-benar tidak masuk akal kecuali Anda memahami seluruh latar belakang dari berbagai pendekatan dan benar-benar tidak menawarkan banyak nilai. kepada seseorang yang hanya ingin menulis tes yang bagus.

Ewan
sumber
Terima kasih atas jawaban lengkapnya. Pada saat saya peduli dengan apa yang dipikirkan orang lain tentang tes saya - sebenarnya saya ingin menghindari penulisan semi-integrasi, tes semi-unit yang cenderung menjadi berantakan ketika proyek berlangsung.
Alexander Lomia
tidak masalah, saya pikir masalahnya adalah bahwa definisi dari kedua hal tersebut tidak 100% disetujui oleh semua orang.
Ewan
Saya akan mengubah nama kelas Anda MockCalcmenjadi StubCalc, dan menyebutnya sebuah rintisan bukan tiruan. martinfowler.com/articles/…
bdsl
@ bdsl Artikel itu berumur 15 tahun
Ewan
4
  1. Bagaimana seharusnya unit test dilaksanakan dengan benar?

Aturan praktis saya adalah unit test yang tepat:

  • Dikodekan dengan antarmuka, bukan implementasi . Ini memiliki banyak manfaat. Pertama, itu memastikan bahwa kelas Anda mengikuti Prinsip Ketergantungan Inversi dari SOLID . Juga, ini yang dilakukan oleh kelas Anda yang lain ( benar? ) Sehingga tes Anda harus melakukan hal yang sama. Selain itu, ini memungkinkan Anda untuk menguji beberapa implementasi dari antarmuka yang sama sambil menggunakan kembali banyak kode uji (hanya inisialisasi dan beberapa pernyataan yang akan berubah).
  • Mandiri . Seperti yang Anda katakan, perubahan kode luar apa pun tidak dapat memengaruhi hasil tes. Dengan demikian, pengujian unit dapat dijalankan pada waktu-bangun. Ini berarti Anda perlu mengejek untuk menghilangkan efek samping. Namun, jika Anda mengikuti Prinsip Ketergantungan Pembalikan, ini seharusnya relatif mudah. Kerangka kerja pengujian yang baik seperti Spock dapat digunakan untuk secara dinamis menyediakan implementasi tiruan dari semua antarmuka untuk digunakan dalam pengujian Anda dengan pengkodean minimal. Ini berarti bahwa setiap kelas uji hanya perlu menggunakan kode dari tepat satu kelas implementasi, ditambah kerangka uji (dan mungkin model kelas ["kacang"]).
  • Tidak memerlukan aplikasi yang berjalan terpisah . Jika tes perlu "berbicara dengan sesuatu", apakah itu database atau layanan web, itu adalah tes integrasi, bukan tes unit. Saya menarik garis pada koneksi jaringan atau sistem file. Database SQLite murni dalam memori, misalnya, adalah permainan yang adil menurut pendapat saya untuk unit test jika Anda benar-benar membutuhkannya.

Jika ada kelas utilitas dari kerangka kerja yang mempersulit pengujian unit, Anda bahkan mungkin merasa berguna untuk membuat antarmuka dan kelas "wrapper" yang sangat sederhana untuk memfasilitasi mengejek dependensi tersebut. Pembungkus-pembungkus itu tidak harus diuji unit.

  1. Di mana tepatnya garis di antara mereka [tes unit] dan tes integrasi berada?

Saya telah menemukan perbedaan ini menjadi yang paling berguna:

  • Tes unit mensimulasikan "kode pengguna" , memverifikasi perilaku kelas implementasi terhadap perilaku yang diinginkan dan semantik antarmuka tingkat kode .
  • Tes integrasi mensimulasikan pengguna , memverifikasi perilaku aplikasi yang sedang berjalan terhadap kasus penggunaan yang ditentukan dan / atau API formal. Untuk layanan web, "pengguna" akan menjadi aplikasi klien.

Ada area abu-abu di sini. Misalnya, jika Anda dapat menjalankan aplikasi dalam wadah Docker dan menjalankan tes integrasi sebagai tahap akhir pembuatan, dan menghancurkan wadah setelahnya, apakah boleh memasukkan tes-tes tersebut sebagai "tes unit"? Jika ini perdebatan sengit Anda, Anda berada di tempat yang cukup bagus.

  1. Benarkah bahwa hampir setiap unit tes perlu diejek?

Tidak. Beberapa kasus uji individual akan untuk kondisi kesalahan, seperti lulus nullsebagai parameter dan memverifikasi Anda mendapatkan pengecualian. Banyak tes seperti itu tidak akan membutuhkan cemoohan. Juga, implementasi yang tidak memiliki efek samping, misalnya pemrosesan string atau fungsi matematika, mungkin tidak memerlukan cemoohan karena Anda cukup memverifikasi output. Tetapi sebagian besar kelas bernilai, saya pikir, akan membutuhkan setidaknya satu tiruan di suatu tempat dalam kode tes. (Semakin sedikit, semakin baik.)

Masalah "bau kode" yang Anda sebutkan muncul ketika Anda memiliki kelas yang terlalu rumit, yang membutuhkan daftar panjang dependensi tiruan untuk menulis tes Anda. Ini adalah petunjuk bahwa Anda perlu memperbaiki implementasi dan membagi hal-hal, sehingga setiap kelas memiliki tapak yang lebih kecil dan tanggung jawab yang lebih jelas, dan karenanya lebih mudah diuji. Ini akan meningkatkan kualitas dalam jangka panjang.

Hanya satu unit tes yang harus dipecahkan oleh bug di unit yang diuji.

Saya tidak berpikir ini adalah harapan yang masuk akal, karena itu berfungsi melawan penggunaan kembali. Anda mungkin memiliki privatemetode, misalnya, yang dipanggil dengan beberapa publicmetode yang diterbitkan oleh antarmuka Anda. Bug yang diperkenalkan ke dalam satu metode itu kemudian dapat menyebabkan beberapa kegagalan pengujian. Ini tidak berarti Anda harus menyalin kode yang sama ke setiap publicmetode.

wberry
sumber
3
  1. Mereka tidak boleh dipecah oleh perubahan kode yang tidak terkait di tempat lain dalam basis kode.

Saya tidak begitu yakin bagaimana aturan ini bermanfaat. Jika perubahan dalam satu kelas / metode / apa pun dapat merusak perilaku yang lain dalam kode produksi, maka hal-hal tersebut, pada kenyataannya, kolaborator, dan tidak terkait. Jika tes Anda rusak dan kode produksi Anda tidak, maka tes Anda dicurigai.

  1. Hanya satu tes unit yang harus dipecahkan oleh bug di unit yang diuji, yang bertentangan dengan tes integrasi (yang mungkin pecah dalam tumpukan).

Saya akan menganggap aturan ini dengan kecurigaan juga. Jika Anda benar-benar cukup baik untuk menyusun kode Anda dan menulis tes Anda sehingga satu bug menyebabkan kegagalan satu unit, maka Anda mengatakan bahwa Anda telah mengidentifikasi semua bug potensial, bahkan ketika basis kode berkembang untuk menggunakan kasing yang Anda miliki. belum diantisipasi.

Di mana tepatnya garis antara mereka dan tes integrasi terletak?

Saya tidak berpikir itu perbedaan penting. Apa itu 'unit' kode?

Cobalah untuk menemukan titik masuk di mana Anda dapat menulis tes yang 'masuk akal' dalam hal masalah domain / aturan bisnis yang ditangani oleh tingkat kode tersebut. Seringkali tes ini agak 'fungsional' - dimasukkan ke dalam input, dan uji bahwa output seperti yang diharapkan. Jika tes menyatakan perilaku sistem yang diinginkan, maka tes tersebut sering tetap stabil bahkan ketika kode produksi berevolusi dan direaktor ulang.

Bagaimana tepatnya tes unit harus ditulis tanpa mengejek secara luas?

Jangan membaca terlalu banyak ke dalam kata 'unit', dan condong ke arah menggunakan kelas produksi nyata Anda dalam pengujian, tanpa terlalu khawatir jika Anda melibatkan lebih dari satu dari mereka dalam tes. Jika salah satu dari mereka sulit digunakan (karena butuh banyak inisialisasi, atau perlu menekan database nyata / server email dll), maka biarkan pikiran Anda beralih ke mengejek / berpura-pura.

topo morto
sumber
" Bagaimanapun, 'unit' kode apa? " Pertanyaan yang sangat bagus yang dapat memiliki jawaban tak terduga yang bahkan mungkin bergantung pada siapa yang menjawab. Biasanya, sebagian besar definisi tes unit menjelaskannya sebagai berkaitan dengan metode atau kelas tapi itu bukan ukuran yang benar-benar berguna dari "unit" dalam semua kasus. Jika saya memiliki Person:tellStory()metode menggabungkan detail seseorang ke dalam string kemudian mengembalikannya, maka "cerita" mungkin satu unit. Jika saya membuat metode pembantu pribadi yang menghilangkan beberapa kode, maka saya tidak percaya saya telah memperkenalkan unit baru - saya tidak perlu mengujinya secara terpisah.
VLAZ
2

Pertama, beberapa definisi:

Sebuah unit menguji unit secara terpisah dari unit lain, tetapi apa artinya itu tidak secara konkret ditentukan oleh sumber otoritatif, jadi mari kita mendefinisikannya sedikit lebih baik: Jika batas I / O dilintasi (apakah I / O itu jaringan, disk, layar, atau input UI), ada tempat semi-objektif yang dapat kita gambar garis. Jika kode tergantung pada I / O, itu melintasi batas unit, dan karena itu perlu mengejek unit yang bertanggung jawab untuk I / O itu.

Di bawah definisi itu, saya tidak melihat alasan kuat untuk mengejek hal-hal seperti fungsi murni, yang berarti bahwa pengujian unit cocok untuk fungsi murni, atau fungsi tanpa efek samping.

Jika Anda ingin unit unit tes dengan efek, unit yang bertanggung jawab atas efek harus diejek, tetapi mungkin Anda harus mempertimbangkan tes integrasi. Jadi, jawaban singkatnya adalah: "jika Anda perlu mengejek, tanyakan pada diri sendiri apakah yang benar-benar Anda butuhkan adalah tes integrasi." Tapi ada jawaban yang lebih baik, lebih lama di sini, dan lubang kelinci jauh lebih dalam. Mengolok-olok mungkin bau kode favorit saya karena ada begitu banyak yang bisa dipelajari dari mereka.

Kode Berbau

Untuk ini, kami akan beralih ke Wikipedia:

Dalam pemrograman komputer, bau kode adalah ciri khas apa pun dalam kode sumber program yang mungkin mengindikasikan masalah yang lebih dalam.

Itu berlanjut nanti ...

"Bau adalah struktur tertentu dalam kode yang mengindikasikan pelanggaran prinsip-prinsip desain dasar dan berdampak negatif pada kualitas desain". Suryanarayana, Girish (November 2014). Refactoring untuk Bau Desain Perangkat Lunak. Morgan Kaufmann. hal. 258.

Bau kode biasanya bukan bug; mereka tidak salah secara teknis dan tidak mencegah program berfungsi. Sebaliknya, mereka menunjukkan kelemahan dalam desain yang dapat memperlambat pengembangan atau meningkatkan risiko bug atau kegagalan di masa depan.

Dengan kata lain, tidak semua kode bau. Sebaliknya, mereka adalah indikasi umum bahwa sesuatu mungkin tidak diungkapkan dalam bentuk optimal, dan bau dapat mengindikasikan peluang untuk memperbaiki kode yang dimaksud.

Dalam kasus ejekan, bau menandakan bahwa unit yang tampaknya membutuhkan ejekan tergantung pada unit yang akan diejek. Ini mungkin merupakan indikasi bahwa kita belum menguraikan masalah menjadi potongan-potongan yang dapat dipecahkan secara atom, dan itu bisa menunjukkan cacat desain pada perangkat lunak.

Inti dari semua pengembangan perangkat lunak adalah proses memecah masalah besar menjadi lebih kecil, potongan-potongan independen (dekomposisi) dan menyusun solusi bersama untuk membentuk aplikasi yang memecahkan masalah besar (komposisi).

Mengejek diperlukan ketika unit yang digunakan untuk memecah masalah besar menjadi bagian-bagian yang lebih kecil bergantung satu sama lain. Dengan kata lain, mengejek diperlukan ketika unit atom yang seharusnya kita komposisi tidak benar-benar atomik, dan strategi dekomposisi kita telah gagal untuk menguraikan masalah yang lebih besar menjadi lebih kecil, masalah independen yang harus diselesaikan.

Apa yang membuat mengolok-olok bau kode bukanlah bahwa ada sesuatu yang salah dengan mengolok-olok - kadang-kadang itu sangat berguna. Apa yang membuatnya menjadi bau kode adalah bahwa itu bisa menunjukkan sumber kopling yang bermasalah dalam aplikasi Anda. Kadang-kadang menghilangkan sumber sambungan jauh lebih produktif daripada menulis tiruan.

Ada banyak jenis kopling, dan beberapa lebih baik daripada yang lain. Memahami bahwa mengejek adalah bau kode dapat mengajarkan Anda untuk mengidentifikasi dan menghindari jenis terburuk di awal siklus hidup desain aplikasi, sebelum bau berkembang menjadi sesuatu yang lebih buruk.

Eric Elliott
sumber
1

Mengejek seharusnya hanya digunakan sebagai upaya terakhir, bahkan dalam tes unit.

Metode bukanlah sebuah unit, dan bahkan sebuah kelas bukanlah sebuah unit. Unit adalah setiap pemisahan logis dari kode yang masuk akal, terlepas dari apa yang Anda sebut. Elemen penting dari memiliki kode yang teruji dengan baik adalah dapat secara bebas melakukan refactor, dan bagian dari kemampuan untuk secara bebas melakukan refactor berarti Anda tidak perlu mengubah tes Anda untuk melakukannya. Semakin banyak Anda mengejek, semakin banyak Anda harus mengubah tes saat refactor. Jika Anda mempertimbangkan metode unit, maka Anda harus mengubah tes Anda setiap kali Anda refactor. Dan jika Anda menganggap kelas sebagai unit, maka Anda harus mengubah tes Anda setiap kali Anda ingin membagi kelas menjadi beberapa kelas. Ketika Anda harus memperbaiki tes Anda untuk memperbaiki kode Anda, itu membuat orang memilih untuk tidak memperbaiki kode mereka, yang merupakan hal terburuk yang dapat terjadi pada suatu proyek. Sangat penting bahwa Anda dapat membagi kelas menjadi beberapa kelas tanpa harus memperbaiki tes Anda, atau Anda akan berakhir dengan kelas spaghetti 500 baris yang terlalu besar. Jika Anda memperlakukan metode atau kelas sebagai unit Anda dengan pengujian unit, Anda mungkin tidak melakukan Pemrograman Berorientasi Objek tetapi semacam pemrograman fungsional mutan dengan objek.

Mengisolasi kode Anda untuk unit test tidak berarti Anda mengejek semua yang ada di luarnya. Jika ya, Anda harus mengejek kelas Matematika bahasa Anda, dan sama sekali tidak ada yang berpikir itu ide yang bagus. Ketergantungan internal tidak boleh diperlakukan secara berbeda dari dependensi eksternal. Anda percaya bahwa mereka diuji dengan baik dan bekerja seperti yang seharusnya. Satu-satunya perbedaan nyata adalah bahwa jika dependensi internal Anda melanggar modul Anda, Anda dapat menghentikan apa yang Anda lakukan untuk memperbaikinya daripada harus mengirim masalah pada GitHub dan menggali basis kode yang tidak Anda mengerti untuk memperbaikinya atau berharap yang terbaik.

Mengisolasi kode Anda hanya berarti Anda memperlakukan dependensi internal Anda seperti kotak hitam dan tidak menguji hal-hal yang terjadi di dalamnya. Jika Anda memiliki Modul B yang menerima input 1, 2, atau 3, dan Anda memiliki Modul A, yang menyebutnya, Anda tidak memiliki tes untuk Modul A melakukan setiap opsi tersebut, Anda hanya memilih satu dan menggunakannya. Ini berarti bahwa tes Anda untuk Modul A harus menguji berbagai cara Anda memperlakukan tanggapan dari Modul B, bukan hal-hal yang Anda sampaikan.

Jadi, jika controller Anda meneruskan objek kompleks ke dependensi, dan dependensi melakukan beberapa hal yang mungkin, mungkin menyimpannya ke database dan mungkin mengembalikan berbagai kesalahan, tetapi semua controller Anda sebenarnya hanya memeriksa untuk melihat apakah itu mengembalikan kesalahan atau tidak dan meneruskan informasi itu, maka semua yang Anda uji di controller Anda adalah satu tes untuk apakah itu mengembalikan kesalahan dan meneruskannya dan satu tes untuk jika itu tidak mengembalikan kesalahan. Anda tidak menguji apakah ada sesuatu yang disimpan dalam database atau jenis kesalahan apa kesalahan itu, karena itu akan menjadi tes integrasi. Anda tidak perlu mengejek ketergantungan untuk melakukan ini. Anda telah mengisolasi kodenya.

TiggerToo
sumber