Unit-Tes dan database: Pada titik mana saya benar-benar terhubung ke database?

37

Ada jawaban untuk pertanyaan tentang bagaimana kelas uji yang terhubung ke database, mis. "Haruskah kelas uji layanan terhubung ..." dan "Pengujian unit - Aplikasi yang digabungkan dengan basis data" .

Jadi, singkatnya mari kita asumsikan Anda memiliki kelas A yang perlu terhubung ke database. Alih-alih membiarkan A benar-benar terhubung, Anda memberikan A dengan antarmuka yang A dapat digunakan untuk terhubung. Untuk pengujian Anda mengimplementasikan antarmuka ini dengan beberapa hal - tanpa menghubungkan tentu saja. Jika kelas B instantiate A harus melewati koneksi database "nyata" ke A. Tapi itu berarti B membuka koneksi database. Itu berarti untuk menguji B Anda menyuntikkan koneksi ke B. Tapi B dipakai di kelas C dan sebagainya.

Jadi pada titik mana saya harus mengatakan "di sini saya mengambil data dari database dan saya tidak akan menulis unit test untuk potongan kode ini"?

Dengan kata lain: Di suatu tempat dalam kode di beberapa kelas saya harus menelepon sqlDB.connect()atau yang serupa. Bagaimana saya menguji kelas ini?

Dan apakah itu sama dengan kode yang harus berurusan dengan GUI atau sistem file?


Saya ingin melakukan Tes Unit. Jenis tes lain apa pun tidak terkait dengan pertanyaan saya. Saya tahu bahwa saya hanya akan menguji satu kelas dengan itu (saya sangat setuju dengan Anda Kilian). Sekarang, beberapa kelas harus terhubung ke DB. Jika saya ingin menguji kelas ini dan bertanya "Bagaimana saya melakukan ini" banyak yang mengatakan: "Gunakan Injeksi Ketergantungan!" Tapi itu hanya memindahkan masalah ke kelas lain, bukan? Jadi saya bertanya, bagaimana cara menguji kelas yang benar-benar membangun koneksi?

Pertanyaan bonus: Beberapa jawaban di sini bermuara pada "Gunakan benda tiruan!" Apa artinya? Saya mengejek kelas yang bergantung pada kelas yang diuji. Haruskah saya mengejek kelas yang sedang diuji sekarang dan benar-benar menguji tiruannya (yang mendekati gagasan menggunakan Metode Templat, lihat di bawah)?

TobiMcNamobi
sumber
Apakah ini koneksi basis data yang Anda uji? Apakah membuat basis data sementara di memori (seperti derby ) dapat diterima?
@MichaelT Masih saya harus mengganti DB sementara di memori dengan database nyata. Dimana? Kapan? Bagaimana unit itu diuji? Atau apakah OK untuk tidak menguji unit kode ini?
TobiMcNamobi
3
Tidak ada apa pun untuk "unit test" tentang basis data. Itu dikelola oleh orang lain, dan jika ada bug di dalamnya, Anda harus membiarkan mereka memperbaikinya daripada melakukannya sendiri. Satu-satunya hal yang harus berbeda antara penggunaan aktual kelas Anda dan penggunaan selama tes harus menjadi parameter koneksi database. Tidak mungkin bahwa kode pembacaan file properti, atau mekanisme injeksi Spring atau apa pun yang Anda gunakan untuk menenun aplikasi Anda bersama-sama rusak (dan jika ya, Anda tidak dapat memperbaikinya sendiri, lihat di atas) - jadi saya menganggap itu dapat diterima tidak menguji sedikit fungsi pipa ledeng ini.
Kilian Foth
2
@KilianFoth yang murni terkait dengan lingkungan kerja dan peran karyawan. Itu tidak benar-benar ada hubungannya dengan pertanyaan. Bagaimana jika tidak ada satu orang yang bertanggung jawab atas database?
Reactgular
Beberapa kerangka kerja mengejek memungkinkan Anda untuk menyuntikkan benda tiruan ke dalam apa saja, bahkan menjadi anggota pribadi dan statis. Ini membuat pengujian dengan hal-hal seperti koneksi mock db jadi sangat mudah. Mockito + Powermock adalah yang bekerja untuk saya hari ini (mereka Java, tidak yakin apa yang Anda kerjakan).
FrustratedWithFormsDesigner

