Saya mulai menulis beberapa unit test untuk proyek saya saat ini. Saya tidak benar-benar memiliki pengalaman dengannya. Pertama saya ingin sepenuhnya "mengerti", jadi saya saat ini tidak menggunakan framework IoC saya atau perpustakaan yang mengejek.
Saya bertanya-tanya apakah ada yang salah dengan memberikan argumen nol untuk konstruktor objek dalam unit test. Biarkan saya memberikan beberapa contoh kode:
public class CarRadio
{...}
public class Motor
{
public void SetSpeed(float speed){...}
}
public class Car
{
public Car(CarRadio carRadio, Motor motor){...}
}
public class SpeedLimit
{
public bool IsViolatedBy(Car car){...}
}
Namun Another Car Code Example (TM), berkurang menjadi hanya bagian-bagian yang penting untuk pertanyaan. Saya sekarang menulis tes seperti ini:
public class SpeedLimitTest
{
public void TestSpeedLimit()
{
Motor motor = new Motor();
motor.SetSpeed(10f);
Car car = new Car(null, motor);
SpeedLimit speedLimit = new SpeedLimit();
Assert.IsTrue(speedLimit.IsViolatedBy(car));
}
}
Tes berjalan dengan baik. SpeedLimit
perlu Car
dengan Motor
untuk melakukan hal itu. Itu tidak tertarik CarRadio
sama sekali, jadi saya berikan nol untuk itu.
Saya bertanya-tanya apakah suatu objek menyediakan fungsionalitas yang benar tanpa sepenuhnya dibangun adalah pelanggaran SRP atau bau kode. Saya memiliki perasaan yang mengganggu, tetapi speedLimit.IsViolatedBy(motor)
tidak terasa benar - batas kecepatan dilanggar oleh mobil, bukan motor. Mungkin saya hanya perlu perspektif berbeda untuk unit test vs kode kerja, karena seluruh niatnya adalah untuk menguji hanya sebagian saja.
Apakah membangun objek dengan null dalam tes unit bau kode?
sumber
Motor
mungkin seharusnya tidak memilikispeed
sama sekali. Seharusnyathrottle
dan menghitungtorque
berdasarkan arusrpm
danthrottle
. Adalah tugas mobil untuk menggunakanTransmission
untuk mengintegrasikan itu ke kecepatan saat ini, dan untuk mengubahnya menjadirpm
sinyal untuk kembali keMotor
... Tapi saya kira, Anda toh tidak ada di dalamnya untuk realisme, kan?null
radio, batas kecepatan dihitung dengan benar. Sekarang Anda mungkin ingin membuat tes untuk memvalidasi batas kecepatan dengan radio; kalau-kalau perilakunya berbeda ...Jawaban:
Dalam kasus contoh di atas, masuk akal bahwa a
Car
dapat ada tanpa aCarRadio
. Dalam hal ini, saya akan mengatakan bahwa tidak hanya dapat diterima dengan nolCarRadio
kadang-kadang, saya akan mengatakan bahwa itu wajib. Tes Anda perlu memastikan bahwa implementasiCar
kelas kuat, dan tidak membuang pengecualian null pointer ketika tidakCarRadio
ada.Namun, mari kita ambil contoh berbeda - mari kita pertimbangkan a
SteeringWheel
. Mari kita asumsikan bahwaCar
harus memilikiSteeringWheel
, tetapi tes kecepatan tidak benar-benar peduli. Dalam hal ini, saya tidak akan memberikan nolSteeringWheel
karena ini kemudian mendorongCar
kelas ke tempat-tempat di mana ia tidak dirancang untuk pergi. Dalam hal ini, Anda akan lebih baik membuat semacamDefaultSteeringWheel
yang (untuk melanjutkan metafora) dikunci dalam garis lurus.sumber
Car
tidak dapat dibangun tanpaSteeringWheel
...Car
tanpaCarRadio
, tidak masuk akal untuk membuat konstruktor yang tidak memerlukannya untuk dilewati dan tidak perlu khawatir tentang null eksplisit sama sekali ?Car
akan melempar NRE dengan nolCarRadio
, tetapi kode yang relevan untukSpeedLimit
tidak. Dalam hal pertanyaan seperti yang diposting sekarang Anda akan benar dan saya memang akan melakukannya.Iya nih. Perhatikan definisi kode bau C2 : "CodeSmell adalah petunjuk bahwa ada sesuatu yang salah, bukan kepastian."
Jika Anda mencoba menunjukkan bahwa
speedLimit.IsViolatedBy(car)
tidak bergantung padaCarRadio
argumen yang diteruskan ke konstruktor, maka mungkin akan lebih jelas bagi pengelola di masa mendatang untuk membuat batasan itu secara eksplisit dengan memperkenalkan uji ganda yang gagal dalam pengujian jika itu dipanggil.Jika, seperti lebih mungkin, Anda hanya berusaha menyelamatkan diri dari pekerjaan membuat
CarRadio
yang Anda tahu tidak digunakan dalam kasus ini, Anda harus memperhatikan bahwaCarRadio
tidak digunakan bocor ke dalam ujian)Car
tanpa menentukan aCarRadio
Saya sangat curiga bahwa kode tersebut mencoba memberi tahu Anda bahwa Anda menginginkan konstruktor yang mirip
dan kemudian konstruktor itu dapat memutuskan apa yang harus dilakukan dengan fakta bahwa tidak ada
CarRadio
atau
atau
dll.
Itu bau yang menarik kedua - untuk menguji SpeedLimit, Anda harus membangun seluruh mobil di sekitar radio, meskipun satu-satunya hal yang mungkin diperhatikan oleh pemeriksaan SpeedLimit adalah kecepatan .
Ini mungkin petunjuk yang
SpeedLimit.isViolatedBy
harus menerima antarmuka peran yang diimplementasikan oleh mobil , daripada membutuhkan seluruh mobil.IHaveSpeed
adalah nama yang sangat buruk; semoga bahasa domain Anda akan mengalami peningkatan.Dengan antarmuka itu, pengujian Anda
SpeedLimit
bisa menjadi lebih sederhanasumber
IHaveSpeed
antarmuka sebagai (setidaknya dalam c #) Anda tidak akan dapat membuat instance antarmuka itu sendiri.Tidak
Tidak semuanya. Sebaliknya, jika
NULL
nilai yang tersedia sebagai argumen (beberapa bahasa mungkin memiliki konstruksi yang melarang lewat dari pointer nol), Anda harus mengujinya.Pertanyaan lain adalah apa yang terjadi ketika Anda lulus
NULL
. Tapi itulah yang dimaksud dengan unit test: memastikan hasil yang pasti terjadi ketika untuk setiap input yang memungkinkan. DanNULL
merupakan input potensial, jadi Anda harus memiliki hasil yang ditentukan dan Anda harus memiliki tes untuk itu.Jadi jika Mobil Anda seharusnya dapat dibangun tanpa radio, Anda harus menulis tes untuk itu. Jika tidak dan seharusnya melempar pengecualian, Anda harus menulis tes untuk itu.
Either way, ya Anda harus menulis ujian untuk
NULL
. Itu akan menjadi bau kode jika Anda meninggalkannya. Itu berarti Anda memiliki cabang kode yang belum diuji yang baru saja Anda anggap ajaib bebas bug.Harap dicatat bahwa saya setuju dengan jawaban lain bahwa kode Anda yang sedang diuji dapat ditingkatkan. Meskipun demikian, jika kode yang ditingkatkan memiliki parameter yang pointer, melewati
NULL
bahkan untuk kode yang ditingkatkan adalah tes yang baik. Saya ingin tes untuk menunjukkan apa yang terjadi ketika saya lulusNULL
sebagai motor. Itu bukan bau kode, itu kebalikan dari bau kode. Ini pengujian.sumber
Car
sini. Meskipun saya tidak melihat apa pun yang saya anggap sebagai pernyataan yang salah, pertanyaannya adalah tentang pengujianSpeedLimit
.NULL
ke konstruktorCar
.SpeedLimit
. Anda dapat melihat bahwa tes ini disebut "SpeedLimitTest" dan hanyaAssert
memeriksa metodeSpeedLimit
. PengujianCar
- misalnya "jika Mobil Anda seharusnya dapat dibangun tanpa radio, Anda harus menulis tes untuk itu" - akan terjadi dalam tes yang berbeda.Saya pribadi akan ragu untuk menyuntikkan nol. Bahkan, setiap kali saya menyuntikkan dependensi ke dalam konstruktor, salah satu hal pertama yang saya lakukan adalah meningkatkan ArgumentNullException jika suntikan itu nol. Dengan begitu saya bisa menggunakannya dengan aman di dalam kelas saya, tanpa puluhan cek kosong yang tersebar. Inilah pola yang sangat umum. Perhatikan penggunaan readonly untuk memastikan dependensi yang diatur dalam konstruktor tidak dapat disetel ke nol (atau apa pun) setelahnya:
Dengan yang di atas, saya mungkin bisa memiliki satu atau dua unit test, di mana saya menyuntikkan nol, hanya untuk memastikan cek nol saya berfungsi seperti yang diharapkan.
Karena itu, ini menjadi masalah, setelah Anda melakukan dua hal:
Jadi untuk contoh Anda, Anda akan memiliki:
Rincian lebih lanjut tentang penggunaan Moq dapat ditemukan di sini .
Tentu saja, jika Anda ingin memahaminya tanpa mengejek terlebih dahulu, Anda masih bisa melakukannya, selama Anda menggunakan antarmuka. Cukup buat CarRadio dan Motor dummy sebagai berikut, dan suntikkan sebagai gantinya:
sumber
IMotor
punyagetSpeed()
metode, makaDummyMotor
perlu mengembalikan kecepatan yang dimodifikasi setelah berhasilsetSpeed
juga.Ini tidak hanya baik-baik saja, ini adalah teknik pemecahan ketergantungan yang terkenal untuk memasukkan kode lama ke dalam alat uji. (Lihat “Bekerja Secara Efektif dengan Kode Warisan” oleh Michael Feathers.)
Apakah baunya? Baik...
The tes tidak berbau. Tes sedang melakukan tugasnya. Itu menyatakan "objek ini bisa nol dan kode yang diuji masih berfungsi dengan benar".
Bau itu ada dalam implementasinya. Dengan mendeklarasikan argumen konstruktor, Anda menyatakan "kelas ini membutuhkan benda ini agar berfungsi dengan baik". Jelas, itu tidak benar. Apa pun yang tidak benar sedikit berbau.
sumber
Mari kita mundur ke prinsip pertama.
Namun akan ada konstruktor atau metode yang mengambil kelas lain sebagai parameter. Cara Anda menangani ini akan bervariasi dari kasus ke kasus tetapi pengaturan harus minimal karena mereka tangensial ke tujuan. Mungkin ada skenario di mana melewati parameter NULL benar-benar valid - seperti mobil yang tidak memiliki radio.
Saya berasumsi di sini, bahwa kami hanya menguji garis balap untuk memulai (untuk melanjutkan tema mobil), tetapi Anda mungkin juga ingin meneruskan NULL ke parameter lain untuk memeriksa apakah kode pertahanan dan mekanisme penyerahan pengecualian berfungsi sebagai wajib.
Sejauh ini baik. Namun, bagaimana jika NULL bukan nilai yang valid. Nah, di sini Anda berada di dunia tiruan, bertopik, boneka dan palsu .
Jika semua ini tampaknya agak sulit, Anda sebenarnya sudah menggunakan dummy dengan memasukkan NULL di konstruktor Mobil karena nilai yang berpotensi tidak akan digunakan.
Apa yang seharusnya menjadi jelas dengan cepat, adalah bahwa Anda berpotensi menembak kode Anda dengan banyak penanganan NULL. Jika Anda mengganti parameter kelas dengan antarmuka, maka Anda juga membuka jalan untuk mengejek dan DI dll yang membuatnya jauh lebih mudah untuk ditangani.
sumber
Melihat desain Anda, saya akan menyarankan bahwa a
Car
terdiri dari satu setFeatures
. Fitur mungkin aCarRadio
, atauEntertainmentSystem
yang dapat terdiri dari hal-hal sepertiCarRadio
,DvdPlayer
dll.Jadi, minimal, Anda harus membangun
Car
dengan satu setFeatures
.EntertainmentSystem
danCarRadio
akan menjadi pelaksana antarmuka (Java, C # dll) daripublic [I|i]nterface Feature
dan ini akan menjadi contoh dari pola desain Komposit.Oleh karena itu, Anda akan selalu membuat
Car
denganFeatures
dan unit test tidak akan pernah membuatCar
tanpaFeatures
. Meskipun Anda mungkin mempertimbangkan untuk mengejek seorangBasicTestCarFeatureSet
yang memiliki serangkaian fungsi minimal yang diperlukan untuk pengujian paling dasar Anda.Hanya beberapa pemikiran.
John
sumber