اختبار رمز SQL Server مع tSQLt

لمعلوماتك: هذه المقالة هي نسخة موسعة من حديثي في أيام 25 من SQA.

بناءً على تجربتي مع الزملاء ، يمكنني أن أذكر أن اختبار كود DB ليس ممارسة واسعة الانتشار. هذا يمكن أن يكون خطيرا. يتم كتابة منطق DB من قبل البشر تماما مثل جميع التعليمات البرمجية "المعتادة" الأخرى. لذلك ، يمكن أن يكون هناك إخفاقات يمكن أن تسبب عواقب سلبية على المنتج أو العمل أو المستخدمين. سواء كانت هذه إجراءات مخزنة تساعد في الخلفية أم أنها تقوم بتعديل بيانات ETL في المستودع - فهناك دائمًا مخاطرة ويساعد الاختبار في تقليلها. أريد أن أقول لك ما هو tSQLt وكيف يساعدنا على اختبار رمز DB.


السياق


يوجد مستودع كبير يستخدم SQL Server ويحتوي على بيانات تجريبية سريرية مختلفة. يتم ملؤها من مجموعة متنوعة من المصادر (قواعد البيانات الموجهة المستند بشكل أساسي). يقوم الكثير من ETLs بتحويل البيانات داخل المستودع في العديد من المناسبات. يمكن تحميل هذه البيانات في قواعد بيانات أصغر للاستخدام بواسطة تطبيقات الويب الموجهة نحو مهام محددة صغيرة. طلب بعض عملاء العميل تنفيذ واجهات برمجة التطبيقات (APIs) لاحتياجاتهم. غالبًا ما تستخدم واجهات برمجة التطبيقات هذه الإجراءات المخزنة والاستعلامات المختلفة.


بشكل عام ، يوجد قدر كبير جدًا من الكود على جانب نظام إدارة قواعد البيانات.

لماذا نحتاج هذا؟


كما ترون من المقدمة ، يعد كود DB جزءًا من كود التطبيق ويمكن أن يحتوي على أخطاء أيضًا.

أعتقد أن الكثير منا على دراية بمنحنى Boehm's Curve: دائمًا ما تكون الأخطاء أكثر تكلفة في وقت لاحق من إصلاحها. يمكن أن يكلف الخطأ الذي حدث في مرحلة التطوير السابقة والمترجمة في مرحلة لاحقة. ويرجع ذلك إلى ضرورة إجراء الكثير من الخطوات المؤقتة (الترميز ، واختبار الوحدة ، واختبار التكامل ، واختبار النظام ، وما إلى ذلك) مرتين: للتصحيح ولإعادة الرمز إلى المرحلة التي تم العثور عليها فيها. هذا التأثير صحيح بالنسبة لحالة المستودع أيضًا. إذا كان هناك خطأ في إجراء ETL وتم تعديل البيانات عدة مرات ، يجب علينا:

  1. انتقل من خلال كافة خطوات تحويل البيانات إلى مصدر المشكلة
  2. حل المشكلة
  3. اشتقاق البيانات المناسبة مرة أخرى (قد تكون هناك حاجة إلى تعديلات يدوية إضافية)
  4. تأكد من عدم وجود بيانات مقطوعة أخرى ناتجة عن الخطأ.

لا تنس أننا لا نبيع ألعابًا ناعمة. خطأ في مجال مثل التجارب السريرية قد يضر ليس فقط الأعمال التجارية ولكن أيضا صحة الإنسان.

كيف تختبر؟


بما أننا نتحدث عن اختبار الشفرة ، فإننا نعني اختبار الوحدة والتكامل. هذه الأشياء متكررة للغاية وتتضمن تدهورًا مستمرًا. بالمعنى الدقيق للكلمة ، لا يتم إجراء هذا الاختبار يدويًا (جيدًا ، باستثناء بعض الحالات الفردية).

مكافأة لطيفة: يمكن أن تكون الاختبارات مواد داعمة لتوثيق الكود. على سبيل المثال ، قد تبدو المتطلبات بهذا الشكل (قابلة للنقر):


ملف XLS ، وعمودان مع المتطلبات + معلومات إضافية مجزأة في أعمدة أخرى + العلامات مربكة. قد يكون من الصعب استعادة الرغبات الأولية إذا لزم الأمر. يمكن أن تساعد الاختبارات في تسجيل الفروق الدقيقة في التنفيذ. بالطبع ، لا ينبغي اعتبارها بديلاً للوثائق.

لسوء الحظ ، يزيد اختبار التعقيد مع نمو تعقيد الرمز ، لذلك ، يمكن تخفيف هذا التأثير.