Jawaban:

21

Inti dari tes unit adalah untuk menguji satu kelas (pada kenyataannya, biasanya harus menguji satu metode ).

Ini berarti bahwa ketika Anda menguji kelas A, Anda menyuntikkan database uji ke dalamnya - sesuatu yang ditulis sendiri, atau database dalam memori yang cepat, apa pun yang menyelesaikan pekerjaan.

Namun, jika Anda menguji kelas B, yang merupakan klien A, maka biasanya Anda mengejek seluruh Aobjek dengan sesuatu yang lain, mungkin sesuatu yang melakukan tugasnya dengan cara primitif, yang telah diprogram sebelumnya - tanpa menggunakan Aobjek aktual dan tentunya tanpa menggunakan data base (kecuali Amelewati seluruh koneksi basis data kembali ke peneleponnya - tapi itu sangat mengerikan saya tidak ingin memikirkannya). Demikian juga, ketika Anda menulis unit test untuk kelas C, yang merupakan klien B, Anda akan mengejek sesuatu yang mengambil peran B, dan lupakan Asemuanya.

Jika Anda tidak melakukan itu, itu bukan lagi tes unit, tetapi tes sistem atau integrasi. Itu juga sangat penting, tetapi ketel ikan yang sama sekali berbeda. Untuk mulai dengan, mereka biasanya lebih banyak upaya untuk mengatur dan menjalankan, itu tidak praktis untuk menuntut melewati mereka sebagai prasyarat untuk check-in, dll.

Kilian Foth
sumber
11

Melakukan tes unit terhadap koneksi database sangat normal, dan merupakan praktik yang umum. Sama sekali tidak mungkin untuk membuat puristpendekatan di mana segala sesuatu di sistem Anda dapat disuntikkan ketergantungan.

Kuncinya di sini adalah untuk menguji terhadap basis data sementara atau hanya menguji, dan memiliki proses memulai paling ringan untuk membangun database pengujian.

Untuk pengujian unit di CakePHP ada hal-hal yang disebut fixtures. Jadwal adalah tabel basis data sementara yang dibuat dengan cepat untuk pengujian unit. Fixture memiliki metode kenyamanan untuk membuatnya. Mereka dapat membuat ulang skema dari database produksi di dalam database pengujian, atau Anda dapat menentukan skema menggunakan notasi sederhana.

Kunci sukses dengan ini adalah tidak mengimplementasikan database bisnis, tetapi untuk fokus hanya pada aspek kode yang Anda uji. Jika Anda memiliki tes unit yang memverifikasi bahwa model data hanya membaca dokumen yang diterbitkan, maka skema tabel untuk tes itu hanya memiliki bidang yang diperlukan oleh kode itu. Anda tidak perlu menerapkan kembali seluruh basis data manajemen konten hanya untuk menguji kode itu.

Beberapa referensi tambahan.

http://en.wikipedia.org/wiki/Test_fixture

http://phpunit.de/manual/3.7/en/database.html

http://book.cakephp.org/2.0/id/development/testing.html#fixtures

