Mengapa saya memerlukan tes unit untuk menguji metode repositori?

19

Saya perlu berperan sebagai penasihat setan dalam pertanyaan ini sedikit karena saya tidak dapat mempertahankannya dengan baik karena kurangnya pengalaman. Inilah kesepakatannya, saya mendapatkan perbedaan konsep antara pengujian unit dan pengujian integrasi. Ketika secara khusus berfokus pada metode kegigihan dan repositori, unit test akan menggunakan tiruan yang mungkin melalui kerangka kerja seperti Moq untuk menegaskan bahwa mengatakan perintah yang dicari dikembalikan seperti yang diharapkan.

Katakanlah saya telah membangun unit test berikut:

[TestMethod]
public void GetOrderByIDTest()
{
   //Uses Moq for dependency for getting order to make sure 
   //ID I set up in 'Arrange' is same one returned to test in 'Assertion'
}

Jadi jika saya mengatur OrderIdExpected = 5dan objek tiruan saya kembali 5sebagai ID pengujian saya akan berlalu. Saya mengerti. Saya unit menguji kode untuk memastikan apa yang saya preforms kode mengembalikan objek dan ID yang diharapkan dan bukan sesuatu yang lain.

Argumen yang akan saya dapatkan adalah ini:

"Mengapa tidak lewati saja tes unit dan lakukan tes integrasi? Ini menguji prosedur yang tersimpan di basis data dan kode Anda bersama-sama itu penting. Sepertinya terlalu banyak pekerjaan ekstra untuk memiliki tes unit dan tes integrasi ketika pada akhirnya saya ingin tahu apakah database memanggil dan kodenya berfungsi. Saya tahu tesnya lebih lama, tetapi harus dijalankan dan diuji, jadi sepertinya tidak ada gunanya bagi saya untuk memiliki keduanya. Cukup uji terhadap yang penting. "

Saya dapat mempertahankannya dengan definisi buku teks seperti: "Ya, itu adalah tes integrasi dan kita perlu menguji kode secara terpisah sebagai tes unit dan, yada, yada, yada ..." Ini adalah kasus di mana penjelasan yang murni tentang praktik vs kenyataan kalah. Saya kadang-kadang mengalami hal ini dan jika saya tidak dapat mempertahankan alasan di balik kode pengujian unit yang pada akhirnya bergantung pada dependensi eksternal, maka saya tidak dapat membuktikannya.

Setiap bantuan untuk pertanyaan ini sangat kami hargai, terima kasih!

atconway
sumber
2
saya katakan kirim langsung ke pengujian pengguna ... mereka akan mengubah persyaratan bagaimanapun ...
nathan hayfield
+1 untuk sarkasme agar segala sesuatunya ringan dari waktu ke waktu
atconway
2
Jawaban sederhananya adalah bahwa setiap metode dapat memiliki x jumlah kasus tepi (misalkan Anda ingin menguji ID positif, ID 0 dan ID negatif). Jika Anda ingin menguji beberapa fungsionalitas yang menggunakan metode ini, yang memiliki 3 kasus tepi, Anda perlu menulis 9 kasus uji untuk menguji setiap kombinasi kasus tepi. Dengan mengisolasi mereka, Anda hanya perlu menulis 6. Selain itu, tes memberi Anda ide yang lebih spesifik tentang mengapa sesuatu pecah. Mungkin repositori Anda mengembalikan nol pada kegagalan, dan pengecualian nol dilemparkan beberapa ratus baris ke bawah kode.
Rob
Saya pikir Anda terlalu ketat pada definisi Anda tentang tes "unit". Apa itu "unit kerja" untuk kelas repositori?
Caleb
Dan pastikan Anda mempertimbangkan ini: jika unit test Anda Mengejek semua yang Anda coba uji, apa yang sebenarnya Anda uji?
Caleb

Jawaban:

20

Tes unit dan tes integrasi memiliki tujuan yang berbeda.

Tes unit memverifikasi fungsionalitas kode Anda ... bahwa Anda mendapatkan kembali apa yang Anda harapkan dari metode ketika Anda menyebutnya. Tes integrasi menguji bagaimana kode berperilaku ketika dikombinasikan bersama sebagai suatu sistem. Anda tidak akan mengharapkan tes unit untuk mengevaluasi perilaku sistem, Anda juga tidak akan mengharapkan tes integrasi untuk memverifikasi output spesifik dari metode tertentu.

Tes unit, jika dilakukan dengan benar, lebih mudah diatur daripada tes integrasi. Jika Anda hanya mengandalkan tes integrasi, pengujian Anda akan:

  1. Lebih sulit untuk menulis, secara keseluruhan,
  2. Menjadi lebih rapuh, karena semua dependensi yang diperlukan, dan
  3. Tawarkan cakupan kode lebih sedikit.

Intinya: Gunakan pengujian integrasi untuk memverifikasi bahwa koneksi antara objek berfungsi dengan baik, tetapi bersandar pada pengujian unit terlebih dahulu untuk memverifikasi persyaratan fungsional.