يمكن أن تكون الاختبارات طبقة أمان إضافية ضد الدمج التلقائي. تساعد اختبارات CI التلقائية في هذه المشكلة بسبب شكليتها.

لذلك ، إذا قررنا استخدام الأتمتة ، نحتاج إلى اختيار أداة لذلك.

ماذا تستخدم للاختبار؟


في حالة اختبار كود DB ، أرى طريقتين: تعمل بنظام SQL (عندما تعمل أداة في نظام إدارة قواعد البيانات مباشرة) وغير تعمل بنظام SQL. فيما يلي الاختلافات الرئيسية التي وجدتها:
SQL التي تعمل بالطاقة
غير SQL التي تعمل بالطاقة
يتطلب تثبيت كائنات DB
يتطلب تثبيت أدوات خارجية (فيما يتعلق DB)
الاختبارات مستقلة عن التقنيات المستخدمة خارج الديسيبل
يمكن أن تعتمد الاختبارات على التقنيات المستخدمة خارج الديسيبل
الإطار مخصص دائمًا لنظام إدارة قواعد البيانات واحد فقط
غالبًا ما يدعم Framework قواعد بيانات متعددة
المعرفة DBMS هو الشرط الوحيد لكتابة الاختبارات. من الممكن استخدام اختبار يدوي أو DBA
مطلوب معرفة إضافية بلغات البرمجة أو التقنيات لكتابة الاختبارات ؛ غالبا ما تكون هناك حاجة إلى مساعدة المطور
التنفيذ على مستوى DBMS يسمح باستخدام التزوير والتأكيدات المتقدمة.
التنفيذ خارج نظام إدارة قواعد البيانات قد يحد من ميزات الأداة
في حالة SQL Server ، لدينا العديد من الخيارات:
معلومات عامة
اسمنهجهندسة معماريةاللغة / المنصةاختبارات اللغة
tSQLtSQL التي تعمل بالطاقةxUnitT-SQL + CLRT-SQL
TSQLUnitSQL التي تعمل بالطاقةxUnitT-SQLT-SQL
utTSQLSQL التي تعمل بالطاقةxUnitT-SQLT-SQL
تجارة الرقيق عبر الأطلسيSQL التي تعمل بالطاقةxUnitT-SQLT-SQL
DbFitغير SQL التي تعمل بالطاقةFitNesseC # / جافاويكي تخفيض السعر
المتكاسلغير SQL التي تعمل بالطاقةRSpec (BDD الموجهة)ياقوتياقوت
نونيت ، الخغير SQL التي تعمل بالطاقةxUnitلا يوجدلا يوجد
تواريخ
اسمأول ظهورآخر التزامأحدث إصدار
tSQLt27-07-20082019/01/072016/01/31
TSQLUnit2006-12-16 (0.9)
2007-07-21 (0.91 rc1)
04/26/2018 (جيثب)04/09/2011 (SourceForge)
utTSQL12-03-200812-03-200812-03-2008
تجارة الرقيق عبر الأطلسي02-03-2009 (الإصدار 1.0)لا يوجد30-03-2012
DbFit12-01-20092018/09/102015/08/15
المتكاسل23-06-20112018/10/122018/10/12
نونيت ، الخلا يوجدلا يوجدلا يوجد
ملامح
اسمCLR غير مطلوبإخراج XMLاختبارات في المعاملات المنفصلةالمزيفونخطأ معالجاتتأكيدات
tSQLt-++++ممتاز
TSQLUnit+-+--الفشل
utTSQL+----أقل من المتوسط
تجارة الرقيق عبر الأطلسي+++ (اختياري)-+ممتاز
DbFit+-+ (اختياري)-+جيد جدا الدقة في درجات االختالف
المتكاسل+-+ (اختياري)--جيد جدا الدقة في درجات االختالف
نونيت ، الخ++لا يوجدلا يوجدلا يوجدممتاز. الدقة في درجات االختالف
آخر
اسمتوثيقمجتمع
tSQLtممتاز. الدقة في درجات االختالفممتاز
TSQLUnitأقل من المتوسطأقل من المتوسط
utTSQLممتازأقل من المتوسط
تجارة الرقيق عبر الأطلسيممتازأقل من المتوسط
DbFitممتازمتوسط
المتكاسلممتازمتوسط
نونيت ، الخممتازممتاز
مقياس "ممتاز - الفشل" شخصي ، آسف ، من الصعب إيجاد طريقة للتغلب عليه.

"أول ظهور" - أول تاريخ لظهور الإطار الذي يمكنني العثور عليه - أول إصدار أو التزام.