Reactgular
sumber
28
Saya tidak setuju. Tes yang membutuhkan koneksi basis data bukanlah tes unit, karena tes pada dasarnya akan memiliki efek samping. Itu tidak berarti Anda tidak dapat menulis tes otomatis, tetapi tes tersebut menurut definisi merupakan tes integrasi, menggunakan area sistem Anda di luar basis kode Anda.
KeithS
5
Panggil saya purist, tapi saya berpegang pada prinsip bahwa unit test tidak boleh melakukan tindakan apa pun yang meninggalkan "kotak pasir" dari lingkungan runtime pengujian. Mereka tidak boleh menyentuh basis data, sistem file, soket jaringan, dll. Ini karena beberapa alasan, tidak sedikit di antaranya adalah ketergantungan tes pada keadaan eksternal. Lainnya adalah kinerja; unit tes unit Anda harus berjalan cepat, dan berinteraksi dengan data eksternal ini menyimpan tes lambat dengan perintah besarnya. Dalam perkembangan saya sendiri, saya menggunakan ejekan parsial untuk menguji hal-hal seperti repositori saya, dan saya merasa nyaman mendefinisikan "tepi" pada kotak pasir saya.
KeithS
2
@ gbjbaanb - Kedengarannya bagus pada awalnya, tetapi dalam pengalaman saya itu sangat berbahaya. Bahkan di suite dan kerangka kerja uji terbaik yang dirancang, kode untuk mengembalikan transaksi ini mungkin tidak dijalankan. Jika pelari uji crash atau dibatalkan di dalam tes, atau tes melempar SOE atau OOME, kasus terbaiknya adalah Anda memiliki koneksi yang tergantung dan transaksi dalam DB yang akan mengunci tabel yang Anda sentuh hingga koneksi terbunuh. Cara-cara Anda mencegah masalah yang menyebabkan ini, seperti menggunakan SQLite sebagai tes DB, memiliki kelemahan sendiri, misalnya fakta Anda tidak benar - benar menggunakan DB nyata .
KeithS
5
@KeithS Saya pikir kita sedang berdebat tentang semantik. Ini bukan tentang apa definisi tes unit atau tes integrasi. Saya menggunakan perlengkapan untuk menguji kode tergantung pada koneksi database. Jika itu tes integrasi, maka saya tidak masalah dengan itu. Saya perlu tahu bahwa tes lulus. Saya tidak peduli tentang ketergantungan, kinerja, atau risiko. Saya tidak akan tahu apakah kode itu berfungsi kecuali tes itu lolos. Untuk sebagian besar tes tidak ada dependensi, tetapi untuk yang ada, maka dependensi tidak dapat dipisahkan. Mudah untuk mengatakan bahwa mereka seharusnya, tetapi mereka tidak bisa.
Reactgular
4
Saya pikir kita juga. Saya menggunakan "kerangka kerja unit-test" (NUnit) untuk tes integrasi saya juga, tapi saya pastikan untuk memisahkan dua kategori tes ini (sering di perpustakaan terpisah). Poin yang saya coba sampaikan adalah bahwa unit test suite Anda, yang Anda jalankan beberapa kali sehari sebelum setiap check-in saat Anda mengikuti metodologi iteratif merah-hijau-refactor, harus sepenuhnya terisolasi, sehingga Anda dapat menjalankan tes ini beberapa kali sehari tanpa menginjak kaki rekan kerja Anda.
KeithS
4

Ada, di suatu tempat di basis kode Anda, satu baris kode yang melakukan tindakan aktual untuk menghubungkan ke DB jarak jauh. Baris kode ini, 9 kali dalam 10, panggilan ke metode "bawaan" yang disediakan oleh pustaka runtime yang spesifik untuk bahasa dan lingkungan Anda. Karena itu, ini bukan kode "Anda" dan karenanya Anda tidak perlu mengujinya; untuk keperluan pengujian unit, Anda dapat percaya bahwa panggilan metode ini akan bekerja dengan benar. Apa yang Anda bisa, dan harus, masih uji di unit test unit Anda adalah hal-hal seperti memastikan parameter yang akan digunakan untuk panggilan ini adalah apa yang Anda harapkan, seperti memastikan string koneksi sudah benar, atau pernyataan SQL atau nama prosedur tersimpan.

Ini adalah salah satu tujuan di balik pembatasan bahwa tes unit tidak boleh meninggalkan "kotak pasir" runtime dan bergantung pada keadaan eksternal. Ini sebenarnya cukup praktis; tujuan dari tes unit adalah untuk memverifikasi bahwa kode yang Anda tulis (atau akan menulis, dalam TDD) berperilaku seperti yang Anda pikir akan. Kode yang tidak Anda tulis, seperti perpustakaan yang Anda gunakan untuk melakukan operasi basis data Anda, tidak boleh menjadi bagian dari cakupan setiap unit test, karena alasan yang sangat sederhana bahwa Anda tidak menulisnya.

