Cara unit metode pengujian yang mengembalikan koleksi sambil menghindari logika dalam pengujian

14

Saya menguji-mengemudi metode yang menghasilkan kumpulan objek data. Saya ingin memverifikasi bahwa properti objek sedang diatur dengan benar. Beberapa properti akan diatur ke hal yang sama; yang lain akan ditetapkan ke nilai yang tergantung pada posisi mereka dalam koleksi. Cara alami untuk melakukan ini tampaknya dengan loop. Namun, Roy Osherove sangat menyarankan untuk tidak menggunakan logika dalam unit test ( Art of Unit Testing , 178). Dia berkata:

Tes yang berisi logika biasanya menguji lebih dari satu hal pada suatu waktu, yang tidak disarankan, karena tes tersebut kurang mudah dibaca dan lebih rapuh. Tetapi test logic juga menambah kompleksitas yang mungkin mengandung bug tersembunyi.

Pengujian harus, sebagai aturan umum, menjadi serangkaian panggilan metode tanpa aliran kontrol, bahkan try-catch, dan dengan panggilan tegas.

Namun, saya tidak dapat melihat ada yang salah dengan desain saya (bagaimana lagi Anda menghasilkan daftar objek data, beberapa yang nilainya bergantung pada di mana dalam urutannya? —Tidak dapat menghasilkan dan mengujinya secara terpisah). Apakah ada sesuatu yang tidak ramah uji dengan desain saya? Atau apakah saya terlalu setia pada pengajaran Osherove? Atau adakah sihir tes unit rahasia yang saya tidak tahu tentang hal itu menghindari masalah ini? (Saya menulis dalam bahasa C # / VS2010 / NUnit, tetapi mencari jawaban agnostik bahasa jika memungkinkan.)

Kazark
sumber
4
Saya sarankan untuk tidak mengulang. Jika tes Anda adalah bahwa hal ketiga memiliki Bar ditetapkan ke Frob, kemudian tulis tes untuk secara khusus memeriksa bahwa Bar hal ketiga adalah Frob. Itu satu tes dengan sendirinya, langsung saja, tidak ada loop. Jika tes Anda adalah bahwa Anda mendapatkan koleksi 5 hal, itu juga satu tes. Itu tidak berarti Anda tidak pernah memiliki loop (eksplisit atau sebaliknya), hanya saja Anda tidak sering perlu melakukannya. Juga, perlakukan buku Osherove sebagai pedoman lebih dari aturan yang sebenarnya.
Anthony Pegram
1
@AnthonyPegram Sets tidak berurutan - Frob mungkin terkadang ke-3, kadang-kadang ke-2. Anda tidak dapat mengandalkannya, membuat perulangan (atau fitur bahasa seperti Python in) diperlukan, jika pengujiannya "Frob berhasil ditambahkan ke koleksi yang ada".
Izkata
1
@Izbata, pertanyaannya secara khusus menyebutkan bahwa pemesanan itu penting. Kata-katanya: "orang lain akan ditetapkan ke nilai yang tergantung pada posisi mereka dalam koleksi." Ada banyak jenis koleksi dalam C # (bahasa yang dia referensikan) yang dipesankan sisipan. Untuk itu, Anda juga bisa mengandalkan pesanan dengan daftar di Python, bahasa yang Anda sebutkan.
Anthony Pegram
Juga, katakanlah Anda sedang menguji metode Reset pada koleksi. Anda perlu mengulang koleksi dan memeriksa setiap item. Bergantung pada ukuran koleksi, tidak mengujinya dalam satu lingkaran adalah konyol. Atau katakanlah saya sedang menguji sesuatu yang seharusnya menambah setiap item dalam koleksi. Anda dapat mengatur semua item dengan nilai yang sama, hubungi kenaikan Anda, lalu periksa. Tes itu payah. Anda harus mengatur beberapa dari mereka ke nilai yang berbeda, meningkatkan panggilan, dan memeriksa bahwa semua nilai yang berbeda bertambah dengan benar. Memeriksa hanya satu item acak dalam koleksi meninggalkan banyak peluang.
iheanyi
Saya tidak akan menjawab seperti ini karena saya akan mendapatkan trilyun downvotes, tapi saya sering hanya toString()Koleksi dan membandingkan dengan apa yang seharusnya. Sederhana dan berhasil.
user949300

Jawaban:

16

TL; DR:

  • Tulis tesnya
  • Jika tesnya terlalu banyak, kodenya juga bisa terlalu banyak.
  • Ini mungkin bukan tes unit (tapi bukan tes buruk).

Hal pertama untuk pengujian adalah tentang dogma yang tidak membantu. Saya menikmati membaca The Way of Testivus yang menunjukkan beberapa masalah dengan dogma dengan cara yang ringan.

Tulis tes yang perlu ditulis.

Jika tes perlu ditulis dengan cara tertentu, tulis seperti itu. Mencoba memaksa tes ke beberapa tata letak tes ideal atau tidak memilikinya sama sekali bukan hal yang baik. Memiliki tes hari ini yang menguji itu lebih baik daripada memiliki tes "sempurna" beberapa hari kemudian.

Saya juga akan menunjukkan bit pada tes jelek:

Ketika kode jelek, tes mungkin jelek.

Anda tidak suka menulis tes yang jelek, tetapi kode jelek paling membutuhkan pengujian.

Jangan biarkan kode jelek menghentikan Anda dari menulis tes, tetapi biarkan kode jelek menghentikan Anda dari menulis lebih banyak.

Ini dapat dianggap disangkal bagi mereka yang telah mengikuti untuk waktu yang lama ... dan mereka menjadi tertanam dalam cara berpikir dan menulis tes. Bagi orang-orang yang belum dan sedang berusaha untuk sampai ke titik itu, pengingat dapat membantu (saya bahkan menemukan membaca ulang mereka membantu saya menghindari terkunci ke dalam dogma).


Pertimbangkan bahwa ketika menulis tes jelek, jika kode itu mungkin merupakan indikasi bahwa kode tersebut mencoba melakukan terlalu banyak. Jika kode yang Anda uji terlalu rumit untuk dapat dilakukan dengan baik dengan menulis tes sederhana, Anda mungkin ingin mempertimbangkan untuk memecah kode menjadi bagian-bagian yang lebih kecil yang dapat diuji dengan tes yang lebih sederhana. Seseorang seharusnya tidak menulis tes unit yang melakukan segalanya (itu mungkin bukan tes unit ). Sama seperti 'objek dewa' buruk, 'tes unit dewa' juga buruk dan harus menjadi indikasi untuk kembali dan melihat kode lagi.

Anda harus dapat menggunakan semua kode dengan cakupan yang masuk akal melalui tes sederhana tersebut. Tes yang melakukan lebih banyak ujung ke ujung pengujian yang berhubungan dengan pertanyaan yang lebih besar ("Saya memiliki objek ini, disusun ke xml, dikirim ke layanan web, melalui aturan, mundur, dan tidak tertandai") adalah tes yang sangat baik - tetapi tentu saja tidak adalah unit test (dan jatuh ke ranah pengujian integrasi - bahkan jika ia telah mengolok-olok layanan yang ia panggil dan kustom dalam database memori untuk melakukan pengujian). Mungkin masih menggunakan kerangka kerja XUnit untuk pengujian, tetapi kerangka kerja pengujian tidak membuatnya menjadi unit test.


sumber
7

Saya menambahkan jawaban baru karena sudut pandang saya berbeda dari ketika saya menulis pertanyaan dan jawaban yang asli; tidak masuk akal untuk menyatukan mereka menjadi satu.

Saya katakan dalam pertanyaan awal

Namun, saya tidak dapat melihat ada yang salah dengan desain saya (bagaimana lagi Anda menghasilkan daftar objek data, beberapa yang nilainya bergantung pada di mana dalam urutannya? —Tidak bisa menghasilkan dan mengujinya secara terpisah)

Di sinilah saya salah. Setelah melakukan pemrograman fungsional untuk tahun lalu, saya sekarang menyadari bahwa saya hanya perlu operasi pengumpulan dengan akumulator. Kemudian saya bisa menulis fungsi saya sebagai fungsi murni yang beroperasi pada satu hal dan menggunakan beberapa fungsi perpustakaan standar untuk menerapkannya ke koleksi.

Jadi jawaban baru saya adalah: gunakan teknik pemrograman fungsional dan Anda akan menghindari masalah ini hampir sepanjang waktu. Anda dapat menulis fungsi Anda untuk beroperasi pada satu hal dan hanya menerapkannya pada koleksi hal pada saat terakhir. Tetapi jika mereka murni, Anda dapat mengujinya tanpa referensi koleksi.

Untuk logika yang lebih kompleks, bersandar pada tes berbasis properti . Ketika mereka memiliki logika, itu harus kurang dari dan terbalik dengan logika kode yang diuji, dan setiap tes memverifikasi jauh lebih banyak daripada uji unit berbasis kasus bahwa jumlah kecil logika sepadan.

Di atas semua selalu bersandar pada tipenya . Dapatkan tipe terkuat yang Anda bisa dan gunakan untuk keuntungan Anda. Ini akan mengurangi jumlah tes yang harus Anda tulis di tempat pertama.

Kazark
sumber
4

Jangan mencoba menguji terlalu banyak hal sekaligus. Setiap properti dari setiap objek data dalam koleksi terlalu banyak untuk satu tes. Sebagai gantinya, saya merekomendasikan:

  1. Jika koleksi memiliki panjang tetap, tulis uji unit untuk memvalidasi panjang. Jika panjang variabel, tulis beberapa tes untuk panjang yang akan mencirikan perilakunya (mis. 0, 1, 3, 10). Either way, jangan memvalidasi properti dalam tes ini.
  2. Tulis uji unit untuk memvalidasi masing-masing properti. Jika koleksi tersebut memiliki panjang tetap dan pendek, cukup tegaskan pada satu properti dari masing-masing elemen untuk setiap tes. Jika itu tetap-panjang tapi panjang, pilih sampel representatif tetapi kecil elemen untuk menegaskan terhadap masing-masing properti. Jika panjang variabel, hasilkan koleksi yang relatif pendek tetapi representatif (mis. Mungkin tiga elemen) dan tegaskan satu properti masing-masing.

Melakukannya dengan cara ini membuat tes cukup kecil sehingga meninggalkan loop tidak terasa menyakitkan. Contoh C # / Unit, diberikan metode yang sedang diuji ICollection<Foo> generateFoos(uint numberOfFoos):

[Test]
void generate_zero_foos_returns_empty_list() { ... }
void generate_one_foo_returns_list_of_one_foo() { ... }
void generate_three_foos_returns_list_of_three_foos() { ... }
void generated_foos_have_sequential_ID()
{
    var foos = generateFoos(3).GetEnumerable();
    foos.MoveNext();
    Assert.AreEqual("ID1", foos.Current.id);
    foos.MoveNext();
    Assert.AreEqual("ID2", foos.Current.id);
    foos.MoveNext();
    Assert.AreEqual("ID3", foos.Current.id);
}
void generated_foos_have_bar()
{
    var foos = generateFoos(3).GetEnumerable();
    foos.MoveNext();
    Assert.AreEqual("baz", foos.Current.bar);
    foos.MoveNext();
    Assert.AreEqual("baz", foos.Current.bar);
    foos.MoveNext();
    Assert.AreEqual("baz", foos.Current.bar);
}

Jika Anda terbiasa dengan paradigma "flat unit test" (tidak ada struktur / logika bersarang), tes ini tampaknya cukup bersih. Dengan demikian logika dihindari dalam pengujian dengan mengidentifikasi masalah asli sebagai mencoba menguji terlalu banyak properti sekaligus, daripada kekurangan loop.

Kazark
sumber
1
Osherove akan memiliki kepalamu di piring karena memiliki 3 menegaskan. ;) Yang pertama gagal berarti Anda tidak pernah memvalidasi sisanya. Perhatikan juga bahwa Anda tidak benar-benar menghindari loop. Anda hanya secara eksplisit memperluasnya ke bentuk yang dieksekusi. Bukan kritik keras, tetapi hanya saran untuk mendapatkan lebih banyak latihan mengisolasi kasus pengujian Anda ke jumlah minimum yang mungkin, untuk memberikan diri Anda umpan balik yang lebih spesifik ketika sesuatu gagal, sambil terus memvalidasi kasus-kasus lain yang mungkin masih bisa lulus (atau gagal, dengan umpan balik spesifik mereka sendiri).
Anthony Pegram
3
@AnthonyPegram Saya tahu tentang paradigma one-assert-per-test. Saya lebih suka mantra "uji satu hal" (seperti yang disarankan oleh Bob Martin, terhadap one-assert-per-test, dalam Clean Code ). Catatan: kerangka pengujian unit yang memiliki "harapan" versus "menegaskan" bagus (Google Tests). Sedangkan sisanya, mengapa Anda tidak membagi saran Anda menjadi jawaban lengkap, dengan contoh? Saya pikir saya bisa mendapat manfaat.
Kazark