لمعلوماتك: هذه المقالة هي نسخة موسعة من تقريري عن أيام 25 من SQA.بناءً على تجربتي في التواصل مع الزملاء ، يمكنني القول أن اختبار الكود في قاعدة البيانات ليس ممارسة شائعة. هذا يمكن أن يكون خطرا محتملا. تتم كتابة المنطق في قاعدة البيانات بواسطة نفس الأشخاص الذين يكتبون الكود "العادي". وبالتالي ، يمكن أن تكون هناك أخطاء أيضًا ، ويمكن أن تترتب عليها أيضًا عواقب سلبية على المنتج والأعمال والمستهلكين. لا يهم ما إذا كان الأمر يتعلق بالإجراءات المخزنة التي تساعد الخلفية ، أو ETLs التي تقوم بتحويل البيانات إلى التخزين - فهناك خطر ، ويمكن للاختبار أن يقللها بشكل كبير. أريد أن أقول لك ما هو tSQLt وكيف يساعدنا في اختبار التعليمات البرمجية في SQL Server.
السياق
يوجد مستودع كبير على SQL Server يحتوي على بيانات بحث سريرية مختلفة. يتم ملؤها من مصادر مختلفة (بشكل أساسي قواعد البيانات الموجهة المستند). داخل الخادم نفسه ، يتم تحويل البيانات بشكل متكرر باستخدام ETL. في المستقبل ، يمكن تحميل هذه البيانات إلى قواعد بيانات أصغر للاستخدام من قبل تطبيقات الويب التي تحل بعض المشاكل الصغيرة المحددة. يسأل بعض العملاء أيضًا عن واجهة برمجة التطبيقات لتلبية احتياجاتهم الداخلية. في تنفيذ واجهات برمجة التطبيقات هذه ، غالبًا ما يتم استخدام الإجراءات المخزنة والاستعلامات.
بشكل عام ، الكود موجود على جانب نظام إدارة قواعد البيانات.
لماذا كل هذا مطلوب
كما هو معروف بالفعل من المقدمة ، الكود الموجود في قاعدة البيانات هو نفس الكود
التطبيقات ، مثل البقية ، وقد يكون هناك أيضا أخطاء.
أعتقد أن الكثير من الناس يدركون اعتماد سعر الخطأ في وقت اكتشافه ، ويعزى اكتشافه عادةً إلى Barry Bohem. قد يكون الخطأ الذي تم ارتكابه في مرحلة مبكرة والكشف عنه في مرحلة لاحقة أكثر تكلفة بسبب الحاجة إلى المرور عبر العديد من المراحل (الترميز ، الوحدة ، التكامل ، اختبار النظام ، وما إلى ذلك) بشكل متكرر على حد سواء لترجمة الخطأ وإحضار الرمز الذي تم تصحيحه مرة أخرى إلى المرحلة التي تم فيها تحديد المشكلة. هذا التأثير مناسب أيضًا لحالة المستودع. إذا تسللت خطأ إلى بعض ETL ، وخضعت البيانات لتحويلات متعددة ، ثم إذا تم اكتشاف خطأ في البيانات ، فسيتعين عليك:
- انتقل من خلال جميع خطوات التحويل إلى توطين المشكلة.
- حل المشكلة.
- أعد الحصول على البيانات المصححة (لا يتم استبعاد التصحيحات اليدوية).
- تحقق من أن البيانات غير الصحيحة الناتجة عن الخطأ لم تظهر في أي مكان آخر.
لا تنس أننا لا نبيع ألعابًا لينة. يمكن أن يسبب الخطأ في مجال مثل البحث السريري ضررًا ليس فقط لرجال الأعمال ، ولكن أيضًا لصحة الناس.
كيف تختبر؟
بما أننا نتحدث عن اختبار الشفرة ، فإننا نعني اختبار الوحدة والتكامل. هذه الأشياء بروفة للغاية وتتضمن انحدارًا مستمرًا. بالمعنى الدقيق للكلمة ، لا أحد يجري مثل هذا الاختبار يدويًا (حسنًا ، ربما باستثناء بعض الحالات المتدهورة تمامًا).
مكافأة لطيفة: يمكن أن تكون الاختبارات مادة مساعدة عند توثيق الكود. بالمناسبة ، قد تبدو متطلبات العملاء بهذا الشكل (قابلة للنقر):
التفوق ، عمودين بمتطلبات + معلومات داعمة متفرقة في أعمدة أخرى + ترميز مؤلم ، وهو أمر مربك أكثر مما يساعد. إذا لزم الأمر ، قد يكون من الصعب استعادة الرغبات الأصلية. يمكن أن تساعد الاختبارات بشكل أكثر دقة في التعرف على الفروق الدقيقة في التنفيذ (بالطبع ، يجب ألا تعتبرها مكافئة للوثائق).
لسوء الحظ ، مع تعقيد الكود ، يزداد تعقيد الاختبارات ، ويمكن تسوية هذا التأثير.
الاختبارات يمكن أن تكون بمثابة طبقة إضافية من الأمن ضد الفظ عفوية. الاختبارات الذاتية في CI بسبب الشكليات تساعد على التعامل مع هذه المشكلة.
إذا وقع اختيارنا على مسار الأتمتة ، فعلينا أن نقرر الأدوات اللازمة لتنفيذه.
كيف تختبر؟
في حالة اختبار الكود في قاعدة البيانات ، فإنني أميز بين نهجين: مزود بنظام SQL ، أي يعمل مباشرة في DBMS ، وغير مزود ببرنامج SQL. كنت قادرا على تسليط الضوء على الفروق الدقيقة التالية:
في SQL Server ، لدينا بعض الخيارات:
تقييمات "حسن النية" ذاتية ، آسف ، بدون هذا ، لا يوجد مكان.
Explanation: "المظهر الأول" هو أقدم تاريخ في مسار حياة الإطار الذي تمكنت من العثور عليه ، أي الإصدار الأقدم أو الالتزام.
قد تلاحظ أنه تم التخلي عن البدائل التي تعمل بنظام SQL لبعض الوقت ، وأن tSQLt هو الخيار الوحيد المدعوم. وظيفيا ، tSQLt يفوز أيضا. الشيء الوحيد هو أنه من حيث التأكيدات ، تفتخر TST بخيار أكثر ثراءً قليلاً من tSQLt ، والذي من غير المرجح أن يفوق سلبياته.
هناك فروق دقيقة في وثائق tSQLt ، لكنني سأتحدث عن هذا لاحقًا.
في العالم الذي لا يعمل بنظام SQL ، لا تكون الأمور سهلة للغاية. يجري تطوير بدائل ، وإن لم تكن نشطة للغاية. DbFit هي أداة مثيرة للاهتمام تعتمد على إطار FitNesse. انه يقدم اختبارات الكتابة على العلامات ويكي. يعتبر Slacker أيضًا أمرًا غريبًا - نهج BDD عند كتابة اختبارات قاعدة البيانات.
سأناقش التأكيدات في غير مزود بالطاقة. ظاهريا ، هناك عدد أقل منهم ، ويمكن للمرء أن يقول أنهم أسوأ بسبب هذا. ولكن هنا تجدر الإشارة إلى أنها تختلف اختلافًا جوهريًا عن tSQLt. ليس كل شيء بسيط جدا.
السطر الأخير هو "NUnit ، إلخ" - إنه بالأحرى تذكير. يمكن استخدام العديد من أطر اختبار الوحدة المألوفة في العمل اليومي في قواعد البيانات المساعدة باستخدام المكتبات المساعدة. يحتوي الجدول على الكثير من N / A ، لأن هذا السطر ، في الواقع ، يتضمن العديد من الأدوات. تأتي "الفروق الدقيقة" في عمود التأكيد من نفس النقطة - في أدوات مختلفة ، يمكن أن تختلف مجموعتها ، ومسألة قابلية التطبيق على قاعدة البيانات مفتوحة.
كمقياس آخر مثير للاهتمام ، يمكننا النظر في
اتجاهات Google .
الفروق:
- لم أقم بتضمين Slacker ، لأن هذا الاسم يمكن أن يعني الكثير من الأشياء (وطلبات مثل "Slacker framework" غير مرئية بشكل خاص في المخططات).
- من أجل الفضول ، تم إضافة اتجاه TST ، لكنه أيضًا لا يعكس الحالة كثيرًا ، لأنه اختصار يعني الكثير من الأشياء المختلفة.
- لم أقم بتضمين NUnit ونظائرها ، حيث إنها كانت في الأساس أطر لاختبار كود التطبيقات نفسها ، ولا تشير اتجاهاتها إلى سياقنا.
بشكل عام ، يمكننا أن نقول أن tSQLt يبدو إيجابيا على خلفية نظائرها.
ما هو tSQLt؟
tSQLt ، كما قد تتخيل ، هو إطار اختبار وحدة مدعوم من SQL.
→
الموقع الرسميتم الإعلان عن دعم SQL Server منذ 2005 SP2. لم أتمكن مطلقًا من البحث عن الماضي ، لكن لدينا 2012 على خادم dev ، لديّ محليًا عام 2017 - لم تكن هناك أية مشكلات.
المصدر المفتوح ، رخصة أباتشي 2.0 ،
متاح على جيثب . يمكنك تفرع وتهريب واستخدام مجاني في المشاريع التجارية ، والأهم من ذلك ، لا تخافوا من الإشارات المرجعية في CLR.
ميكانيكا العمل
حالات الاختبار هي الإجراءات المخزنة. يتم دمجها في فصول الاختبار (مجموعة الاختبار من حيث xUnit).
فئات الاختبار ليست أكثر من مخططات قاعدة البيانات العادية. وهي تختلف عن المخططات الأخرى عن طريق التسجيل في جداول الإطار. يمكنك إجراء هذا التسجيل عن طريق استدعاء إجراء واحد - tSQLt.NewTestClass.
داخل فئة الاختبار ، من الممكن أيضًا تحديد إجراء SetUp الذي سيتم تشغيله قبل تنفيذ كل حالة اختبار فردية.
إجراء teardown لاستعادة النظام عند الانتهاء من حالة الاختبار غير مطلوب. يتم إجراء كل حالة اختبار مع إجراء SetUp في معاملة منفصلة ، والتي يتم التراجع عنها بعد جمع النتائج. هذا مريح للغاية ، لكن له بعض الآثار السلبية ، والتي سأناقشها أدناه.
يتيح لك إطار العمل تشغيل حالات اختبار فردية أو فصول اختبار كاملة أو جميع فصول الاختبار المسجلة مرة واحدة.
ميزات حسب المثال
لعدم وجود رغبة في إعادة طرح دليل رسمي بسيط بالفعل ، سأتحدث عن إمكانيات الإطار باستخدام الأمثلة.
تنويه:- الأمثلة مبسطة ؛
- في الأصل ، ليس كل رمز اختبار مكتوبًا بواسطتي ، بل هو ثمار الإبداع الجماعي ؛
- تم اختراع المثال 2 من أجل إظهار قدرات tSQLt بشكل كامل.
مثال 1: CsvSql
بناءً على طلب أحد عملاء العميل ، تم تنفيذ ما يلي. قاعدة البيانات في حقول Nvarchar (MAX) بتخزين استعلامات SQL. لعرض هذه الاستعلامات ، يتم إغلاق الحد الأدنى للواجهة الأمامية. يتم استخدام مجموعات النتائج التي يتم إرجاعها بواسطة هذه الطلبات بواسطة الواجهة الخلفية لإنشاء ملف CSV للعودة عبر مكالمة API.
مجموعات النتائج ثقيلة جدًا وتحتوي على العديد من الأعمدة. مثال مشروط لمجموعة النتائج هذه:
مجموعة النتائج هذه هي بعض بيانات التجارب السريرية. دعونا نلقي نظرة فاحصة على كيفية النظر في ClinicsNum - عدد العيادات المشاركة في الدراسة. لدينا جدولان: [التجربة] و [العيادة]:
يوجد FK: [Clinic]. [TrialID] -> [Trial]. [TrialID]. من الواضح ، لحساب عدد العيادات ، نحتاج فقط إلى COUNT المعتاد (*).
SELECT COUNT(*), ... FROM dbo.Trial LEFT JOIN dbo.Clinic ON Trial.ID = Clinic.TrialID WHERE Trial.Name = @trialName GROUP BY ...
كيف يمكننا اختبار مثل هذا الطلب؟ بادئ ذي بدء ، يمكننا استخدام كعب الروتين المريح - FakeTable ، والذي سوف يبسط مزيدًا من العمل إلى حد كبير.
EXEC tSQLt.FakeTable 'dbo.Trial'; EXEC tSQLt.FakeTable 'dbo.Clinic';
FakeTable يفعل شيئًا بسيطًا - إعادة تسمية الجداول القديمة وإنشاء جداول جديدة بدلاً منها. نفس الأسماء ، نفس الأعمدة ، ولكن دون قيود و trigger'ov.
لماذا نحتاج هذا:
- قد تكون هناك بعض البيانات في قاعدة بيانات الاختبار التي قد تتداخل مع الاختبارات. بفضل FakeTable ، نحن لا نعتمد عليها.
- للاختبار ، كقاعدة عامة ، تحتاج إلى ملء بضعة أعمدة فقط. يمكن أن يكون هناك الكثير منهم في الجدول ، وبعضهم سيكون لديه قيود. وبهذه الطريقة ، نقوم بتبسيط عملية التثبيت الإضافي لبيانات الاختبار - سنقوم فقط بإدراج البيانات الضرورية حقًا لحالة الاختبار.
- بالتأكيد لن نؤثر على أي مشغل عند إدراج بيانات الاختبار ولا داعي للقلق بشأن الآثار اللاحقة غير المرغوب فيها.
بعد ذلك ، أدخل بيانات الاختبار التي نحتاجها:
INSERT INTO dbo.Trial ([ID], [Name]) VALUES (1, 'Valerian'); INSERT INTO dbo.Clinic ([ID], [TrialID], [Name]) VALUES (1, 1, 'Clinic1'), (2, 1, 'Clinic2');
نحصل على استعلامنا من قاعدة البيانات ، وننشئ الجدول الفعلي ونملأه بمجموعة النتائج من استعلامنا:
DECLARE @sqlStatement NVARCHAR(MAX) = (SELECT… CREATE TABLE actual ([TrialID], ...); INSERT INTO actual EXEC sp_executesql @sqlStatement, ...
التعبئة المتوقعة - القيم المتوقعة:
CREATE TABLE expected ( ClinicsNum INT ); INSERT INTO expected SELECT 2
أريد أن ألفت انتباهكم إلى حقيقة أنه في الجدول المتوقع لدينا عمود واحد فقط ، بينما في الواقع لدينا مجموعة كاملة.
هذا بسبب ميزة إجراء AssertEqualsTable ، والتي سنستخدمها للتحقق من القيم.
EXEC tSQLt.AssertEqualsTable 'expected', 'actual', 'incorrect number of clinics';
يقارن فقط تلك الأعمدة الموجودة في كلا الجدولين قيد المقارنة. هذا مناسب للغاية في حالتنا ، نظرًا لأن استعلام الاختبار يعرض الكثير من الأعمدة ، ولكل منها منطقه الخاص ، وأحيانًا يكون مربكًا للغاية. لا نريد تضخيم حالات الاختبار ، لذلك هذه الميزة مفيدة جدًا لنا. بالطبع ، هذا سيف ذو حدين. في حالة ملء مجموعة من الأعمدة في الوضع الفعلي تلقائيًا من خلال SELECT TOP 0 وفي وقت ما يظهر عمود إضافي فجأة ، فلن تتمكن حالة الاختبار هذه من اللحاق بهذه اللحظة. للتعامل مع مثل هذه الحالات ، تحتاج إلى القيام بفحوصات إضافية.
إجراءات الأخت AssertEqualsTable
تجدر الإشارة إلى أن tSQLt يحتوي على إجراءين مماثلين لـ AssertEqualsTable. هذه هي AssertEqualsTableSchema و AssertResultSetsHaveSameMetaData. الأول يفعل نفس AssertEqualsTable ، ولكن على الجدول بيانات التعريف. الثاني يجعل مقارنة مماثلة ، ولكن على البيانات الوصفية لمجموعات النتائج.
مثال 2: القيود
في المثال السابق ، رأينا كيف يمكنك إزالة القيد. ولكن ماذا لو كنا بحاجة للتحقق منها؟ من الناحية الفنية ، يعد هذا أيضًا جزءًا من المنطق ، ويمكن اعتباره أيضًا مرشحًا للتغطية الاختبارية.
النظر في الموقف من المثال السابق. جدولين - [التجربة] و [عيادة] ؛ [TrialID] FK:
دعنا نحاول كتابة حالة اختبار لاختبار هذا القيد. في البداية ، كما في المرة الأخيرة ، نحن مزيفة الجداول.
EXEC tSQLt.FakeTable '[dbo].[Trial]' EXEC tSQLt.FakeTable '[dbo].[Clinic]'
الهدف هو نفسه - للتخلص من القيود غير الضرورية. نريد فحوصات معزولة دون لفتات غير ضرورية.
بعد ذلك ، نرجع القيد الذي نحتاجه إلى المكان باستخدام إجراء ApplyConstraint:
EXEC tSQLt.ApplyConstraint '[dbo].[Clinic]', 'Trial_FK';
هنا وضعنا معا تكوين مناسب للتحقق المباشر. سيتألف الاختيار نفسه من حقيقة أن محاولة إدراج البيانات ستؤدي حتماً إلى استثناء. من أجل أن تعمل حالة الاختبار بشكل صحيح ، نحتاج إلى ملاحظة هذا الاستثناء ذاته. سيساعدنا معالج استثناء ExpectException في ذلك.
EXEC tSQLt.ExpectException @ExpectedMessage = 'The INSERT statement conflicted...', @ExpectedSeverity = 16, @ExpectedState = 0;
بعد تثبيت المعالج ، يمكنك محاولة إدراج غير قابل للإدراج.
INSERT INTO [dbo].[Clinic] ([TrialID]) VALUES (1)
استثناء اشتعلت. تمرير الاختبار.
إجراءات الأخت
مطورو TSQLt يقدمون لنا طريقة مماثلة لاختبار المشغلات. يمكنك استخدام الإجراء ApplyTrigger لإرجاع المشغل إلى الجدول المزيف. علاوة على ذلك ، كل شيء كما في المثال أعلاه - نقوم بتشغيل المشغل والتحقق من النتيجة.
ExpectNoException - شعار ExpectException
في الحالات التي لا يجب فيها حدوث استثناء ، هناك إجراء ExpectNoException. إنه يعمل بنفس طريقة ExpectException ، باستثناء أن الاختبار يعتبر فاشلاً في حالة حدوث استثناء.
مثال 3: إشارة
الوضع على النحو التالي. هناك عدد من الإجراءات المخزنة وخدمات النوافذ. بداية تنفيذها يمكن أن يكون سبب الأحداث الخارجية المختلفة. في هذه الحالة ، يتم إصلاح الأمر المسموح به لتنفيذها. يتم استخدام إشارة لتمييز الوصول إلى جداول قاعدة البيانات. إنها مجموعة من الإجراءات المخزنة.
على سبيل المثال ، ضع في اعتبارك أحد هذه الإجراءات. لدينا جدولان:
يحتوي الجدول [Process] على قائمة بالعمليات المسموح بتنفيذها ، [ProcStatus] - قائمة بأوضاع هذه العمليات.
ماذا تفعل إجراءاتنا؟ عند الاتصال ، تحدث سلسلة من الاختبارات أولاً:
- يتم البحث عن اسم العملية المراد بدءها ، والتي تم تمريرها في معلمة الإجراء ، في حقل [الاسم] في جدول [المعالجة].
- إذا تم العثور على اسم العملية ، فسيتم التحقق مما إذا كان من الممكن حاليًا تشغيله - علامة [IsRunable] في جدول [Process].
- إذا تبين أن العملية مقبولة للتنفيذ ، يبقى أن نتأكد من أنها ليست قيد التشغيل بالفعل. في الجدول [ProcStatus] ، يتم التحقق من عدم وجود سجلات حول هذه العملية مع الحالة = 'InProg'.
إذا كان كل شيء على ما يرام ، فسيتم إضافة سجل جديد حول هذه العملية بالحالة "InProg" إلى ProcStatus (يُعتبر هذا تشغيلًا) ، ويتم إرجاع معرف هذا السجل مع المعلمة ProcStatusId. إذا فشل أي تحقق ، فإننا نتوقع ما يلي:
- يتم إرسال خطاب إلى مسؤول النظام.
- إرجاع ProcStatusId = -1.
- لا يتم إضافة إدخال جديد في [ProcStatus].
دعنا نكتب حالة اختبار لاختبار الحالة عندما لا تكون العملية في قائمة الحالات المقبولة.
للراحة ، قم بتطبيق FakeTable على الفور. هنا ليس مهمًا جدًا ، ولكنه قد يكون مفيدًا:
- نحن مضمونون للتخلص من أي بيانات قد تتداخل مع التنفيذ الصحيح لحالة الاختبار.
- سنقوم بتبسيط عملية التحقق من الإدخالات المفقودة في ProcStatus.
EXEC tSQLt.FakeTable 'dbo.Process'; EXEC tSQLt.FakeTable 'dbo.ProcStatus';
لإرسال رسالة ، يتم استخدام الإجراء [SendEmail] الذي كتبه المبرمجون لدينا. للتحقق من إرسال خطاب إلى المسؤولين ، نحتاج إلى الاتصال بها. في هذه الحالة ، يقدم لنا tSQLt استخدام SpyProcedure.
EXEC tSQLt.SpyProcedure 'dbo.SendEmail'
تقوم SpyProcedure بما يلي:
- ينشئ جدول النموذج [dbo]. [SendEmail_SpyProcedureLog].
- مثل FakeTable ، فإنه يستبدل الإجراء الأصلي بالإجراء الخاص به ، بنفس الاسم ، ولكن يحتوي على منطق التسجيل. إذا رغبت في ذلك ، يمكنك إضافة أي من منطقك الخاص.
كما تعتقد ، يحدث التسجيل في الجدول [dbo]. [SendEmail_SpyProcedureLog]. يحتوي هذا الجدول على العمود [_ID_] - رقم تسلسل استدعاء الإجراء. تحمل الأعمدة اللاحقة أسماء المعلمات التي تم تمريرها إلى الإجراء ، ويتم جمع القيم التي تم تمريرها في المكالمات فيها. إذا لزم الأمر ، ويمكن أيضا أن يتم التحقق منها.
آخر ما نحتاج إلى فعله قبل استدعاء الإشارة والتدقيق هو إنشاء متغير نضع فيه معرف السجل من جدول [ProcStatus] (بدقة أكثر ، -1 ، لأنه لن تتم إضافة السجل).
DECLARE @ProcStatusId BIGINT;
اتصل بالإشارة:
EXEC dbo.[Semaphore_JobStarter] 'SomeProcess', @ProcStatusId OUTPUT;
هذا كل شيء ، الآن لدينا جميع البيانات اللازمة للتحقق. لنبدأ بالتحقق من الشحنة.
الرسائل:
IF NOT EXISTS ( SELECT * FROM dbo.SendEmail_SpyProcedureLog) EXEC tSQLt.Fail 'SendEmail has not been run.';
في هذه الحالة ، قررنا عدم التحقق من المعلمات المرسلة أثناء الإرسال ، ولكن ببساطة تحقق من الحقيقة نفسها. أود أن ألفت انتباهكم إلى الإجراء tSQLt.Fail. انها تسمح لك "رسميا" تفشل حالة الاختبار. إذا كنت بحاجة إلى إنشاء بعض الإنشاءات المحددة ، فإن tSQLt.Fail سيتيح لك القيام بذلك.
بعد ذلك ، تحقق من عدم وجود إدخالات في [dbo]. [ProcStatus]:
EXEC tSQLt.AssertEmptyTable 'dbo.ProcStatus';
هذا هو المكان الذي ساعدنا فيه FakeTable الذي طبقناه في البداية. بفضله ، يمكننا أن نتوقع الفراغ. وبدون ذلك ، للتحقق الدقيق ، يجب علينا ، بطريقة جيدة ، مقارنة عدد السجلات قبل وبعد الإشارة.
Equal ProcStatusId = -1 يمكننا بسهولة التحقق من AssertEquals:
EXEC tSQLt.AssertEquals -1, @ProcStatusId, 'Wrong ProcStatusId.';
AssertEquals هو أضيق الحدود - فهو يقارن ببساطة قيمتين ، لا شيء خارق للطبيعة.
تأكيد إجراءات الأخوة والأخوات
لمقارنة القيم ، يتم تزويدنا بعدد من الإجراءات:
- AssertEquals
- AssertNotEquals
- AssertEqualsString
- AssertLike
أعتقد أن أسمائهم تتحدث عن نفسها. الشيء الوحيد الجدير بالملاحظة هو وجود إجراء AssertEqualsString منفصل. الشيء هو أن AssertEquals / AssertNotEquals / AssertLike يعمل مع SQL_VARIANT ، ولا ينطبق NVARCHAR (MAX) عليه ، وبالتالي كان على مطوري tSQLt تخصيص إجراء منفصل لاختبار NVARCHAR (MAX).
وظيفة وهمية
FakeFunction مع بعض الامتداد يمكن أن يسمى إجراء مشابه لـ SpyProcedure. يسمح لك هذا المزيف باستبدال أي وظيفة بالوظيفة الأبسط اللازمة. نظرًا لأن الوظائف في SQL Server تعمل على مبدأ الأنبوب مع معجون الأسنان - فهي تعطي النتيجة من خلال "الفجوة التكنولوجية الوحيدة" ، ومن ثم ، لسوء الحظ ، لا يتم توفير وظائف تسجيل. فقط بديل للمنطق.
المزالق
تجدر الإشارة إلى بعض المزالق التي قد تواجهها أثناء العمل مع tSQLt. في هذه الحالة ، أعني بواسطة بعض الأخطاء بعض المشكلات التي نشأت بسبب قيود SQL Server و / أو التي لا يمكن حلها بواسطة مطوري الإطار.
إلغاء / تلف المعاملات
المشكلة الأولى والأكثر أهمية التي واجهها فريقنا كانت إلغاء المعاملات. لا يعرف SQL Server كيفية استعادة المعاملات المتداخلة بشكل منفصل - فقط كل شيء ككل ، وصولاً إلى الخارجية. بالنظر إلى حقيقة أن tSQLt يلتف كل حالة اختبار في معاملة منفصلة ، يصبح هذا مشكلة. بعد كل شيء ، يمكن أن يؤدي تراجع المعاملة داخل إجراء الاختبار إلى إنهاء تنفيذ الاختبار ، مما يؤدي إلى حدوث خطأ غير وصفي في التنفيذ.
للتحايل على هذه المشكلة ، نحن نستخدم savepoints. الفكرة بسيطة. قبل البدء في إجراء ما في إجراء الاختبار ، نتحقق من وجودنا بالفعل داخل المعاملة. إذا تبين أن نعم ، فنحن ، إذن ، بافتراض أن هذه معاملة tSQLt ، فضع نقطة حفظ بدلاً من البدء. ثم ، إذا لزم الأمر ، سوف نعود إلى نقطة التوفير هذه ، وليس إلى بداية المعاملة. التعشيش على هذا النحو ليس كذلك.
المشكلة معقدة بسبب تلف المعاملات. إذا حدث خطأ ما فجأة وحدث الاستثناء ، فقد تصبح المعاملة محكوم عليها. لا يمكن ارتكاب مثل هذه الصفقة فحسب ، بل يتم التراجع عنها أيضًا إلى نقطة التوفير ، ولكن يتم التراجع عن الأمر برمته.
بالنظر إلى كل ما سبق ، يتعين عليك تطبيق التصميم التالي:
DECLARE @isNestedTransaction BIT = CASE WHEN @@trancount > 0 THEN 'true' ELSE 'false' END; BEGIN TRY IF @isNestedTransaction = 'false' BEGIN TRANSACTION ELSE SAVE TRANSACTION SavepointName;
النظر في رمز في أجزاء. أولاً نحتاج إلى تحديد ما إذا كنا داخل معاملة:
DECLARE @isNestedTransaction BIT = CASE WHEN @@trancount > 0 THEN 'true' ELSE 'false' END;
بعد تلقي علامةisNestedTransaction ، قم بتشغيل TRY block وقم بتعيين savepoint أو بداية المعاملة ، تبعًا للموقف.
BEGIN TRY IF @isNestedTransaction = 'false' BEGIN TRANSACTION ELSE SAVE TRANSACTION SavepointName;
بعد قيامنا بشيء مفيد ، ارتكب ، إذا كانت هذه بداية "حقيقية" للإجراء.
بالطبع ، إذا كان هذا إطلاقًا من حالة اختبار ، فلن نحتاج إلى ارتكاب أي شيء. في نهاية التنفيذ ، سيقوم tSQLt ببساطة باستعادة كل التغييرات. إذا حدث خطأ ما فجأة ووصلنا إلى كتلة CATCH ، فإن أول ما يجب فعله هو معرفة ما إذا كان من الممكن الالتزام بمعاملاتنا.
BEGIN CATCH DECLARE @isCommitable BIT = CASE WHEN XACT_STATE() = 1 THEN 'true' ELSE 'false' END;
يمكننا التراجع إلى savepoint فقط إذا
- صفقة مريحة
- يتم إجراء اختبار تجريبي ، أي savepoint موجود.
في حالات أخرى ، نحتاج إلى إعادة المعاملة بالكامل.
IF @isCommitable = 'true' AND @isNestedTransaction = 'true' ROLLBACK TRANSACTION SavepointName; ELSE ROLLBACK; THROW; END CATCH;
نعم ، لسوء الحظ ، إذا حصلنا على معاملة غير قابلة للتطبيق أثناء إجراء اختبار ، فلا يزال لدينا خطأ في تنفيذ حالة الاختبار.
FakeTable ومشكلة مع المفتاح الخارجي
النظر في الجداول المألوفة [محاكمة] و [عيادة]:
نتذكر [TrialID] FK. ما هي المشاكل التي يمكن أن يسببها هذا؟ في الأمثلة المقدمة مسبقًا ، طبقنا FakeTable على كلا الجدولين في وقت واحد. إذا قمنا بتطبيقه فقط على [Trial] ، سنحصل على الموقف التالي:
قد تتحول محاولة إدراج إدخال في [Clinic] ، بهذه الطريقة ، إلى فشل (حتى لو قمنا بإعداد جميع البيانات الضرورية في الإصدار المزيف من الجدول [Trial]).
[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}
الخلاصة: أنت بحاجة إلى أي شيء مزيف ، أو عدم مزيف أي شيء. في الحالة الثانية ، من الواضح أنه يجب إعداد القاعدة مسبقًا للاختبار.
SpyProcedure على إجراءات النظام
للأسف ، سوف تفشل التجسس على المكالمات لإجراءات النظام.
[HabrDemo].[test_test] failed: (Error) Cannot use SpyProcedure on sys.sp_help because the procedure does not exist[16,10] {tSQLt.Private_ValidateProcedureCanBeUsedWithSpyProcedure,7}
في مثال الإشارة ، قمنا بتتبع المكالمات لإجراء [SendEmail] الذي كتبه مطورونا. في هذه الحالة ، يرجع سبب كتابة إجراء منفصل إلى الحاجة إلى جمع بعض المعلومات الإضافية ومعالجتها قبل إرسال الرسائل مباشرةً. بشكل عام ، يجب أن يكون المرء مستعدًا ذهنياً لحقيقة أنه قد يتعين على الشخص كتابة إجراءات البينية لبعض إجراءات النظام فقط لغرض الاختبار.
كرامة
تركيب سريع
يتم التثبيت على مرحلتين ويستغرق حوالي دقيقتين. تحتاج فقط إلى تنشيط CLR على الخادم ، إن لم يكن قد تم بالفعل ، وتنفيذ برنامج نصي واحد. كل شيء ، يمكنك إضافة فئة الاختبار الأولى وكتابة حالات الاختبار.
التطور السريع
tSQLt هو وسيلة سهلة لتعلم الأداة. استغرق الأمر مني بعض الوقت لإتقانه. سألت زملائي الذين عملوا مع الإطار ، واتضح أن الجميع سوف يقضون حوالي يوم واحد.
تنفيذ سريع في CI
استغرق الأمر حوالي ساعتين لإنشاء التكامل في CI في مشروعنا. بالطبع ، يمكن أن يتغير الوقت ، لكن بصفة عامة هذه ليست مشكلة ، ويمكن تنفيذ التكامل بسرعة كبيرة.
مجموعة واسعة من الأدوات
هذا تقييم شخصي ، لكن في رأيي ، فإن الوظيفة التي توفرها tSQLt غنية للغاية وتغطي حصة الأسد من الاحتياجات في الممارسة. في حالات نادرة عندما لا يكون هناك ما يكفي من المنتجات المزيفة والتأكيدات ، يوجد بالطبع ملف tSQLt.Fail.
وثائق مريحة
الوثائق الرسمية مريحة ومتسقة. من خلال مساعدتها ، يمكنك بسهولة فهم جوهر استخدام tSQLt في وقت قصير ، حتى لو كانت هذه هي أول أداة لاختبار الوحدة.
الانتاج مريحة
يمكن الحصول على البيانات في شكل نص واضح للغاية:
[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. -----------------------------------------------------------------------------
يمكنك أيضًا الاستخراج من قاعدة البيانات (القابلة للنقر) ...
... أو احصل على تنسيق 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>
يتيح لك الخيار الأخير دمج الاختبارات بسهولة في CI. على وجه الخصوص ، كل شيء يعمل بالنسبة لنا تحت أطلس الخيزران.
دعم ريدجيت
تتضمن الإيجابيات دعمًا لموفر كبير من أدوات DBA مثل RedGate. اختبار SQL - المكون الإضافي لـ SQL Server Management Studio - يعمل مع tSQLt مباشرة خارج الصندوق. بالإضافة إلى ذلك ، توفر RedGate المساعدة للمطور الرئيسي لـ tSQLt مع بيئة التطوير ، كما يدعي المطور نفسه في
مجموعات Google .
القصور
لا الجدول مزيفة مؤقت
tSQLt لا يسمح الجداول المؤقتة وهمية. إذا لزم الأمر ، يمكنك استخدام
الوظيفة الإضافية غير الرسمية . لسوء الحظ ، هذه الوظيفة الإضافية مدعومة فقط بواسطة SQL Server 2016+.
لا يمكن الوصول إلى قواعد البيانات الخارجية
لن تعمل على الاحتفاظ بقاعدة منفصلة فقط لتخزين الإطار. تم تصميم tSQLt لاختبار ما يكمن معها في نفس قاعدة البيانات. المزيفة ، للأسف ، لن تعمل.
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
يبدو أن التأكيدات تعمل ، لكن لا أحد يضمن أدائها بالطبع.
CREATE PROCEDURE [tSQLtDemo].[test_outer_db_assertions] AS BEGIN SELECT TOP 1 * INTO
البق الوثائق
على الرغم من أنني كتبت أعلاه حول تناسق الوثائق وسهولة الوصول إليها ، فهي تحتوي أيضًا على مشكلات. هناك بعض اللحظات التي عفا عليها الزمن في ذلك.
مثال 1. يقترح
"دليل البدء السريع" تنزيل الإطار من SourceForge. قالوا وداعًا لـ SourceForge
في عام 2015 .
مثال 2.
يستخدم دليل ApplyConstraint في المثال البناء الثقيل مع إجراء Fail للتعرف على الاستثناء ، والذي سيكون من الأسهل والأكثر بروزًا استبداله بـ 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
وهذا أمر طبيعي ، لأنه يحدث ...
التخلي الجزئي
هناك استراحة طويلة في تطوير tSQLt من بداية عام 2016 إلى يونيو 2019. نعم ، للأسف ، تم التخلي عن هذه الأداة جزئيًا. في عام 2019 ، وبالتدريج ، وفقًا
لـ GitHub ، لا يزال التطور قد تم. على الرغم من أن مجموعات Google الرسمية
تحتوي على سلسلة رسائل تم فيها توجيه سؤال إلى Sebastian ، المطور الرئيسي لـ tSQLt ، مباشرةً حول مصير التطوير. تم طرح السؤال الأخير في 2 مارس 2019 ، ولم يتم تلقي الإجابة بعد.
مشكلة في SQL Server 2017
إذا كنت تستخدم SQL Server 2017 ، فبالنسبة لك ، ربما يتطلب تثبيت tSQLt بعض المعالجة الإضافية.
الشيء هو أنه لأول مرة منذ عام 2012 ، قام SQL Server بإجراء تغييرات أمنية. على مستوى الخادم ، تمت إضافة علامة "أمان صارم CLR" ، والتي تحظر إنشاء تجميعات غير موقعة (حتى SAFE). وصف تفصيلي للمشكلة يستحق مقالة منفصلة (ولحسن الحظ ، كل شيء تم وصفه جيدًا والمقالات التالية في السلسلة). فقط يكون مستعدا عقليا لذلك.بالطبع ، يمكن أن يعزى هذا العيب إلى "المزالق" ، التي لا يعتمد حلها على مطوري tSQLt ، ولكن من الممكن حل هذه المشكلة على مستوى الإطار ، وإن كان يستغرق وقتًا طويلاً. لدى GitHub مشكلة بالفعل ، ومع ذلك ، فقد تم سحبها منذ أكتوبر 2017 (انظر الفقرة الفرعية السابقة).بدائل (for) لقواعد إدارة قواعد البيانات الأخرى
تجدر الإشارة أيضًا إلى بدائل لـ DBMSs الأخرى. tSQLt ليست هي الأداة الوحيدة من نوعها. على الرغم من اختلاف خصائص التطبيق (CLR و T-SQL بشكل كبير عن لهجات SQL الأخرى) ، لا يمكنك استخدامها في قواعد بيانات إدارة قواعد البيانات الأخرى ، لا يزال بإمكانك العثور على أدوات مماثلة. ألاحظ أن هذه البدائل تختلف اختلافًا كبيرًا عن tSQLt ، لذلك نحن نتحدث في المقام الأول عن النهج الذي تعمل به SQL ككل.لذلك ، تحت PostgreSQL ، هناك ptTAP مطور إلى حد ما ومتطور بشكل فعال . أنه ينطوي على اختبارات الكتابة في PL / pgSQL الأصلي وإخراج النتائج في تنسيق TAP. تحت MySQL ، هناك أداة مشابهة ، وإن كانت أقل فاعلية إلى حد ما - MyTAP . إذا كنت محظوظًا للعمل مع Oracle ، فستتاح لك الفرصة لاستخدام utPLSQL- أداة قوية للغاية ونشطة (وأود أن أقول أكثر من) أداة تطوير.استنتاج
ربما ، مع كل المعلومات المذكورة أعلاه ، أردت أن أنقل فكرتين رئيسيتين.الأول هو فائدة اختبار التعليمات البرمجية في قاعدة بيانات. سواء كنت جالسًا في SQL Server أو Oracle أو MySQL ، فهذا ليس بالأمر المهم. إذا كان لديك مقدار معين من المنطق غير المختبر المخزن في قاعدة البيانات ، فإنك تتحمل مخاطر إضافية. الأخطاء في رمز قاعدة البيانات قادرة ، مثل الأخطاء في بقية التعليمات البرمجية ، على التسبب في تلف المنتج ، ونتيجة لذلك ، للشركة التي توفر له.الفكرة الثانية هي اختيار أداة. إذا كنت ، مثلي ، تعمل مع SQL Server ، فإن tSQLt هو ، إن لم يكن فائزًا بنسبة 100٪ ، فإن الأمر يستحق اهتمامك بالتأكيد. على الرغم من التطور البطيء مؤخرًا ، إلا أنها لا تزال أداة ذات صلة تسهل الاختبار إلى حد كبير.المصادر التي ساعدتني (قائمة غير كاملة)