Cara terbaik untuk menguji kueri SQL [closed]

109

Saya mengalami masalah di mana kami terus mengalami kueri SQL yang kompleks dengan kesalahan. Pada dasarnya ini menghasilkan pengiriman email ke pelanggan yang salah dan 'masalah' lain seperti itu.

Apa pengalaman semua orang dengan membuat kueri SQL seperti itu? Kami membuat kelompok data baru setiap minggu.

Jadi inilah beberapa pemikiran saya dan batasannya:

  • Membuat data uji Meskipun ini akan membuktikan bahwa kami memiliki semua data yang benar, hal ini tidak memaksakan pengecualian anomali dalam produksi. Itu adalah data yang akan dianggap salah hari ini tetapi mungkin benar 10 tahun yang lalu; itu tidak didokumentasikan dan oleh karena itu kami hanya mengetahuinya setelah data diekstraksi.

  • Membuat diagram Venn dan peta data Ini tampaknya menjadi cara yang solid untuk menguji desain kueri, namun tidak menjamin bahwa penerapannya benar. Ini membuat pengembang merencanakan ke depan dan memikirkan apa yang terjadi saat mereka menulis.

Terima kasih atas masukan yang dapat Anda berikan untuk masalah saya.

Bluephlame
sumber

Jawaban:

164

Anda tidak akan menulis aplikasi dengan fungsi sepanjang 200 baris. Anda akan menguraikan fungsi yang panjang itu menjadi fungsi yang lebih kecil, masing-masing dengan satu tanggung jawab yang jelas.

Mengapa menulis SQL Anda seperti itu?

Dekomposisi kueri Anda, sama seperti Anda menguraikan fungsi Anda. Ini membuatnya lebih pendek, sederhana, lebih mudah dipahami, lebih mudah diuji , lebih mudah difaktor ulang. Dan ini memungkinkan Anda untuk menambahkan "shims" di antara mereka, dan "pembungkus" di sekitarnya, seperti yang Anda lakukan dalam kode prosedural.

Bagaimana kamu melakukan ini? Dengan membuat setiap hal penting yang dilakukan kueri menjadi tampilan. Kemudian Anda membuat kueri yang lebih kompleks dari tampilan yang lebih sederhana ini, sama seperti Anda membuat fungsi yang lebih kompleks dari fungsi yang lebih primitif.

Dan yang hebat adalah, untuk sebagian besar komposisi tampilan, Anda akan mendapatkan kinerja yang persis sama dari RDBMS Anda. (Untuk beberapa Anda tidak akan; lalu kenapa? Pengoptimalan prematur adalah akar dari semua kejahatan. Buat kode dengan benar terlebih dahulu, kemudian optimalkan jika perlu.)

Berikut adalah contoh penggunaan beberapa tampilan untuk menguraikan kueri yang rumit.

Dalam contoh, karena setiap tampilan hanya menambahkan satu transformasi, masing-masing dapat diuji secara independen untuk menemukan kesalahan, dan pengujiannya sederhana.

Berikut tabel dasar pada contoh:

create table month_value( 
    eid int not null, month int, year int,  value int );

Tabel ini cacat, karena menggunakan dua kolom, bulan dan tahun, untuk mewakili satu datum, satu bulan absolut. Berikut spesifikasi kami untuk kolom terhitung baru:

Kita akan melakukannya sebagai transformasi linier, sehingga mengurutkan sama seperti (tahun, bulan), dan untuk setiap tupel (tahun, bulan) hanya ada satu nilai, dan semua nilai berurutan:

create view cm_absolute_month as 
select *, year * 12 + month as absolute_month from month_value;

Sekarang apa yang harus kita uji melekat pada spesifikasi kita, yaitu untuk setiap tuple (tahun, bulan), ada satu dan hanya satu (absolute_month), dan itu (absolute_month) adalah berurutan. Mari kita tulis beberapa tes.

Pengujian kami akan menjadi selectkueri SQL , dengan struktur berikut: nama pengujian dan pernyataan kasus yang dikategorikan bersama. Nama pengujian hanyalah string arbitrer. Pernyataan kasus hanyalah case whenpernyataan uji then 'passed' else 'failed' end.

Pernyataan pengujian hanya akan berupa pemilihan SQL (subkueri) yang harus benar agar pengujian dapat lulus.

Inilah tes pertama kami:

--a select statement that catenates the test name and the case statement
select concat( 
-- the test name
'For every (year, month) there is one and only one (absolute_month): ', 
-- the case statement
   case when 
-- one or more subqueries
-- in this case, an expected value and an actual value 
-- that must be equal for the test to pass
  ( select count(distinct year, month) from month_value) 
  --expected value,
  = ( select count(distinct absolute_month) from cm_absolute_month)  
  -- actual value
  -- the then and else branches of the case statement
  then 'passed' else 'failed' end
  -- close the concat function and terminate the query 
  ); 
  -- test result.

Menjalankan kueri itu menghasilkan hasil ini: For every (year, month) there is one and only one (absolute_month): passed

Selama ada cukup data pengujian di month_value, pengujian ini berfungsi.

Kami juga dapat menambahkan pengujian untuk data pengujian yang memadai:

select concat( 'Sufficient and sufficiently varied month_value test data: ',
   case when 
      ( select count(distinct year, month) from month_value) > 10
  and ( select count(distinct year) from month_value) > 3
  and ... more tests 
  then 'passed' else 'failed' end );

