Bagaimana tes TDD granular seharusnya?

18

Selama pelatihan TDD berdasarkan kasus perangkat lunak medis, kami menerapkan kisah berikut: "Ketika pengguna menekan tombol Simpan, sistem harus menambah pasien, menambah perangkat, dan menambahkan catatan data perangkat".

Implementasi akhir akan terlihat seperti ini:

if (_importDialog.Show() == ImportDialogResult.SaveButtonIsPressed)
{
   AddPatient();
   AddDevice();
   AddDeviceDataRecords();
}

Kami memiliki dua cara untuk mengimplementasikannya:

  1. Tiga tes di mana masing-masing memverifikasi satu metode (AddPatient, AddDevice, AddDeviceDataRecords) dipanggil
  2. Satu tes yang memverifikasi ketiga metode dipanggil

Dalam kasus pertama jika ada sesuatu yang salah terjadi jika kondisi klausa, ketiga tes akan gagal. Tetapi dalam kasus kedua jika tes gagal, kami tidak yakin apa yang sebenarnya salah. Apa yang Anda inginkan?

SiberianGuy
sumber

Jawaban:

8

Tetapi dalam kasus kedua jika tes gagal, kami tidak yakin apa yang sebenarnya salah.

Saya pikir itu akan sangat tergantung pada seberapa baik pesan kesalahan yang dihasilkan tes. Secara umum, ada berbagai cara untuk memverifikasi bahwa suatu metode telah dipanggil; misalnya jika Anda menggunakan objek tiruan, itu akan memberi Anda pesan kesalahan yang menjelaskan metode yang diharapkan tidak dipanggil selama pengujian. Jika Anda memverifikasi bahwa metode itu dipanggil melalui merasakan efek panggilan, terserah Anda untuk menghasilkan pesan kesalahan deskriptif.

Dalam praktiknya, pilihan antara opsi 1 dan 2 juga tergantung pada situasi. Jika saya melihat kode yang Anda tunjukkan di atas dalam proyek lawas, saya memilih pendekatan pragmatis Kasus # 2 hanya untuk memverifikasi bahwa masing-masing dari 3 metode dipanggil dengan benar ketika kondisi terpenuhi. Jika saya mengembangkan potongan kode ini sekarang, 3 panggilan metode kemungkinan besar akan ditambahkan satu per satu, pada titik waktu yang berbeda (mungkin berhari-hari atau berbulan-bulan dari satu sama lain), jadi saya akan menambahkan tes unit baru yang terpisah untuk memverifikasi setiap panggilan.

Perhatikan juga, dengan cara apa pun itu, Anda juga harus memiliki unit test terpisah untuk memverifikasi bahwa masing-masing metode individual melakukan apa yang seharusnya dilakukan.

Péter Török
sumber
Tidakkah menurut Anda masuk akal untuk menggabungkan ketiga tes itu menjadi satu?
SiberianGuy
@Idsa, bisa menjadi keputusan yang masuk akal, meskipun dalam praktiknya saya jarang repot dengan jenis refactoring ini. Kemudian lagi, saya bekerja dengan kode lama, di mana prioritasnya berbeda: kami fokus pada peningkatan cakupan tes kode yang ada, dan menjaga jumlah unit test yang terus bertambah.
Péter Török
30

Granularitas dalam contoh Anda tampaknya merupakan perbedaan antara tes unit dan penerimaan.

Unittest menguji unit fungsionalitas tunggal, dengan sesedikit mungkin dependensi. Dalam kasus Anda, mungkin ada 4 unittests

  • apakah AddPatient menambah pasien (mis. memanggil fungsi basis data yang relevan)?
  • Apakah AddDevice menambahkan perangkat?
  • apakah AddDeviceDataRecords menambahkan catatan?
  • apakah fungsi utama tidak diubah dalam contoh panggilan Anda AddPatient, AddDevice dan AddDeviceFunctions

Unittests adalah untuk pengembang , sehingga mereka mendapatkan kepercayaan, bahwa kode mereka secara teknis benar

Tes penerimaan harus menguji fungsi gabungan, dari perspektif pengguna. Mereka harus dimodelkan di sepanjang cerita pengguna, dan setinggi mungkin. Jadi, Anda tidak perlu memeriksa apakah fungsi dipanggil, tetapi jika manfaat yang terlihat oleh pengguna tercapai:

ketika pengguna memasukkan data, klik ok dan ...

  • ... masuk ke daftar pasien, dia harus melihat pasien baru dengan nama yang diberikan
  • ... masuk ke daftar perangkat, ia akan melihat perangkat baru
  • ... pergi ke detail perangkat baru, ia akan melihat datarecords baru

tes penerimaan adalah untuk pelanggan , atau, untuk membangun komunikasi yang lebih baik dengan mereka.

Untuk menjawab pertanyaan Anda "apa yang Anda inginkan": apa masalah yang lebih besar bagi Anda saat ini, bug dan regresi (=> lebih banyak yang tidak diundang) atau memahami dan memformalkan gambaran besar (=> lebih banyak tes penerimaan)

keppla
sumber
13

Kami memiliki dua cara untuk mengimplementasikannya:

Itu salah.

Tiga tes di mana masing-masing memverifikasi satu metode (AddPatient, AddDevice, AddDeviceDataRecords) dipanggil

Anda harus melakukan ini untuk memastikan itu berfungsi.

Satu tes yang memverifikasi ketiga metode dipanggil

Anda juga harus melakukan ini untuk memastikan API berfungsi.

Kelas - sebagai satu unit - harus diuji sepenuhnya. Setiap metode.

Anda bisa mulai dengan tes yang mencakup ketiga metode, tetapi tidak banyak memberi tahu Anda.

jika tes gagal, kami tidak yakin apa yang sebenarnya salah.

Benar. Itu sebabnya Anda menguji semua metode.

Anda harus menguji antarmuka publik. Karena kelas ini melakukan tiga ditambah satu hal (bahkan jika mereka dibundel dalam satu metode karena cerita pengguna) Anda harus menguji keempat hal tersebut. Tiga tingkat rendah dan satu bundel.

S.Lott
sumber
2

Kami menulis pengujian unit kami untuk kalimat fungsional yang bermakna yang berkali-kali memetakan suatu metode (jika Anda telah menulis kode dengan baik), tetapi kadang-kadang menjadi lebih besar, mencakup banyak metode.

Misalnya, bayangkan menambahkan pasien ke sistem Anda memerlukan beberapa subrutin (fungsi anak) untuk dipanggil:

  1. VerifikasiPatientKualifikasi
  2. EnsureDoctorExistence
  3. Periksa Asuransi Sejarah
  4. EnsureEmptyBed

Kami mungkin juga menulis unit test untuk masing-masing fungsi ini.

Saeed Neamati
sumber
2

Salah satu aturan praktis sederhana yang saya ikuti adalah memberi nama tes sehingga menjelaskan dengan tepat apa yang dilakukan tes. Jika nama tes menjadi terlalu kompleks itu adalah tanda bahwa tes itu mungkin terlalu banyak. Jadi misalnya penamaan tes untuk melakukan apa yang Anda usulkan dalam opsi 2 mungkin terlihat seperti PatientIsAddedDeviceIsAddedAndDeviceDataRecordsWhenSaved yang jauh lebih kompleks dari tiga tes terpisah PatiIAddedWhenSaved, DeviceIsAddedWhenSaved, DataRecordsWhenSaved. Saya juga berpikir pelajaran yang dapat dipelajari dari BDD cukup menarik di mana setiap tes benar-benar mewakili satu persyaratan yang dapat dijelaskan dalam bahasa alami.

jpierson
sumber