Memilih strategi untuk pengujian

Seperti yang dibahas dalam Gambaran Umum, keputusan dasar yang perlu Anda buat adalah apakah pengujian Anda akan melibatkan sistem database produksi Anda - seperti yang dilakukan aplikasi Anda - atau apakah pengujian Anda akan berjalan terhadap pengujian ganda, yang menggantikan sistem database produksi Anda.

Pengujian terhadap sumber daya eksternal nyata - daripada menggantinya dengan pengujian ganda - dapat melibatkan kesulitan berikut:

  1. Dalam banyak kasus, tidak mungkin atau praktis untuk menguji terhadap sumber daya eksternal yang sebenarnya. Misalnya, aplikasi Anda dapat berinteraksi dengan beberapa layanan yang tidak dapat dengan mudah diuji (karena pembatasan laju, atau kurangnya lingkungan pengujian).
  2. Bahkan ketika mungkin untuk melibatkan sumber daya eksternal nyata, ini mungkin terlalu lambat: menjalankan sejumlah besar pengujian terhadap layanan cloud dapat menyebabkan pengujian terlalu lama. Pengujian harus menjadi bagian dari alur kerja sehari-hari pengembang, jadi penting bahwa pengujian berjalan dengan cepat.
  3. Menjalankan pengujian terhadap sumber daya eksternal dapat melibatkan masalah isolasi, di mana pengujian mengganggu satu dengan yang lain. Misalnya, beberapa pengujian yang berjalan secara paralel terhadap database dapat memodifikasi data dan menyebabkan satu sama lain gagal dengan berbagai cara. Menggunakan pengujian ganda menghindari hal ini, karena setiap pengujian berjalan terhadap sumber daya dalam memorinya sendiri, dan karenanya secara alami terisolasi dari pengujian lain.

Namun, pengujian yang lulus terhadap pengujian ganda tidak menjamin bahwa program Anda berfungsi saat berjalan melawan sumber daya eksternal nyata. Misalnya, pengujian database ganda dapat melakukan perbandingan string peka huruf besar/kecil, sedangkan sistem database produksi melakukan perbandingan yang tidak peka huruf besar/kecil. Masalah tersebut hanya terungkap ketika pengujian dijalankan terhadap database produksi nyata Anda, menjadikan pengujian ini bagian penting dari strategi pengujian apa pun.

Pengujian terhadap database mungkin lebih mudah daripada yang terlihat

Karena kesulitan di atas dengan pengujian terhadap database nyata, pengembang sering didesak untuk menggunakan pengujian ganda terlebih dahulu, dan memiliki rangkaian pengujian yang kuat yang dapat sering mereka jalankan di komputer mereka; pengujian yang melibatkan database, sebaliknya, seharusnya dijalankan jauh lebih jarang, dan dalam banyak kasus juga memberikan cakupan yang jauh lebih sedikit. Sebaiknya berikan lebih banyak pemikiran kepada yang terakhir, dan sarankan bahwa database mungkin benar-benar jauh kurang terpengaruh oleh masalah di atas daripada yang cenderung dipikirkan orang:

  1. Sebagian besar database saat ini dapat dengan mudah diinstal pada komputer pengembang. Teknologi berbasis kontainer seperti Docker dapat membuat ini sangat mudah, dan teknologi seperti Github Workspaces dan Dev Container menyiapkan seluruh lingkungan pengembangan Anda untuk Anda (termasuk database). Saat menggunakan SQL Server, Dimungkinkan juga untuk menguji terhadap LocalDB di Windows, atau dengan mudah menyiapkan gambar Docker di Linux.
  2. Pengujian terhadap database lokal - dengan himpunan data pengujian yang wajar - biasanya sangat cepat: komunikasi benar-benar lokal, dan data pengujian biasanya di-buffer dalam memori di sisi database. EF Core sendiri berisi lebih dari 30.000 pengujian terhadap SQL Server saja; ini selesai dengan andal dalam beberapa menit, dijalankan di CI pada setiap penerapan tunggal, dan sangat sering dijalankan oleh pengembang secara lokal. Beberapa pengembang beralih ke database dalam memori ("palsu") dalam keyakinan bahwa ini diperlukan untuk kecepatan - ini hampir tidak pernah benar-benar terjadi.
  3. Isolasi memang merupakan rintangan saat menjalankan pengujian terhadap database nyata, karena pengujian dapat memodifikasi data dan mengganggu satu sama lain. Namun, ada berbagai teknik untuk memberikan isolasi dalam skenario pengujian database; kami berkonsentrasi pada ini dalam Pengujian terhadap sistem database produksi Anda).