Semua yang dikatakan, Anda mungkin tidak perlu pengujian unit untuk metode repositori tertentu. Pengujian unit tidak boleh dilakukan dengan metode sepele ; jika semua yang Anda lakukan adalah melewati permintaan untuk objek ke kode yang dihasilkan ORM dan mengembalikan hasilnya, Anda tidak perlu menguji unit itu, dalam kebanyakan kasus; tes integrasi memadai.

Robert Harvey
sumber
Ya terima kasih atas tanggapan cepatnya saya menghargainya. Satu-satunya kritik saya terhadap tanggapan itu adalah karena perhatian pada paragraf terakhir saya. Oposisi saya masih akan hanya mendengarkan penjelasan dan definisi abstrak dan bukan alasan yang lebih dalam. Sebagai contoh, dapatkah Anda memberikan alasan yang diterapkan pada kasus penggunaan saya untuk menguji prosedur tersimpan / DB dalam kaitannya dengan kode saya dan mengapa unit test masih berharga sehubungan dengan kasus penggunaan khusus ini?
atconway
1
Jika SP baru saja mengembalikan hasil dari database, tes integrasi mungkin memadai. Jika memiliki logika non-sepele di dalamnya, tes unit ditunjukkan. Lihat juga stackoverflow.com/questions/1126614/... dan msdn.microsoft.com/en-us/library/aa833169(v=vs.100).aspx (khusus SQL Server).
Robert Harvey
12

Saya berada di pihak kaum pragmatis. Jangan menguji kode Anda dua kali.

Kami hanya menulis tes integrasi untuk repositori kami. Tes-tes ini hanya tergantung pada pengaturan tes sederhana yang berjalan terhadap database di-memori. Saya pikir mereka memberikan semua yang dilakukan unit test, dan banyak lagi.

  1. Mereka dapat menggantikan tes unit saat melakukan TDD. Walaupun ada beberapa kode uji boilerplate yang harus ditulis sebelum Anda dapat mulai dengan kode asli, ia bekerja dengan sangat baik dengan pendekatan merah / hijau / refaktor setelah semuanya ada.
  2. Mereka menguji kode asli repositori - kode yang terkandung dalam string SQL atau perintah ORM. Lebih penting untuk memverifikasi bahwa kueri itu benar daripada untuk memverifikasi bahwa Anda benar-benar mengirim beberapa string ke StatementExecutor.
  3. Mereka sangat baik sebagai tes regresi. Jika gagal, selalu karena masalah nyata, seperti perubahan skema yang belum diperhitungkan.
  4. Mereka sangat diperlukan ketika mengubah skema database. Anda dapat yakin bahwa Anda belum mengubah skema dengan cara yang merusak aplikasi Anda selama tes lulus. (Uji unit tidak berguna dalam kasus ini, karena ketika prosedur yang tersimpan tidak ada lagi, uji unit masih akan berlalu.)
waxwing
sumber
Memang sangat pragmatis. Saya pernah mengalami kasus bahwa basis data dalam memori sebenarnya berperilaku berbeda (bukan dalam hal kinerja) dengan database asli saya, karena ORM yang sedikit berbeda. Berhati-hatilah dalam tes integrasi Anda.
Marcel
1
@ Marscel, saya pernah mengalami itu. Saya menyelesaikannya dengan terkadang menjalankan semua pengujian saya terhadap basis data nyata.
Winston Ewert
4

Tes unit memberikan tingkat modularitas yang tidak dapat dilakukan uji integrasi (sesuai desain). Ketika suatu sistem direaktor atau dikomposisi ulang, (dan ini akan terjadi) unit test sering dapat digunakan kembali, sedangkan tes integrasi harus sering ditulis ulang. Tes integrasi yang mencoba melakukan pekerjaan dari sesuatu yang seharusnya ada dalam unit test sering melakukan terlalu banyak, yang membuatnya sulit untuk dipertahankan.

Selain itu, termasuk pengujian unit memiliki manfaat berikut:

  1. Tes unit memungkinkan Anda dengan cepat menguraikan tes integrasi yang gagal (mungkin karena bug regresi) dan mengidentifikasi penyebabnya. Selain itu, ini akan mengkomunikasikan masalah kepada seluruh tim lebih cepat daripada diagram atau dokumentasi lainnya.
  2. Tes unit dapat berfungsi sebagai contoh dan dokumentasi (sejenis dokumentasi yang benar - benar dikompilasi ) bersama dengan kode Anda. Tidak seperti dokumentasi lain, Anda akan langsung tahu kapan itu ketinggalan zaman.
  3. Tes unit dapat berfungsi sebagai indikator kinerja dasar ketika mencoba untuk menguraikan masalah kinerja yang lebih besar, sedangkan tes integrasi cenderung memerlukan banyak instrumentasi untuk menemukan masalah.