Di suite tes integrasi Anda , pembatasan ini santai. Sekarang kamu bisates desain yang menyentuh database, untuk memastikan bahwa kode yang Anda tulis bermain dengan baik dengan kode yang tidak Anda tulis. Namun, dua suite pengujian ini harus tetap dipisahkan, karena unit test suite Anda lebih efektif semakin cepat dijalankan (sehingga Anda dapat dengan cepat memverifikasi bahwa semua pernyataan yang dibuat oleh pengembang tentang kode mereka masih berlaku), dan hampir secara definisi, tes integrasi lebih lambat oleh urutan besarnya karena ketergantungan ditambahkan pada sumber daya eksternal. Biarkan build-bot menangani menjalankan paket integrasi penuh Anda setiap beberapa jam, menjalankan tes yang mengunci sumber daya eksternal, sehingga pengembang tidak saling menginjak jari kaki dengan menjalankan tes yang sama ini secara lokal. Dan jika bangunannya rusak, lalu apa? Jauh lebih penting ditempatkan pada memastikan build-bot tidak pernah gagal membangun daripada yang seharusnya.


Sekarang, seberapa ketat Anda dapat mematuhinya tergantung pada strategi pasti Anda untuk menghubungkan dan menanyakan database. Dalam banyak kasus di mana Anda harus menggunakan kerangka akses data "tulang kosong", seperti objek SqlConnection dan SqlStatement ADO.NET, seluruh metode yang dikembangkan oleh Anda dapat terdiri dari pemanggilan metode bawaan dan kode lain yang bergantung pada memiliki koneksi basis data, dan yang terbaik yang dapat Anda lakukan dalam situasi ini adalah mengejek seluruh fungsi dan mempercayai suite pengujian integrasi Anda. Ini juga tergantung pada seberapa bersedia Anda merancang kelas Anda untuk memungkinkan baris kode tertentu diganti untuk tujuan pengujian (seperti saran Tobi tentang pola Metode Templat, yang bagus karena memungkinkan "tiruan sebagian")

Jika model ketekunan data Anda bergantung pada kode di lapisan data Anda (seperti pemicu, procs tersimpan, dll) maka tidak ada cara lain untuk menjalankan kode yang Anda sendiri tulis selain mengembangkan tes yang hidup di dalam lapisan data atau melintasi batas antara runtime aplikasi Anda dan DBMS. Seorang purist akan mengatakan pola ini, karena alasan ini, harus dihindari demi sesuatu seperti ORM. Saya tidak berpikir saya akan melangkah sejauh itu; bahkan di zaman query yang terintegrasi dengan bahasa dan operasi ketekunan yang diperiksa oleh kompiler lainnya, saya melihat nilai dalam mengunci basis data hingga hanya operasi yang diekspos melalui prosedur tersimpan, dan tentu saja prosedur tersimpan tersebut harus diverifikasi menggunakan otomatis tes. Tapi, tes semacam itu bukan tes unit . Mereka adalah integrasi tes.

Jika Anda memiliki masalah dengan perbedaan ini, biasanya didasarkan pada kepentingan yang tinggi ditempatkan pada "cakupan kode" lengkap alias "cakupan unit test". Anda ingin memastikan setiap baris kode Anda dicakup oleh tes unit. Sebuah tujuan mulia di wajahnya, tetapi saya katakan omong kosong; bahwa mentalitas cocok untuk anti-pola yang jauh melampaui kasus khusus ini, seperti menulis tes tegas yang mengeksekusi tetapi tidak berolahragakode Anda. Jenis end-run ini semata-mata demi nomor pertanggungan lebih berbahaya daripada melonggarkan cakupan minimum Anda. Jika Anda ingin memastikan bahwa setiap baris basis kode Anda dieksekusi oleh beberapa tes otomatis, maka itu mudah; saat menghitung metrik cakupan kode, sertakan tes integrasi. Anda bahkan dapat melangkah lebih jauh dan mengisolasi tes "Itino" yang disengketakan ini ("Integrasi nama saja"), dan antara unit uji unit Anda dan sub-kategori tes integrasi ini (yang seharusnya masih berjalan cukup cepat) Anda harus mendapatkan darn dekat dengan cakupan penuh.

KeithS
sumber
2

Tes unit tidak boleh terhubung ke database. Menurut definisi, mereka harus menguji satu unit kode masing-masing (metode) dalam isolasi total dari sisa sistem Anda. Jika tidak, maka itu bukan tes unit.