Hal di atas tidak dimaksudkan untuk membedakan tes ganda atau untuk berdebat terhadap penggunaannya. Untuk satu hal, pengujian ganda diperlukan untuk beberapa skenario yang tidak dapat diuji sebaliknya, seperti mensimulasikan kegagalan database. Namun, dalam pengalaman kami, pengguna sering malu-malu menguji terhadap database mereka karena alasan di atas, percaya itu lambat, sulit atau tidak dapat diandalkan, ketika itu belum tentu terjadi. Pengujian terhadap sistem database produksi Anda bertujuan untuk mengatasi hal ini, memberikan panduan dan sampel untuk menulis pengujian yang cepat dan terisolasi terhadap database Anda.

Berbagai jenis uji ganda

Uji ganda adalah istilah luas yang mencakup pendekatan yang sangat berbeda. Bagian ini mencakup beberapa teknik umum yang melibatkan pengujian ganda untuk menguji aplikasi EF Core:

  1. Gunakan SQLite (mode dalam memori) sebagai database palsu, menggantikan sistem database produksi Anda.
  2. Gunakan penyedia dalam memori EF Core sebagai database palsu, menggantikan sistem database produksi Anda.
  3. Mock atau stub out DbContext dan DbSet.
  4. Perkenalkan lapisan repositori antara EF Core dan kode aplikasi Anda, dan tiruan atau stub lapisan tersebut.

Di bawah ini, kita akan menjelajahi apa arti setiap metode, dan membandingkannya dengan yang lain. Sebaiknya baca melalui berbagai metode untuk mendapatkan pemahaman penuh tentang masing-masing metode. Jika Anda telah memutuskan untuk menulis pengujian yang tidak melibatkan sistem database produksi Anda, maka lapisan repositori adalah satu-satunya pendekatan yang memungkinkan stubbing/mocking lapisan data yang komprehensif dan andal. Namun, pendekatan tersebut memiliki biaya yang signifikan dalam hal implementasi dan pemeliharaan.

SQLite sebagai database palsu

Salah satu pendekatan pengujian yang mungkin adalah menukar database produksi Anda (misalnya SQL Server) dengan SQLite, secara efektif menggunakannya sebagai pengujian "palsu". Selain kemudahan penyiapan, SQLite memiliki fitur database dalam memori yang sangat berguna untuk pengujian: setiap pengujian secara alami diisolasi dalam database dalam memorinya sendiri, dan tidak ada file aktual yang perlu dikelola.

Namun, sebelum melakukan ini, penting untuk dipahami bahwa di EF Core, penyedia database yang berbeda bertingkah berbeda - EF Core tidak mencoba mengabstraksi setiap aspek sistem database yang mendasar. Pada dasarnya, ini berarti bahwa pengujian terhadap SQLite tidak menjamin hasil yang sama seperti terhadap SQL Server, atau database lainnya. Berikut adalah beberapa contoh kemungkinan perbedaan perilaku:

  • Kueri LINQ yang sama dapat mengembalikan hasil yang berbeda pada penyedia yang berbeda. Misalnya, SQL Server melakukan perbandingan string yang tidak peka huruf besar/kecil secara default, sedangkan SQLite peka huruf besar/kecil. Ini dapat membuat pengujian Anda lulus terhadap SQLite di mana mereka akan gagal melawan SQL Server (atau sebaliknya).
  • Beberapa kueri yang berfungsi di SQL Server tidak didukung di SQLite, karena dukungan SQL yang tepat dalam dua database ini berbeda.
  • Jika kueri Anda kebetulan menggunakan metode khusus penyedia seperti SQL Server EF.Functions.DateDiffDay, kueri tersebut akan gagal pada SQLite, dan tidak dapat diuji.
  • SQL mentah mungkin berfungsi, atau mungkin gagal atau mengembalikan hasil yang berbeda, tergantung pada apa yang sedang dilakukan. Dialek SQL berbeda dalam banyak cara di seluruh database.

Dibandingkan dengan menjalankan pengujian terhadap sistem database produksi Anda, relatif mudah untuk memulai dengan SQLite, dan begitu banyak pengguna melakukannya. Sayangnya, batasan di atas cenderung pada akhirnya menjadi bermasalah saat menguji aplikasi EF Core, bahkan jika mereka tampaknya tidak berada di awal. Akibatnya, sebaiknya tulis pengujian Anda terhadap database nyata Anda, atau jika menggunakan pengujian ganda adalah kebutuhan absolut, dengan menggunakan biaya pola repositori seperti yang dibahas di bawah ini.