كما ترون ، تم التخلي عن البدائل التي تعمل بنظام SQL منذ وقت طويل ، و tSQLt هو المنتج الوحيد المدعوم حاليًا. الى جانب ذلك ، tSQLt يفوز وظيفيا. الشيء الوحيد هو ، TST تفتخر مجموعة من الثراء أكثر قليلا من التأكيدات من tSQLt. ومع ذلك ، أشك في أن هذا يمكن أن يفوق كل السلبيات.

تحتوي وثائق tSQLt على بعض الفروق الدقيقة ، وسوف أصفها لاحقًا.

في العالم الذي لا يدعم SQL ، الأمور ليست واضحة. البدائل تتطور ، على الرغم من أنها ليست فائقة النشاط. DbFit هي أداة مثيرة للاهتمام للغاية تستند إلى إطار FitNesse. يعني استخدام علامات الويكي لكتابة الاختبارات. تعتبر لعبة Slacker مثيرة للاهتمام أيضًا: يُقترح نهج BDD لاختبار رمز DB.

يجب أن أقول حول التأكيدات في الحلول التي لا تدعم SQL. للوهلة الأولى ، كمية التأكيدات أقل ، ويمكننا أن نعتقد أن هذه الأدوات أسوأ. ولكن يجب أن نضع في اعتبارنا أنها تختلف اختلافًا جوهريًا عن tSQLt ، لذلك ، هذه النظرة السطحية غير صحيحة.

الصف الأخير - "NUnit ، إلخ" - هو أشبه تذكير. قد يتم تطبيق الكثير من أطر اختبار الوحدات المعتادة على كود DB بمساعدة مكتبات إضافية. يوجد الكثير من N / A في هذا الصف لأن هذا الصف ، في الواقع ، يتضمن أدوات متعددة. هذا هو مصدر "الفروق الدقيقة" في عمود "التأكيدات" - يمكن أن توفر الأدوات المختلفة مجموعات مختلفة وليس هناك ما يضمن إمكانية تطبيق جميع التأكيدات على قاعدة البيانات.

كمقياس مهم آخر ، يمكننا النظر في اتجاهات Google .


الفروق:

  1. قررت عدم تضمين Slacker لأن هذا الاسم قد يعني أشياء مختلفة (بالكاد يتم عرض استعلامات مثل "Framework Slacker" على الرسم البياني).
  2. بدافع الفضول (ولأن فتحة واحدة ظلت فارغة) ، أضفت اتجاه TST. لكنه بالكاد يظهر لنا الصورة الحقيقية لأنها اختصار والتي يمكن أن تعني أشياء مختلفة أيضًا.
  3. لم أدرج NUnit ونظائرها. هذه الأدوات هي أطر عمل لاختبار الكود "المعتاد" ، لذلك اتجاهاتها ليست وصفية لسياقنا.

كما ترون ، tSQLt هي الأداة الأكثر للبحث في القائمة. أداة أخرى (أقل) شعبية هي DbFit. الأدوات الأخرى لها شعبية محدودة.

إلى حد كبير ، يمكننا أن نرى أن tSQLt يضيء على الخلفية.

ما هو tSQLt؟


من السهل تخمين أن tSQLt عبارة عن إطار عمل لاختبار الوحدات يعمل بنظام SQL. الموقع الرسمي هو https://tsqlt.org .

تم الوعد بأن tSQLt يدعم SQL Server بدءًا من حزمة الخدمة SP2 2005. لم أتحقق من هذه المراجعات المبكرة ، لكنني لا أرى أي مشكلات في عام 2012 على خادم dev-server و 2017 على جهازي المحلي.

المصدر المفتوح ، رخصة أباتشي 2.0 ، متاح على جيثب . كالمعتاد ، يمكننا أن نتخلى عن المشروعات التجارية ، والمساهمة فيها واستخدامها مجانًا ، والأهم من ذلك ، ألا تخاف من برامج التجسس في CLR.

علم الميكانيكا



حالات الاختبار هي الإجراءات المخزنة. يمكن دمجها في فصول الاختبار (مجموعة الاختبارات في مصطلحات xUnit).

فئات الاختبار ليست سوى مخططات DB. يتطلب tSQLt تسجيلهم باستخدام إجراء NewTestClass الذي يضيف فئات الاختبار إلى جدول خاص.

من الممكن تحديد إجراء SetUp. سيتم تشغيل هذا الإجراء قبل تشغيل كل حالة اختبار منفصلة.

إجراء teardown بعد تشغيل حالة الاختبار غير مطلوب. يتم تشغيل كل حالة اختبار مع SetUp في معاملة منفصلة يتم إرجاعها بعد تجميع النتائج. إنها مريحة للغاية ولكن لها بعض الآثار السلبية - سوف أصفها لاحقًا.