Dimungkinkan untuk membagi pekerjaan Anda (dan menerapkan pendekatan KERING) saat menggunakan tes Unit dan Integrasi. Cukup mengandalkan tes unit untuk unit fungsionalitas kecil, dan jangan ulangi logika apa pun yang sudah ada dalam tes unit dalam tes integrasi. Ini akan sering menyebabkan lebih sedikit pekerjaan (dan karenanya kurang bekerja kembali).

Zachary Yates
sumber
4

Tes berguna ketika mereka rusak: Secara proaktif atau aktif kembali.

Tes Unit bersifat proaktif dan mungkin merupakan verifikasi fungsional berkelanjutan sementara Tes Integrasi reaktif pada data bertahap / palsu.

Jika sistem yang dipertimbangkan dekat / bergantung pada data lebih dari logika perhitungan, Tes Integrasi harus menjadi lebih penting.

Misalnya, jika kita sedang membangun sistem ETL, tes yang paling relevan adalah seputar data (dipentaskan, dipalsukan, atau hidup). Sistem yang sama akan memiliki beberapa Tes Unit, tetapi hanya seputar validasi, pemfilteran dll.

Pola repositori seharusnya tidak memiliki logika komputasi atau bisnis. Sangat dekat dengan basis data. Tetapi repositori juga dekat dengan logika bisnis yang menggunakannya. Jadi ini adalah pertanyaan untuk mendapatkan KESEIMBANGAN.

Tes Unit dapat menguji BAGAIMANA Repositori berperilaku. Tes Integrasi dapat menguji APA YANG BENAR-BENAR TERJADI ketika repositori dipanggil.

Sekarang, "APA YANG BENAR-BENAR TERJADI" mungkin tampak sangat menarik. Tetapi ini bisa sangat mahal untuk berjalan secara konsisten dan berulang kali. Definisi klasik dari uji getas berlaku di sini.

Jadi sebagian besar waktu, saya hanya merasa cukup baik untuk menulis Tes unit pada objek yang menggunakan repositori. Dengan cara ini kami menguji apakah metode repositori yang tepat dipanggil dan hasil Mocked yang tepat dikembalikan.

Otpidus
sumber
Saya suka ini: "Sekarang," APA YANG BENAR-BENAR TERJADI "mungkin terlihat sangat menarik. Tetapi ini bisa sangat mahal untuk berjalan secara konsisten dan berulang kali."
atconway
2

Ada 3 hal terpisah yang perlu diuji: prosedur tersimpan, kode yang memanggil prosedur tersimpan (yaitu kelas repositori Anda), dan konsumen. Tugas repositori adalah menghasilkan kueri dan mengonversi set data yang dikembalikan menjadi objek domain. Ada cukup kode untuk mendukung pengujian unit yang terpisah dari pelaksanaan aktual permintaan dan pembuatan set data.

Jadi (ini adalah contoh yang sangat sederhana):

interface IOrderRepository
{
    Order GetOrderByID(Guid id);
}

class OrderRepository : IOrderRepository
{
    private readonly ISqlExecutor sqlExecutor;
    public OrderRepository(ISqlExecutor sqlExecutor)
    {
        this.sqlExecutor = sqlExecutor;
    }

    public Order GetOrderByID(Guid id)
    {
        var sql = "SELECT blah, blah FROM Order WHERE OrderId = @p0";
        var dataset = this.sqlExecutor.Execute(sql, p0 = id);
        var result = this.orderFromDataset(dataset);
        return result;
    }
}

Kemudian ketika pengujian OrderRepository, berikan mocked ISqlExecutordan periksa bahwa objek yang diuji lulus dalam SQL yang benar (itu tugasnya) dan mengembalikan Orderobjek yang tepat diberikan beberapa dataset hasil (juga diejek). Kemungkinan satu-satunya cara untuk menguji SqlExecutorkelas beton adalah dengan tes integrasi, cukup adil, tapi itu kelas pembungkus tipis dan jarang akan pernah berubah, jadi masalah besar.

Anda masih harus menguji unit prosedur tersimpan Anda juga.

Scott Whitlock
sumber
0

Saya dapat mereduksi ini menjadi garis pemikiran yang sangat sederhana: Mendukung unit test karena membantu menemukan bug. Dan lakukan ini secara konsisten karena ketidakkonsistenan menyebabkan kebingungan.

Alasan lebih lanjut berasal dari aturan yang sama yang memandu pengembangan OO karena mereka juga berlaku untuk tes. Misalnya, prinsip tanggung jawab tunggal.

Jika beberapa tes unit tampaknya tidak layak dilakukan, maka mungkin itu pertanda bahwa subjek tersebut sebenarnya tidak layak untuk dimiliki. Atau mungkin fungsinya lebih abstrak dari padanannya, dan tes untuk itu (serta kodenya) dapat diabstraksikan ke tingkat yang lebih tinggi.

Seperti apa pun, ada pengecualian. Dan pemrograman masih agak dari bentuk seni sehingga banyak masalah cukup berbeda untuk menjamin menilai masing-masing untuk pendekatan terbaik.

CL22
sumber