Untuk informasi tentang cara menggunakan SQLite untuk pengujian, lihat bagian ini.

Dalam memori sebagai database palsu

Sebagai alternatif untuk SQLite, EF Core juga dilengkapi dengan penyedia dalam memori. Meskipun penyedia ini awalnya dirancang untuk mendukung pengujian internal EF Core itu sendiri, beberapa pengembang menggunakannya sebagai palsu database saat menguji aplikasi EF Core. Melakukannya sangat tidak disarankan: sebagai database palsu, dalam memori memiliki masalah yang sama dengan SQLite (lihat di atas), tetapi selain itu memiliki batasan tambahan berikut:

  • Penyedia dalam memori umumnya mendukung lebih sedikit jenis kueri daripada penyedia SQLite, karena bukan database relasional. Lebih banyak kueri akan gagal atau berprilaku berbeda dibandingkan dengan database produksi Anda.
  • Transaksi tidak didukung.
  • SQL mentah benar-benar tidak didukung. Bandingkan ini dengan SQLite, di mana dimungkinkan untuk menggunakan SQL mentah, selama SQL bekerja dengan cara yang sama pada SQLite dan database produksi Anda.
  • Penyedia dalam memori belum dioptimalkan untuk performa, dan umumnya akan bekerja lebih lambat daripada SQLite dalam mode dalam memori (atau bahkan sistem database produksi Anda).

Singkatnya, dalam memori memiliki semua kelemahan SQLite, bersama dengan beberapa lagi - dan tidak menawarkan keuntungan sebagai gantinya. Jika Anda mencari database palsu dalam memori yang sederhana, gunakan SQLite alih-alih penyedia dalam memori; tetapi pertimbangkan untuk menggunakan pola repositori sebagai gantinya seperti yang dijelaskan di bawah ini.

Untuk informasi tentang cara menggunakan dalam memori untuk pengujian, lihat bagian ini.

Mocking atau stubbing DbContext dan DbSet

Pendekatan ini biasanya menggunakan kerangka kerja tiruan untuk membuat pengujian ganda dan DbContextDbSet, dan pengujian terhadap ganda tersebut. Meniru DbContext dapat menjadi pendekatan yang baik untuk menguji berbagai fungsionalitas non-kueri , seperti panggilan ke Add atau SaveChanges(), memungkinkan Anda memverifikasi bahwa kode Anda memanggilnya dalam skenario tulis.

Namun, fungsionalitas kueri yang meniru DbSetdengan benar tidak dimungkinkan, karena kueri dinyatakan melalui operator LINQ, yang merupakan panggilan metode ekstensi statis melalui IQueryable. Akibatnya, ketika beberapa orang berbicara tentang "meniru DbSet", apa yang benar-benar mereka maksud adalah bahwa mereka membuat DbSet didukung oleh koleksi dalam memori, dan kemudian mengevaluasi operator kueri terhadap koleksi itu dalam memori, sama seperti yang sederhana IEnumerable. Alih-alih tiruan, ini sebenarnya semacam palsu, di mana koleksi dalam memori menggantikan database nyata.

Karena hanya DbSet itu sendiri yang dipalsukan dan kueri dievaluasi dalam memori, pendekatan ini akhirnya sangat mirip dengan menggunakan penyedia dalam memori EF Core: kedua teknik menjalankan operator kueri di .NET melalui koleksi dalam memori. Akibatnya, teknik ini juga menderita kelemahan yang sama: kueri akan berperilaku berbeda (misalnya sekeliling sensitivitas kasus) atau hanya akan gagal (misalnya karena metode khusus penyedia), SQL mentah tidak akan berfungsi dan transaksi akan diabaikan paling baik. Akibatnya, teknik ini umumnya harus dihindari untuk menguji kode kueri apa pun.

Pola repositori

Pendekatan di atas mencoba menukar penyedia database produksi EF Core dengan penyedia pengujian palsu, atau untuk membuat yang DbSet didukung oleh koleksi dalam memori. Teknik ini serupa karena mereka masih mengevaluasi kueri LINQ program - baik dalam SQLite atau dalam memori - dan ini pada akhirnya adalah sumber kesulitan yang diuraikan di atas: kueri yang dirancang untuk dijalankan terhadap database produksi tertentu tidak dapat dijalankan dengan andal di tempat lain tanpa masalah.

Untuk pengujian yang tepat dan andal, pertimbangkan untuk memperkenalkan lapisan repositori yang memediasi antara kode aplikasi Anda dan EF Core. Implementasi produksi repositori berisi kueri LINQ aktual dan menjalankannya melalui EF Core. Dalam pengujian, abstraksi repositori secara langsung tersandung atau ditipu tanpa memerlukan kueri LINQ yang sebenarnya, secara efektif menghapus EF Core dari tumpukan pengujian Anda sama sekali dan memungkinkan pengujian untuk fokus pada kode aplikasi saja.