Selain semantik, ada banyak alasan mengapa ini bermanfaat:

  • Tes menjalankan urutan besarnya lebih cepat
  • Loop umpan balik menjadi instan (<1s umpan balik untuk TDD, sebagai contoh)
  • Tes dapat dijalankan secara paralel untuk sistem build / deploy
  • Tes tidak perlu menjalankan database (membuat build jauh lebih mudah, atau setidaknya lebih cepat)

Tes unit adalah cara untuk memeriksa pekerjaan Anda. Mereka harus menguraikan semua skenario untuk metode yang diberikan, yang biasanya berarti semua jalur yang berbeda melalui metode. Ini adalah spesifikasi yang Anda bangun, mirip dengan pembukuan entri ganda.

Apa yang Anda gambarkan adalah tipe lain dari tes otomatis: tes integrasi. Meskipun mereka juga sangat penting, idealnya Anda akan memiliki lebih sedikit dari mereka. Mereka harus memverifikasi bahwa sekelompok unit saling berintegrasi dengan baik.

Jadi, bagaimana Anda menguji berbagai hal dengan akses database? Semua kode akses data Anda harus berada dalam lapisan tertentu, sehingga kode aplikasi Anda dapat berinteraksi dengan layanan yang dapat diolok-olok alih-alih database yang sebenarnya. Seharusnya tidak peduli apakah layanan tersebut didukung oleh semua jenis database SQL, data uji dalam memori, atau bahkan data layanan web jarak jauh. Itu bukan urusan mereka.

Idealnya (dan ini sangat subjektif), Anda ingin sebagian besar kode Anda dicakup oleh unit test. Ini memberi Anda keyakinan bahwa setiap karya bekerja secara independen. Setelah potongan-potongan dibangun, Anda harus menyatukannya. Contoh - ketika saya hash kata sandi pengguna, saya harus mendapatkan output yang tepat ini.

Katakanlah setiap komponen terdiri dari sekitar 5 kelas - Anda ingin menguji semua titik kegagalan di dalamnya. Ini sama dengan tes yang jauh lebih sedikit hanya untuk memastikan semuanya terhubung dengan benar. Contoh - uji Anda dapat menemukan pengguna dari database yang diberikan nama pengguna / kata sandi.

Akhirnya, Anda ingin beberapa tes penerimaan benar-benar memastikan Anda memenuhi tujuan bisnis. Bahkan ada lebih sedikit lagi; mereka dapat memastikan aplikasi berjalan dan melakukan apa yang dibangun untuk dilakukan. Contoh - mengingat data tes ini, saya harus bisa login.

Pikirkan ketiga jenis tes ini sebagai piramida. Anda memerlukan banyak unit test untuk mendukung semuanya, dan kemudian Anda bekerja dengan cara Anda dari sana.

Adrian Schneider
sumber
1

The Metode Template Pola mungkin membantu.

Anda membungkus panggilan ke database dalam protectedmetode. Untuk menguji kelas ini Anda benar-benar menguji objek palsu yang mewarisi dari kelas koneksi database nyata dan menimpa metode yang dilindungi.

Dengan cara ini panggilan sebenarnya ke database tidak pernah di bawah unit test, itu benar. Tapi ini hanya beberapa baris kode. Dan itu bisa diterima.

TobiMcNamobi
sumber
1
Jika Anda bertanya-tanya mengapa saya menjawab pertanyaan saya sendiri: Ya, ini bisa menjadi jawaban tetapi saya tidak yakin apakah itu yang benar.
TobiMcNamobi
-1

Pengujian dengan data eksternal adalah tes integrasi. Uji unit berarti Anda hanya menguji unit. Sebagian besar dilakukan dengan logika bisnis Anda. Untuk membuat unit kode Anda dapat diuji, Anda harus mengikuti beberapa pedoman, seperti Anda harus membuat unit Anda independen dari bagian lain dari kode Anda. Selama pengujian unit jika Anda membutuhkan data maka Anda harus secara paksa menyuntikkan data tersebut dengan injeksi ketergantungan. Ada beberapa kerangka mengejek dan mematikan di luar sana.

DeveloperArnab
sumber