يسمح إطار العمل بحالات الاختبار واحدًا تلو الآخر ، وفئات الاختبار بأكملها مرة واحدة أو حتى جميع فئات الاختبار المسجلة بأمر واحد.

الميزات والأمثلة


غير راغبة في تكرار الدليل الرسمي ، سأعرض ميزات tSQLt على الأمثلة.

تنويه:

  • أمثلة مبسطة
  • الكود الأصلي ليس لي تمامًا - إنه إبداعات جماعية إلى حد ما
  • المثال 2 هو خيالي من قبلي من أجل إظهار الميزات بشكل كامل.

مثال رقم 1: CsvSql


تم تنفيذ ما يلي بناءً على طلب أحد عملاء العميل. هناك استعلامات SQL المخزنة في حقول Nvarchar (MAX). يتم إنشاء الحد الأدنى لواجهة المستخدم لعرضها. يتم استخدام مجموعات النتائج الناتجة عن هذه الاستعلامات في الخلفية للحصول على مزيد من التأليف كملفات CSV. يمكن طلب ملفات 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 يجعل شيئًا بسيطًا - إعادة تسمية الجداول القديمة وإنشاء جداول جديدة بنفس الاسم. نفس الأسماء ، نفس الأعمدة ، ولكن دون قيود والمشغلات.

نحتاج هذا بسبب:

  1. يمكن أن يحتوي اختبار قاعدة البيانات على بعض البيانات التي يمكن أن تمنع التشغيل السليم للاختبار. FakeTable يسمح لنا بعدم الاعتماد عليها.
  2. عادة ، نحتاج إلى ملء بضعة أعمدة فقط لأغراض الاختبار. يمكن أن يحتوي الجدول على الكثير منها ، وغالبًا ما يحتوي على قيود ومشغلات. نحن نجعل من السهل إدراج البيانات في وقت لاحق - سنقوم بإدراج المطلوب فقط لمعلومات الاختبار ، والحفاظ على الاختبار في أضيق الحدود ممكن.
  3. لن يكون هناك عمليات تشغيل غير مرغوب فيها ، لذلك ، لا داعي للقلق بشأن الآثار اللاحقة.

ثم نقوم بإدراج بيانات الاختبار المطلوبة:

 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) = (SELECTCREATE 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'; 

يقارن فقط تلك الأعمدة التي يتم تقديمها في كلا الجدولين. إنها مريحة للغاية في حالتنا لأن الاستعلام قيد الاختبار يعرض الكثير من الأعمدة ، كل منها يرتبط بمنطق معقد للغاية. لا نريد تضخيم حالات الاختبار ، لذلك هذه الميزة تساعد حقًا. بالطبع ، هذه الميزة هي سيف ذو حدين. إذا كان [Actual] ممتلئًا عبر SELECT TOP 0 وفي وقت ما يظهر عمود غير متوقع ، فإن مثل هذه الحالة لن تخترقها. يجب عليك كتابة شيكات إضافية لتغطية هذا.

AssertEqualsTable إجراءات التوأم


تجدر الإشارة إلى أن tSQLt يحتوي على 2 إجراءات مثل AssertEqualsTable. هم AssertEqualsTableSchema و AssertResultSetsHaveSameMetaData. أول واحد يفعل نفس AssertEqualsTable لكن على البيانات الأولية للجداول. والثاني يفعل الشيء نفسه ولكن في مجموعات البيانات الوصفية.

مثال رقم 2: القيود


يوضح لنا المثال السابق كيف يمكننا إزالة القيود. ولكن ماذا لو كنا بحاجة للتحقق منها؟ من الناحية الفنية ، تعد القيود جزءًا من المنطق أيضًا ، ويمكن اعتبارها مرشحًا للتغطية من خلال الاختبارات.

النظر في الموقف من المثال السابق. 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) 

تم التقاط الاستثناء. تمرير الاختبار.

ApplyConstraint التوأم الإجراءات


طريقة اختبار المشغلات المقترحة من قبل مؤلفي tSQLt تشبه قيود الاختبار. يمكننا استخدام الإجراء ApplyTrigger لإعادة المشغل إلى الجدول. بعد ذلك ، كل شيء يسير كما هو موضح في المثال أعلاه - ابدأ تشغيل المشغل ، تحقق من النتيجة.

ExpectNoException - متناقض ExpectException


يوجد إجراء ExpectNoException للحالات التي يجب ألا يحدث فيها استثناء. يعمل بنفس الطريقة التي يعمل بها ExpectException فيما عدا فشل الاختبار في حالة حدوث استثناء.

