FYI: Artikel ini adalah versi diperluas dari laporan saya tentang SQA Days # 25.Berdasarkan pengalaman saya berkomunikasi dengan kolega, saya dapat mengatakan bahwa pengujian kode dalam database bukanlah praktik yang umum. Ini bisa menjadi bahaya potensial. Logika dalam database ditulis oleh orang yang sama yang menulis kode "biasa". Akibatnya, kesalahan juga dapat terjadi di sana, dan mereka juga dapat menimbulkan konsekuensi negatif bagi produk, bisnis, dan konsumen. Tidak masalah apakah ini tentang prosedur tersimpan yang membantu backend, atau ETL yang mengubah data menjadi penyimpanan - ada risiko, dan pengujian dapat secara signifikan menguranginya. Saya ingin memberi tahu Anda apa itu tSQLt dan bagaimana ini membantu kami dalam menguji kode dalam SQL Server.
Konteks
Ada gudang besar di SQL Server yang berisi berbagai data penelitian klinis. Itu diisi dari berbagai sumber (terutama database berorientasi dokumen). Di dalam server itu sendiri, data berulang kali dikonversi menggunakan ETL. Di masa depan, data ini dapat diunggah ke database yang lebih kecil untuk digunakan oleh aplikasi web yang memecahkan beberapa masalah kecil tertentu. Beberapa pelanggan pelanggan juga meminta API untuk kebutuhan internal mereka. Dalam implementasi API semacam itu, prosedur dan kueri tersimpan sering digunakan.
Secara umum, kode berada di sisi DBMS secara berurutan.
Kenapa semua ini dibutuhkan
Seperti yang sudah dipahami dari pendahuluan, kode dalam database adalah kode yang sama
aplikasi, seperti yang lain, dan mungkin juga ada kesalahan.
Saya pikir banyak orang menyadari ketergantungan harga bug pada saat penemuannya, penemuan yang biasanya dikaitkan dengan Barry Bohem. Kesalahan yang dibuat pada tahap awal dan terdeteksi pada tahap selanjutnya bisa lebih mahal karena harus melalui banyak tahap (pengkodean, unit, integrasi, pengujian sistem, dll.) Berulang kali baik untuk melokalisasi kesalahan dan untuk membawa kode yang diperbaiki kembali ke tahap di mana masalah itu diidentifikasi. Efek ini juga relevan untuk kasus gudang. Jika kesalahan merambah ke beberapa ETL, dan data mengalami beberapa transformasi, maka jika kesalahan terdeteksi dalam data, Anda harus:
- Telusuri semua langkah konversi kembali untuk melokalisasi masalah.
- Perbaiki masalahnya.
- Dapatkan kembali data yang dikoreksi (koreksi manual tidak dikecualikan).
- Verifikasi bahwa data yang salah disebabkan oleh kesalahan tidak muncul di tempat lain.
Jangan lupa bahwa kami tidak menjual mainan lunak. Kesalahan dalam bidang seperti penelitian klinis dapat menyebabkan kerugian tidak hanya untuk bisnis, tetapi juga bagi kesehatan masyarakat.
Bagaimana cara menguji?
Karena kita berbicara tentang pengujian kode, yang kami maksud adalah pengujian unit dan integrasi. Hal-hal ini sangat latihan dan melibatkan regresi konstan. Sebenarnya, tidak ada yang melakukan pengujian secara manual (well, mungkin dengan pengecualian beberapa kasus yang benar-benar merosot).
Bonus yang bagus: tes bisa menjadi bahan tambahan saat mendokumentasikan kode. Omong-omong, persyaratan pelanggan mungkin terlihat seperti ini (dapat diklik):
Excel, dua kolom dengan persyaratan + informasi pendukung yang tersebar di kolom lain + markah yang tidak jelas, yang lebih membingungkan daripada membantu. Jika perlu, mengembalikan keinginan semula mungkin sulit. Tes dapat membantu menangkap nuansa implementasi dengan lebih akurat (tentu saja, Anda tidak boleh menganggapnya setara dengan dokumentasi).
Sayangnya, dengan kompleksitas kode, kompleksitas tes tumbuh, dan efek ini dapat diratakan.
Tes dapat berfungsi sebagai lapisan keamanan tambahan terhadap walrus spontan. Tes mandiri pada CI karena formalisme membantu mengatasi masalah ini.
Jika pilihan kita jatuh pada jalur otomatisasi, maka kita perlu memutuskan alat untuk implementasinya.
Bagaimana cara menguji?
Dalam kasus pengujian kode dalam database, saya membedakan dua pendekatan: SQL-powered, yaitu, berfungsi langsung dalam DBMS, dan Non-SQL-powered. Saya dapat menyoroti nuansa berikut:
Di SQL Server, kami memiliki beberapa pilihan:
Evaluasi "baik-buruk" bersifat subyektif, maaf, tanpa ini, tidak ada tempat.
Penjelasan: "Penampilan pertama" adalah tanggal paling awal dalam jalur kehidupan kerangka kerja yang berhasil saya temukan, yaitu, pelepasan atau komitmen paling awal.
Anda mungkin memperhatikan bahwa alternatif bertenaga SQL telah ditinggalkan selama beberapa waktu, dan tSQLt adalah satu-satunya opsi yang didukung. Secara fungsional, tSQLt juga menang. Satu-satunya hal adalah bahwa dalam hal pernyataan, TST menawarkan pilihan yang sedikit lebih kaya daripada tSQLt, yang, bagaimanapun, tidak mungkin lebih besar daripada yang kontra.
Ada nuansa dalam dokumentasi tSQLt, tetapi saya akan membicarakannya nanti.
Di dunia non-SQL-powered, hal-hal tidak begitu mudah. Alternatif, meskipun tidak super aktif, sedang dikembangkan. DbFit adalah alat yang menarik berdasarkan kerangka kerja FitNesse. Ia menawarkan tes menulis pada marka wiki. Slacker juga merupakan hal yang aneh - pendekatan BDD saat menulis tes untuk database.
Saya akan membahas Pernyataan di non-SQL-powered. Dari luar, jumlah mereka lebih sedikit, dan orang bisa mengatakan bahwa mereka lebih buruk karena hal ini. Tapi di sini ada baiknya mengingat bahwa mereka secara fundamental berbeda dari tSQLt. Tidak semuanya begitu sederhana.
Baris terakhir adalah "NUnit, dll." - Ini lebih sebagai pengingat. Banyak kerangka kerja unit-pengujian yang dikenal dalam pekerjaan sehari-hari dapat digunakan pada database bantu menggunakan perpustakaan bantu. Tabel memiliki banyak N / A, karena baris ini, pada kenyataannya, mencakup banyak alat. "Nuansa" di kolom pernyataan berasal dari titik yang sama - dalam alat yang berbeda, perangkat mereka dapat bervariasi, dan pertanyaan penerapan ke database terbuka.
Sebagai metrik lain yang menarik kita dapat mempertimbangkan
tren Google .
Nuansa:
- Saya tidak memasukkan Slacker, karena nama ini dapat berarti banyak hal (dan permintaan seperti "Kerangka kerja slacker" tidak terlalu terlihat di grafik).
- Demi rasa ingin tahu, tren TST ditambahkan, tetapi juga tidak banyak mencerminkan keadaan, karena itu adalah singkatan yang berarti banyak hal yang berbeda.
- Saya tidak memasukkan NUnit dan analognya, karena ini awalnya adalah kerangka kerja untuk menguji kode aplikasi itu sendiri, dan tren mereka tidak menunjukkan konteks kita.
Secara umum, kita dapat mengatakan bahwa tSQLt terlihat bagus dengan latar belakang analog.
Apa itu tSQLt?
tSQLt, seperti yang Anda duga, adalah kerangka kerja pengujian unit bertenaga SQL.
β
Situs resmiDukungan SQL Server telah diumumkan sejak 2005 SP2. Saya tidak pernah bisa melihat sejauh ini ke masa lalu, tapi kami memiliki 2012 di server dev, saya punya lokal 2017 - tidak ada masalah.
Sumber terbuka, lisensi Apache 2.0,
tersedia di GitHub . Anda dapat melakukan fork, menyelundupkan, menggunakan secara gratis dalam proyek-proyek komersial dan, yang paling penting, tidak perlu takut akan bookmark di CLR.
Mekanik kerja
Kasus uji adalah prosedur tersimpan. Mereka digabungkan menjadi kelas-kelas tes (test suite dalam hal xUnit).
Kelas tes tidak lebih dari skema database biasa. Mereka berbeda dari skema lain dengan pendaftaran dalam tabel kerangka kerja. Anda dapat melakukan pendaftaran tersebut dengan memanggil satu prosedur - tSQLt.NewTestClass.
Di dalam kelas uji, dimungkinkan juga untuk menentukan prosedur Penyetelan yang akan dijalankan sebelum setiap kasus uji dijalankan.
Prosedur teardown untuk mengembalikan sistem setelah selesainya uji kasus tidak diperlukan. Setiap test case bersama dengan prosedur SetUp dilakukan dalam transaksi terpisah, yang dibatalkan setelah mengumpulkan hasilnya. Ini sangat nyaman, tetapi memiliki beberapa efek negatif, yang akan saya bahas di bawah ini.
Kerangka kerja ini memungkinkan Anda untuk menjalankan masing-masing kotak uji, seluruh kelas uji, atau semua kelas uji terdaftar sekaligus.
Fitur dengan contoh
Tidak memiliki keinginan untuk menceritakan kembali panduan resmi yang sudah sederhana, saya akan berbicara tentang kemungkinan kerangka kerja menggunakan contoh.
Penafian:- contoh disederhanakan;
- dalam aslinya, tidak semua kode tes ditulis oleh saya, itu lebih merupakan buah dari kreativitas kolektif;
- Contoh 2 diciptakan untuk lebih mendemonstrasikan kemampuan tSQLt.
Contoh 1: CsvSql
Atas permintaan salah satu pelanggan pelanggan, berikut ini diterapkan. Basis data di bidang Nvarchar (MAX) menyimpan kueri SQL. Untuk melihat pertanyaan ini, frontend minimal dilaut. Set hasil yang dikembalikan oleh permintaan ini digunakan oleh backend untuk menghasilkan file CSV untuk dikembalikan melalui panggilan API.
Set hasil cukup berat dan mengandung banyak kolom. Contoh kondisional dari rangkaian hasil seperti itu:
Kumpulan hasil ini adalah beberapa data uji klinis. Mari kita lihat lebih dekat bagaimana ClinicsNum dipertimbangkan - jumlah klinik yang terlibat dalam penelitian ini. Kami memiliki dua tabel: [Uji Coba] dan [Klinik]:
Ada FK: [Klinik]. [TrialID] -> [Trial]. [TrialID]. Jelas, untuk menghitung jumlah klinik, kita hanya perlu COUNT biasa (*).
SELECT COUNT(*), ... FROM dbo.Trial LEFT JOIN dbo.Clinic ON Trial.ID = Clinic.TrialID WHERE Trial.Name = @trialName GROUP BY ...
Bagaimana kami menguji permintaan semacam itu? Untuk memulainya, kita dapat menggunakan rintisan yang mudah digunakan - FakeTable, yang akan sangat menyederhanakan pekerjaan lebih lanjut.
EXEC tSQLt.FakeTable 'dbo.Trial'; EXEC tSQLt.FakeTable 'dbo.Clinic';
FakeTable melakukan hal yang sederhana - mengganti nama tabel lama dan membuat yang baru di tempatnya. Nama yang sama, kolom yang sama, tetapi tanpa constraint'ov dan trigger'ov.
Mengapa kita membutuhkan ini:
- Mungkin ada beberapa data dalam database uji yang dapat mengganggu tes. Berkat FakeTable, kami tidak bergantung pada mereka.
- Untuk tes, sebagai aturan, Anda hanya perlu mengisi beberapa kolom. Mungkin ada banyak dari mereka dalam tabel, dan beberapa dari mereka akan memiliki kendala. Dengan cara ini, kami menyederhanakan pemasangan lebih lanjut dari data uji - kami hanya akan memasukkan yang benar-benar diperlukan untuk kasus uji.
- Kami pasti tidak akan memengaruhi pemicu apa pun saat memasukkan data uji dan kami tidak perlu khawatir tentang efek pasca yang tidak diinginkan.
Selanjutnya, masukkan data uji yang kami butuhkan:
INSERT INTO dbo.Trial ([ID], [Name]) VALUES (1, 'Valerian'); INSERT INTO dbo.Clinic ([ID], [TrialID], [Name]) VALUES (1, 1, 'Clinic1'), (2, 1, 'Clinic2');
Kami mendapatkan kueri kami dari database, membuat tabel Aktual dan mengisinya dengan hasil yang ditetapkan dari kueri kami:
DECLARE @sqlStatement NVARCHAR(MAX) = (SELECT⦠CREATE TABLE actual ([TrialID], ...); INSERT INTO actual EXEC sp_executesql @sqlStatement, ...
Isi Diharapkan - nilai yang diharapkan:
CREATE TABLE expected ( ClinicsNum INT ); INSERT INTO expected SELECT 2
Saya ingin menarik perhatian Anda pada fakta bahwa dalam tabel yang diharapkan kami hanya memiliki satu kolom, sementara di Actual kami memiliki set lengkap.
Ini karena fitur prosedur AssertEqualsTable, yang akan kita gunakan untuk memeriksa nilainya.
EXEC tSQLt.AssertEqualsTable 'expected', 'actual', 'incorrect number of clinics';
Ini membandingkan hanya kolom yang ada di kedua tabel yang dibandingkan. Ini sangat nyaman dalam kasus kami, karena kueri pengujian mengembalikan banyak kolom, yang masing-masing memiliki logikanya sendiri, kadang-kadang sangat membingungkan. Kami tidak ingin mengembang kasus uji, jadi fitur ini sangat berguna bagi kami. Tentu saja, ini adalah pedang bermata dua. Jika dalam Aktual satu set kolom diisi secara otomatis melalui SELECT TOP 0 dan pada suatu titik kolom tambahan tiba-tiba muncul, maka test case semacam itu tidak akan menangkap momen ini. Untuk menangani situasi seperti itu, Anda perlu melakukan pemeriksaan tambahan.
Suster prosedur AssertEqualsTable
Perlu disebutkan bahwa tSQLt berisi dua prosedur yang mirip dengan AssertEqualsTable. Ini adalah AssertEqualsTableSchema dan AssertResultSetsHaveSameMetaData. Yang pertama melakukan hal yang sama dengan AssertEqualsTable, tetapi pada tabel metadata. Yang kedua membuat perbandingan yang serupa, tetapi pada metadata dari set hasil.
Contoh 2: Kendala
Pada contoh sebelumnya, kami melihat bagaimana Anda dapat menghapus constraint'y. Tetapi bagaimana jika kita perlu memeriksanya? Secara teknis, ini juga merupakan bagian dari logika, dan juga dapat dianggap sebagai kandidat untuk cakupan tes.
Pertimbangkan situasi dari contoh sebelumnya. Dua tabel - [Uji Coba] dan [Klinik]; [TrialID] FK:
Mari kita coba menulis test case untuk menguji batasan ini. Pada awalnya, seperti terakhir kali, kami memalsukan tabel.
EXEC tSQLt.FakeTable '[dbo].[Trial]' EXEC tSQLt.FakeTable '[dbo].[Clinic]'
Tujuannya sama - untuk menyingkirkan pembatasan yang tidak perlu. Kami ingin pemeriksaan terisolasi tanpa gerakan yang tidak perlu.
Selanjutnya, kita mengembalikan batasan yang kita butuhkan ke tempat menggunakan prosedur ApplyConstraint:
EXEC tSQLt.ApplyConstraint '[dbo].[Clinic]', 'Trial_FK';
Di sini kami telah menyusun konfigurasi yang mudah untuk verifikasi langsung. Pemeriksaan itu sendiri akan terdiri dari fakta bahwa upaya untuk memasukkan data pasti akan menyebabkan pengecualian. Agar case test berfungsi dengan benar, kita perlu menangkap pengecualian ini. Penangan pengecualian ExpectException akan membantu kami dalam hal ini.
EXEC tSQLt.ExpectException @ExpectedMessage = 'The INSERT statement conflicted...', @ExpectedSeverity = 16, @ExpectedState = 0;
Setelah menginstal handler, Anda dapat mencoba memasukkan yang tidak dapat dimasukkan.
INSERT INTO [dbo].[Clinic] ([TrialID]) VALUES (1)
Pengecualian tertangkap. Tes lulus.
Prosedur saudara Terapkan Kendala
Pengembang TSQLt menawarkan kepada kami pendekatan serupa untuk menguji pemicu. Anda bisa menggunakan prosedur ApplyTrigger untuk mengembalikan pemicu ke tabel palsu. Selanjutnya, semuanya seperti pada contoh di atas - kami memicu pelatuk, periksa hasilnya.
ExpectNoException - antonim dari ExpectException
Untuk kasus di mana pengecualian seharusnya tidak terjadi, ada prosedur ExpectNoException. Ia bekerja dengan cara yang sama seperti ExpectException, kecuali bahwa tes dianggap gagal jika pengecualian terjadi.
Contoh 3: semaphore
Situasinya adalah sebagai berikut. Ada sejumlah prosedur tersimpan dan layanan windows. Awal dari eksekusi mereka dapat disebabkan oleh berbagai peristiwa eksternal. Dalam hal ini, urutan eksekusi yang diizinkan diperbaiki. Semaphore digunakan untuk membedakan akses ke tabel database. Ini adalah sekelompok prosedur tersimpan.
Misalnya, pertimbangkan salah satu dari prosedur ini. Kami memiliki dua tabel:
Tabel [Proses] berisi daftar proses yang diizinkan untuk dieksekusi, [ProcStatus] - daftar status proses ini.
Apa yang dilakukan prosedur kami? Saat dipanggil, serangkaian pemeriksaan pertama kali terjadi:
- Nama proses untuk memulai, diteruskan dalam parameter prosedur, terlihat di bidang [Nama] pada tabel [Proses].
- Jika nama proses ditemukan, maka diperiksa apakah saat ini memungkinkan untuk memulainya - bendera [IsRunable] dari tabel [Proses].
- Jika ternyata prosesnya dapat diterima untuk dieksekusi, maka masih harus memastikan bahwa itu belum berjalan. Dalam tabel [ProcStatus], tidak ada catatan tentang proses ini dengan status = 'InProg' diperiksa.
Jika semuanya OK, maka catatan baru tentang proses ini dengan status 'InProg' ditambahkan ke ProcStatus (ini dianggap sebagai peluncuran), ID dari catatan ini dikembalikan dengan parameter ProcStatusId. Jika ada verifikasi yang gagal, maka kami mengharapkan yang berikut:
- Surat dikirim ke administrator sistem.
- Pengembalian ProcStatusId = -1.
- Entri baru di [ProcStatus] tidak ditambahkan.
Mari kita menulis test case untuk menguji case ketika prosesnya tidak ada dalam daftar yang dapat diterima.
Untuk kenyamanan, segera terapkan FakeTable. Ini tidak begitu penting secara fundamental, tetapi dapat bermanfaat:
- Kami dijamin untuk menyingkirkan data apa pun yang dapat mengganggu pelaksanaan uji kasus yang benar.
- Kami akan menyederhanakan verifikasi lebih lanjut dari entri yang hilang di ProcStatus.
EXEC tSQLt.FakeTable 'dbo.Process'; EXEC tSQLt.FakeTable 'dbo.ProcStatus';
Untuk mengirim pesan, prosedur [SendEmail] yang ditulis oleh programmer kami digunakan. Untuk memeriksa pengiriman surat kepada administrator, kami perlu menerima teleponnya. Untuk kasus ini, tSQLt menawarkan kita untuk menggunakan SpyProcedure.
EXEC tSQLt.SpyProcedure 'dbo.SendEmail'
SpyProcedure melakukan hal berikut:
- Membuat tabel bentuk [dbo]. [SendEmail_SpyProcedureLog].
- Seperti FakeTable, ini menggantikan prosedur asli dengan prosedurnya sendiri, dengan nama yang sama, tetapi mengandung logika logging. Jika diinginkan, Anda dapat menambahkan logika Anda sendiri.
Seperti yang Anda tebak, pencatatan terjadi di tabel [dbo]. [SendEmail_SpyProcedureLog]. Tabel ini berisi kolom [_ID_] - nomor urut panggilan prosedur. Kolom-kolom berikutnya memuat nama-nama parameter yang diteruskan ke prosedur, dan nilai-nilai yang dikirimkan dalam panggilan dikumpulkan di dalamnya. Jika perlu, mereka juga dapat diperiksa.
Hal terakhir yang perlu kita lakukan sebelum memanggil semaphore dan memeriksa adalah membuat variabel di mana kita akan meletakkan ID rekaman dari tabel [ProcStatus] (lebih tepatnya, -1, karena catatan tidak akan ditambahkan).
DECLARE @ProcStatusId BIGINT;
Panggil semafor:
EXEC dbo.[Semaphore_JobStarter] 'SomeProcess', @ProcStatusId OUTPUT;
Itu saja, sekarang kami memiliki semua data yang diperlukan untuk verifikasi. Mari kita mulai dengan memeriksa pengiriman.
surat:
IF NOT EXISTS ( SELECT * FROM dbo.SendEmail_SpyProcedureLog) EXEC tSQLt.Fail 'SendEmail has not been run.';
Dalam hal ini, kami memutuskan untuk tidak memeriksa parameter yang dikirimkan selama pengiriman, tetapi cukup memeriksa fakta itu sendiri. Saya menarik perhatian Anda pada prosedur tSQLt.Fail. Ini memungkinkan Anda untuk "secara resmi" gagal dalam ujian. Jika Anda perlu membangun beberapa konstruksi spesifik, maka tSQLt.Fail akan memungkinkan Anda untuk melakukan ini.
Selanjutnya, periksa tidak adanya entri di [dbo]. [ProcStatus]:
EXEC tSQLt.AssertEmptyTable 'dbo.ProcStatus';
Di sinilah FakeTable yang kami terapkan di awal membantu kami. Berkat dia, kita bisa mengharapkan kekosongan. Tanpa itu, untuk verifikasi yang akurat, kita harus, dengan cara yang baik, membandingkan jumlah catatan sebelum dan sesudah semaphore.
Equality ProcStatusId = -1 kita dapat dengan mudah memeriksa dengan AssertEquals:
EXEC tSQLt.AssertEquals -1, @ProcStatusId, 'Wrong ProcStatusId.';
AssertEquals adalah minimalis - hanya membandingkan dua nilai, tidak ada yang supernatural.
Prosedur Pertanggungjawaban Saudara yang Setara
Untuk membandingkan nilai-nilai tersebut, kami diberikan sejumlah prosedur:
- AssertEquals
- AssertNotEquals
- AssertEqualsString
- Tegas
Saya pikir nama mereka berbicara sendiri. Satu-satunya hal yang perlu diperhatikan adalah keberadaan prosedur AssertEqualsString yang terpisah. Masalahnya adalah bahwa AssertEquals / AssertNotEquals / AssertLike bekerja dengan SQL_VARIANT, dan NVARCHAR (MAX) tidak berlaku untuk itu, dan karena itu pengembang tSQLt harus mengalokasikan prosedur terpisah untuk pengujian NVARCHAR (MAX).
Fungsi palsu
FakeFunction dengan beberapa regangan dapat disebut prosedur yang mirip dengan SpyProcedure. Palsu ini memungkinkan Anda untuk mengganti fungsi apa pun dengan yang lebih sederhana yang diperlukan. Karena fungsi-fungsi dalam SQL Server bekerja pada prinsip tabung dengan pasta gigi - mereka memberikan hasil melalui "lubang teknologi saja", maka, sayangnya, tidak ada fungsi logging yang disediakan. Hanya pengganti logika.
Perangkap
Perlu dicatat beberapa jebakan yang mungkin Anda temui saat bekerja dengan tSQLt. Dalam hal ini, dengan jebakan yang saya maksud beberapa masalah bermasalah yang lahir karena keterbatasan SQL Server dan / atau yang tidak dapat diselesaikan oleh pengembang kerangka kerja.
Pembatalan / korupsi transaksi
Masalah pertama dan terpenting yang dihadapi tim kami adalah pembatalan transaksi. SQL Server tidak tahu cara mengembalikan transaksi bersarang secara terpisah - hanya semuanya secara keseluruhan, sampai ke yang paling eksternal. Mengingat fakta bahwa tSQLt membungkus setiap test case dalam transaksi terpisah, ini menjadi masalah. Bagaimanapun, rollback transaksi di dalam prosedur pengujian dapat merusak pelaksanaan pengujian, menyebabkan kesalahan eksekusi non-deskriptif.
Untuk menghindari masalah ini, kami menggunakan savepoints. Idenya sederhana. Sebelum memulai transaksi dalam prosedur pengujian, kami memeriksa untuk melihat apakah kami sudah berada di dalam transaksi. Jika ternyata ya, maka, dengan asumsi bahwa ini adalah transaksi tSQLt, masukkan savepoint alih-alih memulai. Kemudian, jika perlu, kami akan kembali ke savepoint ini, dan tidak ke awal transaksi. Bersarang seperti itu tidak.
Masalahnya rumit oleh korupsi transaksi. Jika tiba-tiba ada yang tidak beres dan pengecualiannya berhasil, maka transaksi itu bisa menjadi malapetaka. Transaksi semacam itu tidak hanya dapat dilakukan, tetapi juga digulung kembali ke savepoint, hanya digulung kembali semuanya.
Dengan semua hal di atas, Anda harus menerapkan desain berikut:
DECLARE @isNestedTransaction BIT = CASE WHEN @@trancount > 0 THEN 'true' ELSE 'false' END; BEGIN TRY IF @isNestedTransaction = 'false' BEGIN TRANSACTION ELSE SAVE TRANSACTION SavepointName;
Pertimbangkan kode di beberapa bagian. Pertama, kita perlu menentukan apakah kita berada di dalam suatu transaksi:
DECLARE @isNestedTransaction BIT = CASE WHEN @@trancount > 0 THEN 'true' ELSE 'false' END;
Setelah menerima flag @isNestedTransaction, jalankan blok TRY dan setel savepoint atau awal transaksi, tergantung pada situasinya.
BEGIN TRY IF @isNestedTransaction = 'false' BEGIN TRANSACTION ELSE SAVE TRANSACTION SavepointName;
Setelah kami melakukan sesuatu yang bermanfaat, komit, jika ini adalah awal "nyata" dari prosedur.
Tentu saja, jika ini adalah peluncuran dari test case, kami tidak perlu melakukan apa pun. Pada akhir eksekusi, tSQLt hanya akan memutar kembali semua perubahan. Jika tiba-tiba terjadi kesalahan dan kami masuk ke blok CATCH, maka hal pertama yang harus dilakukan adalah mencari tahu apakah transaksi kami bahkan dapat dilakukan.
BEGIN CATCH DECLARE @isCommitable BIT = CASE WHEN XACT_STATE() = 1 THEN 'true' ELSE 'false' END;
Kita bisa kembali ke savepoint hanya jika
- transaksi yang dapat dilakukan
- uji coba berlangsung, yaitu savepoint ada.
Dalam kasus lain, kita perlu memutar kembali seluruh transaksi.
IF @isCommitable = 'true' AND @isNestedTransaction = 'true' ROLLBACK TRANSACTION SavepointName; ELSE ROLLBACK; THROW; END CATCH;
Ya, sayangnya, jika selama uji coba kami mendapat transaksi yang tidak dapat dilakukan, maka kami masih mendapatkan kesalahan dalam pelaksanaan uji kasus.
FakeTable dan masalah dengan kunci Asing
Pertimbangkan tabel yang sudah dikenal [Uji Coba] dan [Klinik]:
Kami ingat [TrialID] FK. Masalah apa yang bisa menyebabkan ini? Dalam contoh yang diberikan sebelumnya, kami menerapkan FakeTable ke kedua tabel sekaligus. Jika kami menerapkannya hanya di [Uji Coba], kami mendapatkan situasi berikut:
Upaya memasukkan entri di [Klinik], dengan cara ini, dapat mengakibatkan kegagalan (bahkan jika kami menyiapkan semua data yang diperlukan dalam versi palsu dari tabel [Percobaan]).
[dbo].[Test_FK_Problem] failed: (Error) The INSERT statement conflicted with the FOREIGN KEY constraint "Trial_Fk". The conflict occurred in database "HabrDemo", table "dbo.tSQLt_tempobject_ba8f36353f7a44f6a9176a7d1db02493", column 'TrialID'.[16,0]{Test_FK_Problem,14}
Kesimpulan: Anda harus memalsukan segalanya, atau tidak memalsukan apa pun. Dalam kasus kedua, jelas bahwa pangkalan harus dipersiapkan sebelumnya untuk pengujian.
SpyProcedure pada prosedur sistem
Sayangnya, memata-matai panggilan ke prosedur sistem akan gagal.
[HabrDemo].[test_test] failed: (Error) Cannot use SpyProcedure on sys.sp_help because the procedure does not exist[16,10] {tSQLt.Private_ValidateProcedureCanBeUsedWithSpyProcedure,7}
Dalam contoh semafor, kami melacak panggilan ke prosedur [SendEmail] yang ditulis oleh pengembang kami. Dalam hal ini, menulis prosedur terpisah adalah karena kebutuhan untuk mengumpulkan dan memproses beberapa informasi tambahan sebelum mengirim pesan secara langsung. Secara umum, seseorang harus siap secara mental untuk fakta bahwa seseorang mungkin harus menulis prosedur interlayer untuk beberapa prosedur sistem semata-mata untuk tujuan pengujian.
Keuntungan
Instalasi cepat
Instalasi berlangsung dalam 2 tahap dan memakan waktu sekitar 2 menit. Anda hanya perlu mengaktifkan CLR di server, jika belum dilakukan, dan jalankan satu skrip. Semuanya, Anda dapat menambahkan kelas tes pertama dan menulis kasus tes.
Perkembangan cepat
tSQLt adalah alat yang mudah dipelajari. Butuh sedikit hari untuk menguasainya. Saya bertanya kepada kolega saya yang bekerja dengan kerangka kerja, dan ternyata semua orang akan menghabiskan sekitar satu hari.
Implementasi cepat dalam CI
Butuh sekitar 2 jam untuk mengatur integrasi CI pada proyek kami. Waktu, tentu saja, dapat bervariasi, tetapi secara umum ini bukan masalah, dan integrasi dapat dilakukan dengan sangat cepat.
Berbagai alat
Ini adalah penilaian subyektif, tetapi, menurut saya, fungsionalitas yang disediakan oleh tSQLt cukup kaya dan mencakup bagian terbesar dari kebutuhan dalam praktik. Untuk kasus yang jarang terjadi ketika tidak ada cukup banyak pemalsuan dan penegasan, tentu saja ada tSQLt.Fail.
Dokumentasi yang mudah
Dokumentasi resmi nyaman dan konsisten. Dengan bantuannya, Anda dapat dengan mudah memahami esensi menggunakan tSQLt dalam waktu singkat, bahkan jika ini adalah alat pengujian unit pertama Anda.
Output yang nyaman
Data dapat diperoleh dalam bentuk teks yang sangat jelas:
[tSQLtDemo].[test_error_messages] failed: (Failure) Expected an error to be raised. [tSQLtDemo].[test_tables_comparison] failed: (Failure) useful and descriptive error message Unexpected/missing resultset rows! |_m_|Column1|Column2| +---+-------+-------+ |< |2 |Value2 | |= |1 |Value1 | |= |3 |Value3 | |> |2 |Value3 | +----------------------+ |Test Execution Summary| +----------------------+ |No|Test Case Name |Dur(ms)|Result | +--+------------------------------------+-------+-------+ |1 |[tSQLtDemo].[test_constraint] | 83|Success| |2 |[tSQLtDemo].[test_trial_view] | 83|Success| |3 |[tSQLtDemo].[test_error_messages] | 127|Failure| |4 |[tSQLtDemo].[test_tables_comparison]| 147|Failure| ----------------------------------------------------------------------------- Msg 50000, Level 16, State 10, Line 1 Test Case Summary: 4 test case(s) executed, 2 succeeded, 2 failed, 0 errored. -----------------------------------------------------------------------------
Anda juga dapat mengekstrak dari basis data (dapat diklik) ...
... atau dapatkan dalam format XML.
<?xml version="1.0" encoding="UTF-8"?> <testsuites> <testsuite id="1" name="tSQLtDemo" tests="3" errors="0" failures="1" timestamp="2019-06-22T16:46:06" time="0.433" hostname="BLAHBLAHBLAH\SQL2017" package="tSQLt"> <properties /> <testcase classname="tSQLtDemo" name="test_constraint" time="0.097" /> <testcase classname="tSQLtDemo" name="test_error_messages" time="0.153"> <failure message="Expected an error to be raised." type="tSQLt.Fail" /> </testcase> <testcase classname="tSQLtDemo" name="test_trial_view" time="0.156" /> <system-out /> <system-err /> </testsuite> </testsuites>
Opsi terakhir memungkinkan Anda untuk dengan mudah mengintegrasikan tes ke dalam CI. Secara khusus, semuanya bekerja untuk kita di bawah Atlassian Bamboo.
Dukungan Redgate
Plus termasuk dukungan untuk penyedia besar alat DBA seperti RedGate. Uji SQL - plugin mereka untuk SQL Server Management Studio - bekerja dengan tSQLt langsung dari kotak. Selain itu, RedGate memberikan bantuan kepada pengembang utama tSQLt dengan lingkungan dev, seperti yang dikatakan oleh pengembang sendiri di
grup Google .
Kekurangan
Tidak ada tabel palsu sementara
tSQLt tidak mengizinkan tabel sementara palsu. Jika perlu, Anda dapat menggunakan
add-on tidak resmi . Sayangnya, add-on ini hanya didukung oleh SQL Server 2016+.
Tidak ada akses ke database eksternal
Ini tidak akan bekerja untuk menjaga basis yang terpisah hanya untuk menyimpan kerangka kerja. tSQLt dirancang untuk menguji apa yang ada dalam database yang sama. Palsu, sayangnya, tidak akan berhasil.
CREATE PROCEDURE [tSQLtDemo].[test_outer_db] AS BEGIN SELECT TOP 10 * FROM [AdventureWorks2017].[Person].[Password] EXEC tSQLt.FakeTable '[AdventureWorks2017].[Person].[Password]' SELECT TOP 10 * FROM [AdventureWorks2017].[Person].[Password] END
Pernyataan tampaknya berfungsi, tetapi tidak ada yang menjamin kinerja mereka, tentu saja.
CREATE PROCEDURE [tSQLtDemo].[test_outer_db_assertions] AS BEGIN SELECT TOP 1 * INTO
Bug dokumentasi
Terlepas dari kenyataan bahwa saya menulis di atas tentang konsistensi dan aksesibilitas dokumentasi, ini juga mengandung masalah. Ada beberapa momen ketinggalan jaman di dalamnya.
Contoh 1.
"Panduan mulai cepat" menyarankan mengunduh kerangka kerja dari SourceForge. Mereka mengucapkan selamat tinggal pada SourceForge
pada tahun 2015 .
Contoh 2.
Panduan ApplyConstraint dalam contoh menggunakan konstruksi berat dengan prosedur Gagal untuk menangkap pengecualian, yang akan lebih mudah dan lebih visual untuk diganti dengan ExpectException.
CREATE PROCEDURE ConstraintTests.[test ReferencingTable_ReferencedTable_FK prevents insert of orphaned rows] AS BEGIN EXEC tSQLt.FakeTable 'dbo.ReferencedTable'; EXEC tSQLt.FakeTable 'dbo.ReferencingTable'; EXEC tSQLt.ApplyConstraint 'dbo.ReferencingTable','ReferencingTable_ReferencedTable_FK'; DECLARE @ErrorMessage NVARCHAR(MAX); SET @ErrorMessage = ''; BEGIN TRY INSERT INTO dbo.ReferencingTable ( id, ReferencedTableId ) VALUES ( 1, 11 ) ; END TRY BEGIN CATCH SET @ErrorMessage = ERROR_MESSAGE(); END CATCH IF @ErrorMessage NOT LIKE '%ReferencingTable_ReferencedTable_FK%' BEGIN EXEC tSQLt.Fail 'Expected error message containing ''ReferencingTable_ReferencedTable_FK'' but got: ''',@ErrorMessage,'''!'; END END GO
Dan ini wajar, karena itu terjadi ...
Pengabaian sebagian
Ada banyak terobosan dalam pengembangan tSQLt dari awal 2016 hingga Juni 2019. Ya, sayangnya, alat ini sebagian ditinggalkan. Pada 2019, sedikit demi sedikit,
dilihat dari GitHub , perkembangannya masih bergerak maju. Meskipun Grup Google resmi
memiliki utas di mana Sebastian, pengembang utama tSQLt, langsung ditanyai tentang nasib pengembangan. Pertanyaan terakhir ditanyakan pada 2 Maret 2019, jawabannya belum diterima.
Masalah dengan SQL Server 2017
Jika Anda menggunakan SQL Server 2017, maka untuk Anda, mungkin menginstal tSQLt akan memerlukan beberapa manipulasi tambahan.
Masalahnya adalah bahwa untuk pertama kalinya sejak 2012, SQL Server membuat perubahan keamanan. Di tingkat server, bendera "Keamanan ketat CLR" ditambahkan, yang melarang pembuatan rakitan yang tidak ditandatangani (bahkan AMAN). Penjelasan terperinci tentang masalah ini layak mendapatkan artikel terpisah (dan, untungnya, semuanya sudah dijelaskan dengan baik dan artikel-artikel berikutnya dalam seri ini). Bersiaplah secara mental untuk itu.Tentu saja, orang dapat mengaitkan kelemahan ini dengan "jebakan", solusi yang tidak bergantung pada pengembang tSQLt, tetapi dimungkinkan untuk menyelesaikan masalah ini di tingkat kerangka kerja, meskipun sedikit memakan waktu. GitHub sudah memiliki masalah , namun, dengan izinnya telah diseret sejak Oktober 2017 (lihat subayat sebelumnya).Alternatif (Β±) untuk DBMS lainnya
Penting juga menyebutkan alternatif untuk DBMS lainnya. tSQLt bukan satu-satunya alat dari jenisnya. Meskipun, karena kekhasan implementasi (CLR, dan T-SQL sangat berbeda dari dialek SQL lainnya), Anda tidak dapat menggunakannya di DBMS lain, Anda masih dapat menemukan alat yang serupa. Saya perhatikan bahwa alternatif ini berbeda secara signifikan dari tSQLt, jadi kami berbicara terutama tentang pendekatan bertenaga SQL secara keseluruhan.Jadi, di bawah PostgreSQL ada ptTAP yang cukup berkembang dan aktif dikembangkan . Ini melibatkan tes menulis dalam PL / pgSQL asli dan mengeluarkan hasilnya dalam format TAP. Di bawah MySQL, ada alat yang serupa, meskipun agak kurang fungsional - MyTAP . Jika Anda beruntung bekerja dengan Oracle, maka Anda memiliki kesempatan untuk menggunakan utPLSQL- Alat pengembangan yang sangat kuat dan aktif (saya bahkan akan mengatakan, lebih dari).Kesimpulan
Mungkin, dengan semua informasi di atas, saya ingin menyampaikan dua pemikiran utama.Yang pertama adalah kegunaan kode pengujian dalam suatu basis data. Apakah Anda duduk di bawah SQL Server, Oracle atau MySQL, itu tidak masalah. Jika Anda memiliki sejumlah logika yang belum diuji disimpan dalam database, maka Anda mengambil risiko tambahan. Bug dalam kode database mampu, seperti bug di sisa kode, menyebabkan kerusakan pada produk dan, sebagai akibatnya, bagi perusahaan yang memasoknya.Ide kedua adalah memilih alat. Jika Anda, seperti saya, bekerja dengan SQL Server, maka tSQLt adalah, jika bukan 100% pemenang, maka itu pasti patut Anda perhatikan. Meskipun perkembangan lambat baru-baru ini, masih merupakan alat yang relevan yang sangat memudahkan pengujian.Sumber yang membantu saya (daftar tidak lengkap)