Diagram berikut membandingkan pendekatan palsu database (SQLite/in-memory) dengan pola repositori:

Comparison of fake provider with repository pattern

Karena kueri LINQ tidak lagi menjadi bagian dari pengujian, Anda dapat langsung memberikan hasil kueri ke aplikasi Anda. Letakkan cara lain, pendekatan sebelumnya kira-kira memungkinkan stubbing out input kueri (misalnya mengganti tabel SQL Server dengan yang dalam memori), tetapi kemudian masih menjalankan operator kueri aktual dalam memori. Pola repositori, sebaliknya, memungkinkan Anda untuk mengeluarkan output kueri secara langsung, memungkinkan pengujian unit yang jauh lebih kuat dan terfokus . Perhatikan bahwa agar ini berfungsi, repositori Anda tidak dapat mengekspos metode yang dapat dikembalikan IQueryable, karena sekali lagi ini tidak dapat di-stubbing; IEnumerable harus dikembalikan sebagai gantinya.

Namun, karena pola repositori memerlukan enkapsulasi masing-masing dan setiap kueri LINQ (dapat diuji) dalam metode yang dikembalikan IEnumerable, itu memberlakukan lapisan arsitektur tambahan pada aplikasi Anda, dan dapat dikenakan biaya yang signifikan untuk mengimplementasikan dan memelihara. Biaya ini tidak boleh didiskon ketika membuat pilihan tentang cara menguji aplikasi, terutama mengingat bahwa pengujian terhadap database nyata masih mungkin diperlukan untuk kueri yang diekspos oleh repositori.

Perlu dicatat bahwa repositori memang memiliki keuntungan di luar hanya pengujian. Mereka memastikan semua kode akses data terkonsentrasi di satu tempat daripada tersebar di sekitar aplikasi, dan jika aplikasi Anda perlu mendukung lebih dari satu database, maka abstraksi repositori dapat sangat membantu untuk mengubah kueri di seluruh penyedia.

Untuk sampel yang memperlihatkan pengujian dengan repositori, lihat bagian ini.

Perbandingan keseluruhan

Tabel berikut ini menyediakan tampilan cepat dan komparatif dari berbagai teknik pengujian, dan menunjukkan fungsionalitas mana yang dapat diuji di mana pendekatan:

Fitur Dalam memori SQLite dalam memori Mock DbContext Pola repositori Pengujian terhadap database
Uji tipe ganda Palsu Palsu Palsu Mock/stub Nyata, tidak ada ganda
SQL mentah? Tidak Bergantung Tidak Ya Ya
Transaksi? Tidak (diabaikan) Ya Ya Ya Ya
Terjemahan khusus penyedia? Tidak Tidak Tidak Ya Ya
Perilaku kueri yang tepat? Bergantung Bergantung Bergantung Ya Ya
Dapatkah menggunakan LINQ di mana saja dalam aplikasi? Ya Ya Ya Tidak* Ya

* Semua kueri LINQ database yang dapat diuji harus dienkapsulasi dalam metode repositori IEnumerable-returning, agar tersandung/ditiru.

Ringkasan

  • Kami menyarankan agar pengembang memiliki cakupan pengujian yang baik tentang aplikasi mereka yang berjalan terhadap sistem database produksi mereka yang sebenarnya. Ini memberikan keyakinan bahwa aplikasi benar-benar bekerja dalam produksi, dan dengan desain yang tepat, pengujian dapat dijalankan dengan andal dan cepat. Karena pengujian ini diperlukan dalam hal apa pun, ada baiknya untuk memulai di sana, dan jika diperlukan, tambahkan pengujian menggunakan pengujian ganda nanti, sesuai kebutuhan.
  • Jika Anda telah memutuskan untuk menggunakan pengujian ganda, sebaiknya terapkan pola repositori, yang memungkinkan Anda untuk mencambuk atau mengejek lapisan akses data Anda di atas EF Core, daripada menggunakan penyedia EF Core palsu (Sqlite/in-memory) atau dengan menipu DbSet.
  • Jika pola repositori bukan opsi yang layak karena alasan tertentu, pertimbangkan untuk menggunakan database dalam memori SQLite.
  • Hindari penyedia dalam memori untuk tujuan pengujian - ini tidak disarankan dan hanya didukung untuk aplikasi warisan.
  • Hindari meniru DbSet untuk tujuan kueri.