مثال رقم 3: إشارة


هناك بعض الإجراءات المخزنة وخدمات ويندوز. يمكن أن يكون سبب تنفيذها تنفيذها أحداث خارجية مختلفة. ومع ذلك ، فإن ترتيب تنفيذها ثابت. لذلك ، يجب تطبيق التحكم في الوصول على الجانب DB - أي إشارة. في حالتنا ، فإن الإشارة هي مجموعة من الإجراءات المخزنة تعمل معًا.

لنلقِ نظرة على الإجراء داخل الإشارة. لدينا جدولان - [عملية] و [ProcStatus]:


يحتوي الجدول [Process] على قائمة بالعمليات المسموح بتنفيذها. من الواضح أن [ProcStatus] يحتوي على قائمة حالات العملية من الجدول السابق.

لذا ، ماذا تفعل إجراءاتنا؟ أولاً ، يقوم بالتحققات التالية:

  1. لقد تجاوزنا اسم العملية كأحد معلمات الإدخال الخاصة بالإجراء. يتم البحث عن هذا الاسم في حقل [الاسم] في جدول [معالجة].
  2. إذا تم العثور على اسم العملية ، فإنه يتحقق من [IsRunable] علامة الجدول [Process].
  3. إذا كانت العلامة قيد التشغيل ، فإننا نعتبر أنه يمكن تشغيل العملية. يحدث التحقق الأخير في الجدول [ProcStatus]. نحتاج إلى التأكد من عدم تشغيل العملية حاليًا ، مما يعني عدم وجود سجلات حول العملية مع حالة "InProg" في جدول [ProcStatus].

إذا كان كل شيء على ما يرام وتم اجتياز جميع عمليات الفحص ، فإننا نضيف سجلًا جديدًا حول عمليتنا في جدول [ProcStatus] بالحالة "InProg". يتم إرجاع معرف هذا السجل الجديد مع معلمة الإخراج ProcStatusId.

إذا حدث خطأ ما ، نتوقع ما يلي:

  1. يتم إرسال بريد إلكتروني إلى مسؤول النظام.
  2. يتم إرجاع ProcStatusId = -1.
  3. لم تتم إضافة أي سجلات [ProcStatus] جديدة.

لنقم بإنشاء حالة اختبار للتحقق من حالة غياب العملية في جدول [Process].

نستخدم FakeTable مرة أخرى. إنها ليست حرجة للغاية هنا ، لكنها قد تكون مريحة للأسباب التالية:

  1. إنه مضمون ولن تكون هناك بيانات يمكن أن تعطل تنفيذ حالة الاختبار.
  2. سيتم تبسيط عملية التحقق الإضافية من غياب سجلات [ProcStatus] الجديدة.

 EXEC tSQLt.FakeTable 'dbo.Process'; EXEC tSQLt.FakeTable 'dbo.ProcStatus'; 

يوجد إجراء [SendEmail] اسمه يتحدث عن نفسه. نحن بحاجة للقبض على دعوتها. يقترح tSQLt استخدام SpyProcedure وهمية لذلك.

 EXEC tSQLt.SpyProcedure 'dbo.SendEmail' 

تقوم SpyProcedure بما يلي:

  1. ينشئ جدول باسم يشبه [dbo]. [ProcedureName_SpyProcedureLog]
  2. تمامًا مثل FakeTable ، يستبدل الإجراء الأصلي بالإجراء الذي تم إنشاؤه تلقائيًا ، بنفس الاسم ، ولكن مع منطق التسجيل داخل. يمكنك أيضًا إضافة المنطق الخاص بك إلى الإجراء الذي تم إنشاؤه إذا لزم الأمر.

ليس من الصعب تخمين أن السجلات يتم تسجيلها على [dbo]. [SendEmail_SpyProcedureLog] الجدول. يحتوي هذا الجدول على عمود [_ID_] مخصص لأرقام تسلسل المكالمات. تتم تسمية الأعمدة اللاحقة بعد تمرير المعلمات إلى الإجراء واستخدامها لجمعها ، وبالتالي ، يمكن التحقق من قيم المعلمات أيضًا.


آخر شيء نحتاج إلى القيام به قبل استدعاء الإشارة هو إنشاء متغير لتخزين القيمة [ProcStatusId] (لتكون أكثر دقة ، -1 ، حيث لن تتم إضافة السجل).

 DECLARE @ProcStatusId BIGINT; 

نحن نسمي الإشارة:

 EXEC dbo.[Semaphore_JobStarter]   'SomeProcess',   @ProcStatusId OUTPUT; -- here we get -1 

