Saya menulis komponen yang, mengingat file ZIP, perlu:
- Buka zip file.
- Temukan dll tertentu di antara file yang tidak di-zip.
- Muat dll melalui refleksi dan memanggil metode di atasnya.
Saya ingin menguji unit komponen ini.
Saya tergoda untuk menulis kode yang berhubungan langsung dengan sistem file:
void DoIt()
{
Zip.Unzip(theZipFile, "C:\\foo\\Unzipped");
System.IO.File myDll = File.Open("C:\\foo\\Unzipped\\SuperSecret.bar");
myDll.InvokeSomeSpecialMethod();
}
Tetapi orang-orang sering berkata, "Jangan menulis unit test yang bergantung pada sistem file, database, jaringan, dll."
Jika saya menulis ini dengan cara uji unit-friendly, saya kira itu akan terlihat seperti ini:
void DoIt(IZipper zipper, IFileSystem fileSystem, IDllRunner runner)
{
string path = zipper.Unzip(theZipFile);
IFakeFile file = fileSystem.Open(path);
runner.Run(file);
}
Yay! Sekarang bisa diuji; Saya bisa memberi makan di test dobel (mengolok-olok) ke metode DoIt. Tetapi berapa biayanya? Saya sekarang harus mendefinisikan 3 antarmuka baru hanya untuk membuat ini dapat diuji. Dan apa, tepatnya, yang saya uji? Saya sedang menguji bahwa fungsi Doit saya berinteraksi dengan baik dengan dependensinya. Itu tidak menguji bahwa file zip tidak di-zip dengan benar, dll.
Saya tidak merasa sedang menguji fungsionalitas lagi. Rasanya seperti saya hanya menguji interaksi kelas.
Pertanyaan saya adalah ini : apa cara yang tepat untuk menguji unit sesuatu yang tergantung pada sistem file?
sunting Saya menggunakan .NET, tetapi konsepnya dapat menerapkan kode Java atau asli juga.
sumber
myDll.InvokeSomeSpecialMethod();
mana Anda akan memeriksa apakah itu berfungsi dengan baik dalam situasi sukses dan gagal sehingga saya tidak akan menguji unitDoIt
tetapiDllRunner.Run
mengatakan bahwa menyalahgunakan tes UNIT untuk memeriksa ulang bahwa seluruh proses bekerja akan menjadi penyalahgunaan yang dapat diterima dan karena itu akan menjadi tes integrasi yang menyamarkan uji unit, aturan uji unit normal tidak perlu diterapkan secara ketatJawaban:
Benar-benar tidak ada yang salah dengan ini, hanya pertanyaan apakah Anda menyebutnya uji unit atau tes integrasi. Anda hanya perlu memastikan bahwa jika Anda berinteraksi dengan sistem file, tidak ada efek samping yang tidak diinginkan. Secara khusus, pastikan bahwa Anda membersihkan diri sendiri - hapus semua file sementara yang Anda buat - dan bahwa Anda tidak sengaja menimpa file yang ada yang kebetulan memiliki nama file yang sama dengan file sementara yang Anda gunakan. Selalu gunakan jalur relatif dan bukan jalur absolut.
Ini juga merupakan ide bagus untuk
chdir()
menjadi direktori sementara sebelum menjalankan tes Anda, danchdir()
kembali sesudahnya.sumber
chdir()
prosesnya luas sehingga Anda dapat mematahkan kemampuan untuk menjalankan tes Anda secara paralel, jika kerangka uji Anda atau versi yang akan datang mendukungnya.Anda telah memukul paku tepat di kepalanya. Apa yang ingin Anda uji adalah logika metode Anda, belum tentu apakah file yang sebenarnya dapat ditangani. Anda tidak perlu menguji (dalam pengujian unit ini) apakah sebuah file tidak di-zip dengan benar, metode Anda menerima begitu saja. Antarmuka berharga dengan sendirinya karena mereka memberikan abstraksi yang dapat Anda program melawan, daripada secara implisit atau eksplisit bergantung pada satu implementasi konkret.
sumber
DoIt
seperti yang dinyatakan bahkan tidak perlu pengujian. Seperti yang ditunjukkan oleh si penanya dengan benar, tidak ada yang penting untuk diuji. Sekarang ini adalah implementasi dariIZipper
,,IFileSystem
danIDllRunner
itu perlu pengujian, tetapi mereka adalah hal-hal yang telah dipermainkan untuk pengujian!Pertanyaan Anda memaparkan salah satu bagian paling sulit dari pengujian untuk pengembang yang baru saja masuk ke dalamnya:
"Apa yang harus aku uji?"
Contoh Anda tidak terlalu menarik karena itu hanya menempelkan beberapa panggilan API bersama-sama jadi jika Anda menulis tes unit untuk itu Anda akan berakhir hanya dengan menyatakan bahwa metode dipanggil. Tes seperti ini dengan ketat memasangkan detail implementasi Anda dengan tes. Ini buruk karena sekarang Anda harus mengubah tes setiap kali Anda mengubah detail implementasi metode Anda karena mengubah detail implementasi akan merusak tes Anda!
Memiliki tes buruk sebenarnya lebih buruk daripada tidak memiliki tes sama sekali.
Dalam contoh Anda:
Meskipun Anda dapat mengolok-olok, tidak ada logika dalam metode untuk menguji. Jika Anda mencoba tes unit untuk ini mungkin terlihat seperti ini:
Selamat, pada dasarnya Anda menyalin-menempelkan detail implementasi
DoIt()
metode Anda ke dalam tes. Selamat memelihara.Ketika Anda menulis tes, Anda ingin menguji APA dan bukan BAGAIMANA . Lihat Pengujian Kotak Hitam untuk lebih lanjut.
The APA adalah nama dari metode Anda (atau setidaknya seharusnya). The CARA semua rincian pelaksanaan kecil yang hidup di dalam metode Anda. Tes yang baik memungkinkan Anda untuk menukar BAGAIMANA tanpa melanggar APA .
Pikirkan seperti ini, tanyakan pada diri sendiri:
"Jika saya mengubah detail implementasi metode ini (tanpa mengubah kontrak publik) akankah hal itu melanggar pengujian saya?"
Jika jawabannya adalah ya, Anda menguji BAGAIMANA dan bukan APA .
Untuk menjawab pertanyaan spesifik Anda tentang pengujian kode dengan dependensi sistem file, katakanlah Anda memiliki sesuatu yang sedikit lebih menarik terjadi pada sebuah file dan Anda ingin menyimpan konten yang disandikan Base64
byte[]
ke file. Anda dapat menggunakan stream untuk ini untuk menguji bahwa kode Anda melakukan hal yang benar tanpa harus memeriksa bagaimana melakukannya. Salah satu contoh mungkin seperti ini (di Jawa):Tes menggunakan
ByteArrayOutputStream
tetapi dalam aplikasi (menggunakan injeksi ketergantungan) StreamFactory nyata (mungkin disebut FileStreamFactory) akan kembaliFileOutputStream
darioutputStream()
dan akan menulis ke aFile
.Apa yang menarik tentang
write
metode di sini adalah bahwa ia menulis konten keluar dari Base64 yang dikodekan, jadi itulah yang kami uji. UntukDoIt()
metode Anda , ini akan lebih tepat diuji dengan tes integrasi .sumber
Saya enggan mencemari kode saya dengan jenis dan konsep yang hanya ada untuk memfasilitasi pengujian unit. Tentu, jika itu membuat desain lebih bersih dan lebih baik maka bagus, tapi saya pikir itu sering tidak terjadi.
Menurut saya ini adalah bahwa unit test Anda akan melakukan sebanyak mungkin yang mungkin tidak 100% cakupan. Bahkan, mungkin hanya 10%. Intinya adalah, tes unit Anda harus cepat dan tidak memiliki dependensi eksternal. Mereka mungkin menguji kasus seperti "metode ini melempar ArgumentNullException ketika Anda memasukkan nol untuk parameter ini".
Saya kemudian akan menambahkan tes integrasi (juga otomatis dan mungkin menggunakan kerangka pengujian unit yang sama) yang dapat memiliki dependensi eksternal dan menguji skenario end-to-end seperti ini.
Saat mengukur cakupan kode, saya mengukur tes unit dan integrasi.
sumber
Tidak ada yang salah dengan memukul sistem file, anggap saja ini sebagai tes integrasi daripada tes unit. Saya akan menukar jalur kode keras dengan jalur relatif dan membuat subfolder TestData untuk berisi ritsleting untuk tes unit.
Jika tes integrasi Anda membutuhkan waktu terlalu lama untuk dijalankan, pisahkan mereka sehingga tes tersebut tidak berjalan sesering tes cepat unit Anda.
Saya setuju, kadang-kadang saya berpikir pengujian berbasis interaksi dapat menyebabkan kopling terlalu banyak dan seringkali berakhir dengan tidak memberikan nilai yang cukup. Anda benar-benar ingin menguji unzipping file di sini bukan hanya memverifikasi Anda memanggil metode yang tepat.
sumber
Salah satu caranya adalah dengan menulis metode unzip untuk mengambil InputStreams. Kemudian unit test dapat membangun InputStream dari array byte menggunakan ByteArrayInputStream. Isi array byte itu bisa berupa konstanta dalam kode uji unit.
sumber
Ini tampaknya lebih merupakan tes integrasi karena Anda bergantung pada detail spesifik (sistem file) yang dapat berubah, secara teori.
Saya akan abstrak kode yang berhubungan dengan OS ke dalam modul itu sendiri (kelas, perakitan, toples, apa pun). Dalam kasus Anda, Anda ingin memuat DLL tertentu jika ditemukan, jadi buatlah antarmuka IDllLoader dan kelas DllLoader. Sudahkah aplikasi Anda memperoleh DLL dari DllLoader menggunakan antarmuka dan mengujinya .. Anda tidak bertanggung jawab atas kode unzip juga?
sumber
Dengan asumsi bahwa "interaksi sistem file" diuji dengan baik dalam kerangka itu sendiri, buat metode Anda untuk bekerja dengan stream, dan uji ini. Membuka FileStream dan meneruskannya ke metode dapat ditinggalkan dari tes Anda, karena FileStream.Open diuji dengan baik oleh pembuat kerangka.
sumber
Anda seharusnya tidak menguji interaksi kelas dan pemanggilan fungsi. alih-alih Anda harus mempertimbangkan pengujian integrasi. Uji hasil yang diperlukan dan bukan operasi pemuatan file.
sumber
Untuk pengujian unit, saya sarankan Anda memasukkan file tes dalam proyek Anda (file EAR atau yang setara) kemudian gunakan jalur relatif dalam tes unit yaitu "../testdata/testfile".
Selama proyek Anda diekspor / diimpor dengan benar, maka tes unit Anda akan berfungsi.
sumber
Seperti yang orang lain katakan, yang pertama baik-baik saja sebagai tes integrasi. Tes kedua hanya melakukan fungsi yang seharusnya dilakukan, yang harus dilakukan oleh semua unit test.
Seperti yang ditunjukkan, contoh kedua terlihat sedikit tidak berguna, tetapi itu memberi Anda kesempatan untuk menguji bagaimana fungsi merespons kesalahan dalam salah satu langkah. Anda tidak memiliki kesalahan memeriksa dalam contoh, tetapi dalam sistem nyata yang Anda miliki, dan injeksi dependensi akan membiarkan Anda menguji semua respons terhadap kesalahan. Maka biayanya akan sepadan.
sumber