Sekarang mari kita uji berturut-turut:

select concat( '(absolute_month)s are consecutive: ',
case when ( select count(*) from cm_absolute_month a join cm_absolute_month b 
on (     (a.month + 1 = b.month and a.year = b.year) 
      or (a.month = 12 and b.month = 1 and a.year + 1 = b.year) )  
where a.absolute_month + 1 <> b.absolute_month ) = 0 
then 'passed' else 'failed' end );

Sekarang mari kita letakkan pengujian kita, yang hanya berupa kueri, ke dalam file, dan menjalankan skrip tersebut pada database. Memang, jika kita menyimpan definisi tampilan dalam skrip (atau skrip, saya merekomendasikan satu file per tampilan terkait) untuk dijalankan terhadap database, kita dapat menambahkan pengujian untuk setiap tampilan ke skrip yang sama , sehingga tindakan (re -) membuat tampilan kita juga menjalankan pengujian tampilan. Dengan begitu, kita berdua mendapatkan pengujian regresi saat membuat ulang tampilan, dan saat pembuatan tampilan bertentangan dengan produksi, tampilan tersebut juga akan diuji dalam produksi.

tpdi
sumber
27
Ini adalah pertama kalinya saya melihat kode bersih dan pengujian unit di sql, saya senang untuk hari itu :)
Maxime ARNSTAMM
1
hack sql yang luar biasa
CodeFarmer
13
Ini bagus, tetapi mengapa menggunakan satu nama huruf untuk kolom dan nama tampilan yang hampir tidak terbaca? Mengapa SQL harus lebih sedikit mendokumentasikan diri atau terbaca daripada Python?
snl
1
Penjelasan luar biasa untuk sesuatu yang bermanfaat yang tidak pernah saya lihat di dunia SQL / DB. Saya juga suka cara Anda menguji database di sini.
Jackstine
Sebagai peringatan, saya telah melihat tampilan sql yang bergabung dengan tampilan sql berkinerja sangat buruk di PostgreSQL. Saya telah menggunakan teknik ini secara efektif dengan M $ SQL.
Ben Liyanage
6

Buat database sistem pengujian yang dapat Anda muat ulang sesering yang Anda inginkan. Muat data Anda atau buat data Anda dan simpan. Menghasilkan cara mudah untuk memuatnya kembali. Lampirkan sistem pengembangan Anda ke database tersebut dan validasi kode Anda sebelum Anda pergi ke produksi. Tendang diri Anda sendiri setiap kali Anda berhasil membiarkan masalah masuk ke produksi. Buat rangkaian pengujian untuk memverifikasi masalah umum dan mengembangkan rangkaian pengujian Anda dari waktu ke waktu.

ojblass
sumber
4

Anda mungkin ingin memeriksa DbUnit , jadi Anda dapat mencoba menulis pengujian unit untuk program Anda dengan kumpulan data tetap. Dengan cara itu, Anda harus dapat menulis kueri dengan hasil yang lebih atau kurang dapat diprediksi.

Hal lain yang mungkin ingin Anda lakukan adalah membuat profil tumpukan eksekusi SQL Server Anda dan mencari tahu apakah semua kueri memang benar, misalnya, jika Anda hanya menggunakan satu kueri yang mengembalikan hasil yang benar dan salah, maka jelas kueri tersebut digunakan masih menjadi pertanyaan, tetapi bagaimana jika aplikasi Anda mengirimkan kueri yang berbeda pada titik yang berbeda dalam kode?

Upaya apa pun untuk memperbaiki kueri Anda akan sia-sia ... kueri nakal mungkin masih menghasilkan hasil yang salah.

Jon Limjap
sumber
2

Re: tpdi

case when ( select count(*) from cm_abs_month a join cm_abs_month b  
on (( a.m + 1 = b.m and a.y = b.y) or (a.m = 12 and b.m = 1 and a.y + 1 = b.y) )   
where a.am + 1 <> b.am ) = 0  

Perhatikan bahwa ini hanya memeriksa bahwa nilai am untuk bulan berturut-turut akan berturut-turut, bukan data yang berurutan (yang mungkin seperti yang Anda maksudkan pada awalnya). Ini akan selalu berlalu jika tidak ada data sumber Anda yang berurutan (misalnya Anda hanya memiliki bulan genap), meskipun perhitungan am Anda benar-benar tidak aktif.

Juga, apakah saya melewatkan sesuatu, atau apakah paruh kedua klausa ON itu menabrak nilai bulan yang salah? (yaitu memeriksa bahwa 12/2011 datang setelah 1/2010)

Yang lebih buruk, jika saya ingat dengan benar, SQL Server setidaknya memungkinkan Anda kurang dari 10 level tampilan sebelum pengoptimal melemparkan tangan virtualnya ke udara dan mulai melakukan pemindaian tabel penuh pada setiap permintaan, jadi jangan lakukan pendekatan ini secara berlebihan.

Ingatlah untuk menguji coba test case Anda!

Jika tidak, membuat sekumpulan data yang sangat luas untuk mencakup sebagian besar atau semua bentuk input yang mungkin, menggunakan SqlUnit atau DbUnit atau unit * lainnya untuk mengotomatiskan pemeriksaan hasil yang diharapkan terhadap data tersebut, dan meninjau, memelihara, dan memperbaruinya seperlunya secara umum tampaknya jalan untuk pergi.

Mars the Infomage
sumber