الآن لدينا جميع البيانات المطلوبة للشيكات. لنبدأ من التدقيق
أن الرسالة قد أرسلت.

 IF NOT EXISTS (   SELECT *   FROM dbo.SendEmail_SpyProcedureLog) EXEC tSQLt.Fail 'SendEmail has not been run.'; 

في هذه الحالة ، لا نتحقق من المعلمات التي تم تمريرها ونختبر حقيقة الإرسال فقط. أريد أن ألفت انتباهكم إلى إجراء فشل. انها تسمح لنا "رسميا" تفشل حالة الاختبار. إذا كنت بحاجة إلى بناء مبنى متطور ، فيمكنك المساعدة في Fail.

الآن نتحقق من عدم وجود سجلات في الجدول [ProcStatus] باستخدام إجراء AssertEmptyTable.

 EXEC tSQLt.AssertEmptyTable 'dbo.ProcStatus'; 

هذا حيث ساعدنا FakeTable في البداية. مع ذلك ، قد نتوقع جدولًا فارغًا واختباره باستخدام سطر واحد من التعليمات البرمجية. تتمثل الطريقة الصحيحة للتحقق من ذلك دون تزوير الجدول في مقارنة عدد الصفوف قبل تنفيذ الإجراء وبعده ، وهذا يتطلب المزيد من الإجراءات.

يمكننا بسهولة التحقق من ProcStatusId = -1 المساواة مع AssertEquals.

 EXEC tSQLt.AssertEquals   -1,       @ProcStatusId,       'Wrong ProcStatusId.'; 

AssertEquals هو أضيق الحدود. يقارن فقط 2 القيم ، لا شيء غير عادي.

تأكيد إجراءات التوأم


لدينا الإجراءات التالية لمقارنة القيم:

  • AssertEquals
  • AssertNotEquals
  • AssertEqualsString
  • AssertLike

الأسماء واضحة بذاتها ، على ما أعتقد. الإجراء الوحيد الذي أريد التأكيد عليه هو AssertEqualsString. إنه الإجراء المخصص للتحقق من قيم السلسلة. لماذا نحتاج إلى إجراء واحد ، مع الأخذ بعين الاعتبار AssertEquals العالمي؟ الشيء ، AssertEquals / AssertNotEquals / AssertLike العمل مع نوع SQL_VARIANT. لا يتم تضمين NVARCHAR (MAX) في SQL_VARIANT ، لذلك كان على مطوري tSQLt اتخاذ إجراء إضافي.

وظيفة وهمية


بكبسة واحدة ، يمكننا استدعاء 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;       -- something useful   IF @isNestedTransaction = 'false'   COMMIT TRANSACTION; END TRY BEGIN CATCH   DECLARE @isCommitable BIT =       CASE WHEN XACT_STATE() = 1           THEN 'true'           ELSE 'false'   END;   IF @isCommitable = 'true' AND @isNestedTransaction = 'true'       ROLLBACK TRANSACTION SavepointName;   ELSE       ROLLBACK;   THROW; END CATCH; 

دعنا نراجع الكود قطعة قطعة. أولاً ، نحتاج إلى تحديد ما إذا كنا داخل معاملة أم لا.

 DECLARE @isNestedTransaction BIT =   CASE WHEN @@trancount > 0       THEN 'true'       ELSE 'false' END; 

بعد اشتقاق علامةisNestedTransaction ، يمكننا بدء TRY-block وتعيين نقطة حفظ أو بدء معاملة بناءً على الموقف.

 BEGIN TRY   IF @isNestedTransaction = 'false'       BEGIN TRANSACTION   ELSE       SAVE TRANSACTION SavepointName;       -- something useful 

بعد القيام بشيء مفيد ، نلتزم بالنتائج إذا كان الإجراء "حقيقيًا".

        -- something useful   IF @isNestedTransaction = 'false'   COMMIT TRANSACTION; END TRY 

بالطبع ، إذا كان الأمر يتعلق بحالة اختبار ، فلن نحتاج إلى ارتكاب أي شيء. سوف tSQLt لفة التغييرات مرة أخرى في النهاية تلقائيا.

إذا حدث خطأ ما ووصلنا إلى كتلة CATCH ، فنحن بحاجة إلى تحديد ما إذا كانت المعاملة قابلة للتحويل أم لا.

 BEGIN CATCH   DECLARE @isCommitable BIT =       CASE WHEN XACT_STATE() = 1           THEN 'true'           ELSE 'false'   END; 

لا يمكننا التراجع إلى نقطة التوفير إلا إذا:

  1. المعاملة صالحة
  2. إنه اختبار تشغيل ، لذلك ، يوجد نقطة حفظ.

في جميع الحالات الأخرى ، يجب أن نراجع المعاملة بالكامل.

    IF @isCommitable = 'true' AND @isNestedTransaction = 'true'       ROLLBACK TRANSACTION SavepointName;   ELSE       ROLLBACK;   THROW; END CATCH; 

نعم ، لسوء الحظ ، إذا وصلنا إلى حالة معاملة غير قابلة للتطبيق أثناء التشغيل التجريبي ، فإننا لا نزال نتلقى خطأ التنفيذ.

Faketable والقضية الرئيسية الأجنبية


دعنا نراجع جداول [Trial] و [Clinic] المألوفة


نتذكر عن [TrialID] FK. ما القضية التي يمكن أن تسببها؟ في الأمثلة أعلاه ، طبقنا FakeTable على كلا الجدولين. إذا استخدمناه على واحد منهم فقط ، فسنصل إلى الإعداد التالي:


لذا ، قد تفشل محاولة إدراج سجل في [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] ، الذي تم إنشاؤه بواسطة مطورينا. في هذه الحالة ، لم يكن مطلوبًا عن طريق الاختبار فقط. كان من الضروري إنشاء إجراء منفصل لأنه ضروري لإعداد بعض البيانات قبل الإرسال. ولكن يجب أن تكون مستعدًا ذهنياً لكتابة إجراء طبقة البينية من أجل تحقيق أهداف الاختبار.

الايجابيات


تركيب سريع


يتكون تركيب tSQLt من خطوتين ويستغرق حوالي دقيقتين. تحتاج إلى تنشيط CLR إذا لم تكن نشطة حاليًا وتنفيذ برنامج نصي SQL واحد. هذا كل شيء: الآن يمكنك إضافة صف الاختبار الأول وكتابة حالات الاختبار.

التعلم السريع


tSQLt سهل التعلم. استغرق الأمر أكثر من يوم عمل لي قليلاً. سألت الزملاء ويبدو أنه يستغرق حوالي يوم عمل للآخرين أيضًا. أشك في أنه يمكن أن يستغرق المزيد من الوقت.

التكامل السريع CI


استغرق الأمر حوالي ساعتين لإعداد تكامل CI في مشروعنا. يمكن أن يختلف الوقت ، بالطبع ، لكنه ليس مشكلة بشكل عام ، ويمكن القيام به بسرعة.

مجموعة واسعة من الصكوك


إنها شخصية ، لكن من وجهة نظري ، تكون وظيفة 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 دون أي مشاكل. على وجه التحديد ، نستخدم tSQLt مع Atlassian Bamboo.

دعم ريدجيت


بصفتي أحد المحترفين ، يمكنني تسمية دعم أحد أكبر مزودي أدوات DBA - RedGate. البرنامج المساعد لـ SQL Server Management Studio المسمى اختبار SQL يعمل مع 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 #Actual   FROM [AdventureWorks2017].[Person].[Password]   SELECT *   INTO #Expected   FROM (          SELECT 'bE3XiWw=' AS [PasswordSalt]   ) expectedresult;   EXEC tSQLt.AssertEqualsTable '#Expected', '#Actual', 'The salt is not salty'; END 


البق الوثائق


على الرغم من أنني ذكرت أعلاه أن الأدلة مريحة ومتسقة ، فإن الوثائق لديها بعض القضايا. أنه يحتوي على أجزاء قديمة.

مثال 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 = ''; /* [NB] Why don't we use ExceptException below? */ 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 

وهذا متوقع ، بسبب ...

التخلي الجزئي


كان هناك توقف لفترة طويلة في التطوير من بداية عام 2016 حتى يونيو 2019. نعم ، للأسف ، تم التخلي عن هذه الأداة جزئيًا. بدأ التطوير ببطء في عام 2019 ، وفقًا لجيثب . على الرغم من أن مجموعات Google الرسمية تحتوي على سلسلة رسائل حيث سئل سيباستيان ، مطور tSQLt الرئيسي ، عن مستقبل المشروع. تم طرح السؤال الأخير في 2 مارس 2019 ، بدون إجابة.

مزود خادم 2017 القضية


قد يتطلب تثبيت tSQLt بعض الإجراءات الإضافية إذا كنت تستخدم SQL Server 2017. نفذت Microsoft أول تغيير أمني منذ عام 2012 في هذا الإصدار. تمت إضافة علامة على مستوى الخادم "أمان صارم CLR". لا يسمح هذا العلم بإنشاء التجميعات غير الموقعة (حتى SAFE). الوصف التفصيلي يستحق مقالة منفصلة (ولحسن الحظ ، لدينا بالفعل مقال جيد ؛ انظر أيضًا المقالات التالية في التسلسل. فقط كن مستعدًا عقلياً لذلك).

بالطبع ، يمكن أن أرجع هذه المشكلة إلى "المزالق" ، ولكن يمكن حل هذه المشكلة عن طريق مطوري tSQLt. لقد تم بالفعل رفع قضية جيثب . ومع ذلك ، لم يتم حلها منذ أكتوبر 2017.

بدائل (for) لنظام إدارة قواعد البيانات الأخرى


tSQLt ليست واحدة من نوع ما. على الرغم من أنه لا يمكنك استخدامه في أنظمة إدارة قواعد البيانات الأخرى بسبب الفروق الدقيقة في CLR و T-SQL ، فلا يزال بإمكانك العثور على شيء مشابه. تجدر الإشارة إلى أن هذه البدائل ليست قريبة جدًا من tSQLt ، لذلك أقصد منهج SQL.

على سبيل المثال ، يمكن لمستخدمي PostgreSQL تجربة pgTAP . إنها أداة متطورة ومطورة بنشاط باستخدام PL / pgSQL الأصلي للاختبارات وتنسيق الإخراج TAP. يمكن أن تساعدك الأداة المشابهة MyTap في إجراء اختبارات تحت MySQL. هذا الإطار أقل فعالية من pgTAP ولكنه لا يزال مفيدًا. وفي تطور نشط أيضا. إذا كنت من مستخدمي Oracle سعداء ، فلديك فرصة لاستخدام أداة قوية جدًا. إنه يتطور بشكل نشط ويوفر عددًا كبيرًا من الميزات.

استنتاج


أردت أن أنقل فكرتين:

الأول: فائدة اختبار رمز DB. ليس من المهم إذا كنت تستخدم SQL Server أو Oracle أو MySQL أو أي شيء آخر. إذا كانت قاعدة بياناتك تحتوي على منطق لم يتم اختباره ، فأنت تتحمل المخاطر. نظرًا لأن جميع الأخطاء الأخرى في جميع التعليمات البرمجية الأخرى ، يمكن أن تتسبب أخطاء رمز DB في تلف المنتج والشركة التي توفره.

والثاني: اختيار الأداة. بالنسبة لأولئك الذين يعملون مع SQL Server ، فإن tSQLt ، حتى لو لم يكن فائزًا بنسبة 100٪ ، يستحق بالتأكيد الاهتمام. على الرغم من التطور البطيء وبعض المشكلات ، فإنه لا يزال إطارًا عمليًا يمكن أن يجعل عملك أسهل كثيرًا.

المصادر التي ساعدتني (قائمة غير شاملة)
DbFit - الاختبار التلقائي لقاعدة البيانات مفتوحة المصدر: http://www.methodsandtools.com/tools/dbfit.php

وثائق DbFit: https://dbfit.imtqy.com/dbfit/docs/

سلاكي ويكي: https://github.com/vassilvk/slacker/wiki

وثائق TST: https://archive.codeplex.com/projects/TST/4e04e281-9f35-4891-809a-15f09d304f4e

تأكيدات NUnit: https://github.com/nunit/docs/wiki/Assertions

شفرة utTSQL: https://sourceforge.net/p/uttsql/code/HEAD/tree/

تأكيد Junit Class: https://junit.org/junit4/javadoc/latest/org/junit/Assert.html

pgTap: https://pgtap.org/

utPLSQL: http://utplsql.org/

MyTap: https://github.com/hepabolu/mytap

مجموعات Google tSQLt: https://groups.google.com/forum/#!forum/tsqlt

الموقع الرسمي tSQLt: https://tsqlt.org/

tSQLt GitHub: https://github.com/tSQLt-org/tSQLt

اتجاهات Google: https://bit.ly/2x7BQL6

كيفية استعادة المعاملة عند الاختبار باستخدام tSQLt: https://stackoverflow.com/questions/8973138/how-to-rollback-a-transaction-when-testing-using-tsqlt

ما هي إيجابيات وسلبيات اختبار الوحدة اليدوية مقابل اختبار الوحدة الآلي؟: https://stackoverflow.com/questions/2948337/what-are-the-pros-and-cons-of-manual-unit-testing-against - وحدة الأتمتة - # 2948354

الخير والشر والأوغلي: https://sqlquantumleap.com 2017/08/07/sqlclr-vs-sql-server-2017-part-1-clr-strict-security/

Rex Black ، Erik Van Veenendal ، Dorothy Graham ، أسس اختبار البرمجيات ، الإصدار الثالث ، 2012 Cengage Learning EMEA

Source: https://habr.com/ru/post/ar465567/


All Articles