نحن من اختبار آخر - نحن نختبر قاعدة البيانات على MSTest

اختبار كمبدأ عالمي


لقد احتفلنا بالألفية الثالثة لربع قرن تقريبًا ، والاختبار بدأ في حياتنا ... من الصعب إقناع المطورين المبتدئين باستخدام هذه التقنية المذهلة في عملهم ... ماذا يمكننا أن نقول عن المطورين ، مجرد بشر ، وليس من الممكن دائمًا فهم أن الاختبار هو الأساس نظم مستدامة! ما مدى صعوبة إقناع البائعة أن اختبار منتج جديد لا يعني تناوله! من الواضح أن حراس الأمن ذوي الخبرة يعملون بالطريقة القديمة - فهم يحاولون اللحاق بالاختيار وتحديد الاختبار. ولن تثبت لهم أنه إذا كان الرب الرب نفسه لا يحتقر لاستخدام TDD في عمله (تذكر الطوفان العظيم) ، ثم كما يقولون ، أمر الله نفسه ...

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

حسنًا ، نحن معك من اختبار آخر ، أول اختبار - ثم كل شيء آخر!

أنا أدرك اختبار بواسطة مشية ...


وهكذا ، عندما بدأت في كتابة أول قاعدة بيانات للتعليمة البرمجية التالية ، فكرت في الأمر ، فلماذا لا نجري الاختبار التلقائي لطبقة DAL الخاصة بي مباشرةً على الاختبارات المضمنة في VisualStudio؟

وفعلت ذلك! شفاف في EntityFramework ، دون أي خفة في اليد تحت الأغطية والاحتيال بأشياء وهمية. الذي يهتم - كشف VS ، واللباس مثل اختبار وتذهب! (أنا دائما ارتداء مثل اختبار)

اختبار الملابس
صورة

كذب على الجميع ، هذا هو الاختبار!


حالة الحياة:
عملت في مشروع مع الكود التالي:

ObjectLink link = this.ObjectLinks.ToList().Where(x => x.SpotCode.ToLowerInvariant() == code.ToLowerInvariant()).SingleOrDefault(); 

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

لقد كان خريفًا شاعريًا في سان بطرسبرغ ، تساقطت الثلوج والأمطار بلطف على وجهي ، وسقطت الأوساخ بمرح من ساقي البنطلون والفتيات غير المألوفات اللائي ابتسمن من خلال الماكياج المنتشر ، صب الشاحنات المارة ... ذهبت بالفعل لإصبعي في أنفي ، عندما كانت غادرة ، قبل أن أتعرض للحرب ، قبل عند الفجر ، قامت Microsoft بقطع الأسلاك الشائكة وأصدرت التحديث الأساسي 3.0. تم تحديث المضيف ، لقد قمت بتحديثه أيضًا ، فلماذا في القديم - هل أنا أسوأ من المضيف؟ راجعت كل شيء مثله وطرح التحديث ... ثم خرجت عيني! وظائف جديدة لم تنجح! يبدو أنني اختبرت ذلك من قبل - ماذا يمكن أن يحدث؟

وهذا ما حدث: قرر Billy القديم قطعه من LINQ ToLowerInvariant ... الآن تحتاج إلى الاتصال به مقدمًا وإدراج القيمة النهائية ... إذا كانت الشفرة مغطاة بالاختبارات ، فسوف ألاحظها فورًا عند الاختبار. من الجيد أنني لاحظت كل شيء بنفسي ، ولم يكن من الضروري أن أقسم العميل ، لأن المختبر كان يخجل من احمرار اللون ... كان عليّ حل المشكلة وإجراء عملية نشر جديدة.

الأجهزة والمواد:


Microsoft VisualStudio 2019
asp.net Core 3.1 (قمت بتثبيته مع الاستوديو ، إذا كان يمكن تسليم أي شيء عبر قائمة مشاريع تثبيت أطر أخرى)
مزود خادم اكسبرس (يأتي مع الاستوديو)
بوابة التمديد لاستوديو البصرية (شمل)

عادة ، في اختبارات الوحدة ، يجب عزل كل اختبار ، والحالة بينهما ليست قائمة. لذلك سوف نحصل في الواقع على اختبارات التكامل ، لكننا سنستخدم MSTest لهذا الغرض. آمل ألا يأخذونا إلى الشرطة من أجل هذا.

في العديد من الإصدارات ، قابلت استخدام كائنات وهمية لاختبار قاعدة البيانات.
للوهلة الأولى ، الفكرة جيدة حتى تبدأ التفاعلات المعقدة بين الجداول.
بعد ذلك سيستغرق إعداد وهمية أكثر من اختبار نفسه. ولكن في الواقع هو - التحكم + C - التحكم + V! يتم تكرار جميع قيود قاعدة البيانات التي تم تسجيلها بالفعل في EF أو قاعدة البيانات أو DataAnnotations أو FluentAPI في طبقة وهمية. والنسخ هو نوع من مثل كسر نمط ... آية ، مواطن ، كسر ... ليست جيدة!

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

كل هذا يهمني ، وقررت اختبار نهج جديد.

جاءت الفكرة ، كما هو الحال دائمًا ، من TRIZ: النظام المثالي هو النظام الغائب ، لكن وظائفه تنفذ . واعتقدت أنني بحاجة لاستخدام قاعدة البيانات نفسها في الاختبارات.
ونوع من العمل بها بالنسبة لي. أريد أن أشارك هذا ، وآمل أن يساعد شخص ما.

سلبيات وهمية:

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

إيجابيات الاختبار على قاعدة حقيقية:

  • البرنامج يتصرف تماما مثل على خادم القتال
  • الاختبارات أكثر بساطة ، يمكنك بناءها واحدة تلو الأخرى بنفس الطريقة التي يتم بها ملء البيانات في قاعدة البيانات
  • يمكننا نحن أنفسنا ضبط نظافة قاعدة البيانات عن طريق ترقيم الاختبارات (في MSTest يتم إجراؤها أبجديًا)
  • يمكنك معرفة الوقت الذي يتم فيه إجراء الاختبار (على الخادم الحقيقي ، سيكون الأمر مختلفًا ، ولكن على الأقل يكون الترتيب مرئيًا - 10 مرات أطول ، مرتين ، ويمكنك بالفعل تقييم كيفية عمل البرنامج ، بكفاءة أو لا)
  • يمكن اختبار الإجراءات المخزنة

هناك بعض الصعوبات في هذا النهج ، الذي كنت أحفره لعدة أيام ، لكننا سنحلها بنجاح ، وربما تكون حجري الخلفية معك!

دعنا نذهب!

نقوم بإنشاء مشروع جديد ASP.Net Core 3.1 Web Application (Model-View-Controller) ، وتغيير المصادقة إلى حسابات المستخدمين الفرديين (تخزين حسابات المستخدمين داخل التطبيق) وانقر فوق إنشاء

صورة

من الآن فصاعدًا ، سأحفظ لقطات المشروع في بوابة ، ويمكنك تنزيلها وتحميل كل فرع من أجل تجربته

github.com/3263927/Habr_1

لقطة: Snapshot_0_ProjectCreated

عن مستودع
حتى عندما أعمل بمفردي ، فإنني أستخدم المستودع دائمًا - فقد أصبح الآن مناسبًا جدًا ، تم تصميمه في Visual Studio ، بدون سطر أوامر ، كل شيء يعمل بشكل صحيح تمامًا من VS. يمكنك تجربة وتغيير ما تريد ، ثم يمكنك دائمًا إصلاح التراجع عن الالتزام أو التبديل إلى الفرع القديم. يوفر الكثير من الوقت والجهد ، أنصح الجميع. ويتكامل مع جيثب مجانا. صحيح ، كان هناك بعض المتأنق منذ بضع سنوات الذي حذف كل شيء ... لذلك فقط في حالة ، وضعت جميع المشاريع في Dropbox وتحديث مرة واحدة في الأسبوع ، وكذلك أرشفة جميع المشاريع وتحميل أحدث الإصدارات يدويا إلى Google Drive. حسنًا ، يوجد على هاتف SD 120 قطعة ، وهناك أيضًا ، في وضع الإحتياطي ، فجأة ، فجأة ... زوجان من محركات أقراص فلاش مع نسخ في جيوبهما غير مرئي للغاية!

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

سوف أقوم بإنشاء فرع جديد في المستودع مباشرةً من VisualStudio ، وسأطلق عليه "Sister of Talent" للاختصار (joke ، Snap_1_DataBases).

الهدف: لإنشاء اتصال وقاعدة العمل .

لقد بدأنا في إنشاء قواعدنا.

يجب أن أقول على الفور أنه سيكون لدينا 3 قواعد بيانات - اختبار واحد (على الجهاز المحلي) ، وإنتاج آخر (على الخادم البعيد ، والعمل) وآخر محلي (للتحقق من صحة الموقع في تكوين DEBUG).

المنطق هو هذا:

  • إذا كنا نريد تشغيل الموقع على الجهاز المحلي ومعرفة كيفية عمله ، فإن Habr1_Local سيعمل لصالحنا
  • إذا وضعنا الكود في الإنتاج فإن Habr1_Production سيعمل
  • عندما تبدأ البنية الأساسية للاختبار الخاصة بنا في الاختبار ، يجب أن تعثر على قاعدة Habr1_Test وتشغيلها

ومع ذلك ، لدينا تناقض واحد - هناك فقط تكوينان ، Debug و Release. لا تزال هذه مشكلة ، ولكن بعد ذلك سيتم حلها.

لذلك ، فإننا نقوم بإنشاء برنامج عمل بسيط - لبداية نتحقق مما إذا كانت هناك قاعدة بيانات واحدة على الأقل ملائمة لنا. لنقم بإنشائها ... بأيدي Visual Studio نفسها!

افتح ملف appsettings.json

هناك مثل هذه الخطوط:

 "ConnectionStrings": { "DefaultConnection": "Server=(localdb)\\mssqllocaldb;Database=aspnet-WebApp-[- ,   ];Trusted_Connection=True;MultipleActiveResultSets=true" }, 

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

لماذا هذا مطلوب؟

تسمح لك تكوينات Visual Studio بتغيير بعض الإعدادات عن طريق تبديل التكوين في لوحة Visual Studio:

صورة

بشكل عام ، تعلن بيئة التطوير ببساطة عن ثابت DEBUG ، ويمكن بعد ذلك قراءته من أي مكان في الكود وفهم التكوين الذي نستخدمه حاليًا ، أي التكوين النشط.

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

لذلك ، نبدأ في تغيير سلسلة الاتصال.

اسم الخادم - يمكنك رؤيته في عرض علامة التبويب -> مستكشف كائن خادم SQL

صورة

(سأقوم بحذف اسم جهاز الكمبيوتر الخاص بي بحكمة ، وإلا فسوف تقوم بحسابي بواسطة IP واكتب شيئًا ما).

لذلك لدي هذا (localdb) \ ProjectsV13. لا أعرف لماذا ، أثناء التثبيت ، اتصلت بـ SQL.
وهذا يعني أن سلسلة الاتصال لدينا أصبحت

 "DefaultConnection": "Server=(localdb)\\ProjectsV13;Database=Habr1_Local; Trusted_Connection=True;MultipleActiveResultSets=true" 

قد يكون لديك بشكل مختلف ، ولكن فقط ProjectV13. يجب ترك الباقي هكذا.
تغيير DefaultConnection إلى Habr1_Local

اتضح مثل هذا:

  "ConnectionStrings": { "Habr1_Local": "Server=(localdb)\\ProjectsV13;Database=Habr1_Local;Trusted_Connection=True;MultipleActiveResultSets=true" }, 

أنت الآن بحاجة إلى الانتقال إلى ملف Startup.cs واستبدال DefaultConnection بـ Habr1_Local هناك:

 services.AddDbContext<ApplicationDbContext>(options => options.UseSqlServer(Configuration.GetConnectionString("DefaultConnection"))); 

يتحول إلى

 services.AddDbContext<ApplicationDbContext>(options => options.UseSqlServer(Configuration.GetConnectionString("Habr1_Local"))); 

نبدأ مشروعنا في تكوين Debug ، يفتح المتصفح ، ونرى الصفحة الأولى ، انقر فوق زر تسجيل الدخول ، وأدخل أي بريد إلكتروني صالح هناك (لن يرسل لك رسائل ، فقط يتحقق من صحة التنسيق) وانقر فوق تسجيل الدخول - ونرى هذه الشاشة:

صورة

انقر فوق "تطبيق الترحيل" ، ونحن ننتظر عندما يظهر التأكيد على يمين الزر الأزرق الذي مرت به الهجرة وتحتاج إلى تحديث الصفحة ، التحديث ، انقر فوق "تأكيد" لإعادة إرسال البيانات والحصول على شاشة تفيد فشل تسجيل الدخول:

صورة

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

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

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

لسوء الحظ ، هذه الأداة معقدة بسبب عدم وجود واجهة مرئية لها ، وتحتاج إلى التعامل معها من سطر الأوامر. تعد عمليات الترحيل ملائمة للاستخدام عندما تحتاج إلى تمرير الحالات عبر Git - ببساطة عن طريق إنشاء لقطات قاعدة البيانات هذه كملفات C #. لكن هذه الأداة لها خطر واحد - إذا تم تكوينها بشكل غير صحيح ، فيمكنها مسح البيانات في قاعدة البيانات ، لذلك يجب عليك استخدامها بحذر.

التحقق من وجود قاعدة البيانات - يجب أن يكون إنشاء Visual Studio

صورة

إذا لم تكن هناك قاعدة بيانات ، فهناك خطأ ما - إما أنه لم يتم تثبيت خادم SQL ، أو أي شيء آخر بشكل عام ، كما هو الحال في نكتة حول كيف أن المبرمجين كانوا أطباء: "طبيب ، ساقي تؤلمني ... - حسنًا ، لا أنا أعلم ، لدي نفس الساق ولا شيء يؤلمني! "

في هذه المرحلة ، أقوم بإنشاء سلسلتين أخريين للاتصال ، تأخذ appsettings.json هذا النموذج:

 "ConnectionStrings": { "Habr1_Local": "Server=(localdb)\\ProjectsV13;Database=Habr1_Local;Trusted_Connection=True;MultipleActiveResultSets=true", "Habr1_Test": "Server=(localdb)\\ProjectsV13;Database=Habr1_Test;Trusted_Connection=True;MultipleActiveResultSets=true", "Habr1_Production": "Server=(localdb)\\ProjectsV13;Database=Habr1_Production;Trusted_Connection=True;MultipleActiveResultSets=true" }, 

أقوم بالالتزام ووضع اللقطة التالية في المستودع:

لقطة: Snap_1_DataBases

إنشاء فرع جديد ، Snap_2_Configurations

الهدف: إنشاء تكوينات العمل

عند التبديل بين التكوينات ، يمكننا أن نفكر من أي مكان في البرنامج في التكوين الحالي (في الواقع ، وليس من أي ، لن يعمل من View - نحن بحاجة إلى إنشاء وظيفة خاصة ، ولكن هذا ليس مهمًا لهذا المشروع):

 #if DEBUG  DEBUG #else  RELEASE ( DEBUG   ) #endif 

افتح الملف Startup.cs وقم بتحويل الأسلوب ConfigureServices إلى هذا:

 public void ConfigureServices(IServiceCollection services) { String ConnStr = ""; #if DEBUG ConnStr = "Habr1_Local"; #else ConnStr = "Habr1_Production"; #endif services.AddDbContext<ApplicationDbContext>(options => options.UseSqlServer( Configuration.GetConnectionString(ConnStr))); services.AddDefaultIdentity<IdentityUser>(options => options.SignIn.RequireConfirmedAccount = true) .AddEntityFrameworkStores<ApplicationDbContext>(); services.AddControllersWithViews(); services.AddRazorPages(); } 

كما ترون ، استبدلنا Habr1_Local بمتغير ، واعتمادًا على التكوين ، ستكون سلسلة الاتصال إما Habr1_Local أو Habr1_Production

يمكنك الآن بدء المشروع والتحقق من كيفية إنشاء قواعد البيانات حسب التكوين

نختار DEBUG على اللوحة ، نبدأ ، تسجيل الدخول ، تطبيق الترحيل ، تحقق من إنشاء قاعدة البيانات (Habr1_Local)

نوقف المشروع ، وحدد تهيئة الإصدار ، ونبدأ ، ونقوم بتسجيل الدخول ، ونطبق الترحيل ، ونتحقق من إنشاء قاعدة البيانات - لدينا قاعدتان.

القيام به!

لقطة: Snap_3_HabrDB

الغرض: إنشاء مشروع قاعدة بيانات منفصلة ، والذي يمكن استخدامه بعد ذلك في مشاريع مختلفة

لماذا مشروع منفصل؟

المشاريع الفردية لها العديد من المزايا:

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

لذلك ، الزر الأيمن على الحل - إضافة -> مجلد حل جديد ، وتسميته DB.
ثم الحق في المجلد الذي تم إنشاؤه - إضافة مشروع جديد -> .net القياسية ، اسم HabrDB.
لسبب ما قمت بإنشائه كـ .net standard 2.0 ، أحتاج إلى تغييره إلى 2.1
(عند إنشائها يوفر مسارًا فعليًا ، اتركه موجودًا في مجلد DB وجسديًا أيضًا).

يبدو مثل هذا بالنسبة لي:

صورة

لذلك ، لدينا بعض ApplicationDBContext في المشروع ، وأنشأنا واحدة أخرى خاصة بنا؟ هل سيتعارضون مع بعضهم البعض؟ الآن سنكون صداقات معهم. سيكون لدينا سياقتان مختلفتان لنفس قاعدة البيانات ، والتي لن تتقاطع من خلال إطار الكيان. سنعطيهم اسم مخطط مختلف: واحد سيبقى dbo بشكل افتراضي ، والآخر سيكون "habr".

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

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

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

أقوم بإنشاء لقطة مشروع جديدة - المرحلة 4.

أهداف هذه المرحلة هي:

  • تغيير المستخدم القياسي إلى متقدم
  • تغيير المستخدم في ملف Srartup.cs
  • تغيير المستخدم في LoginPartial و ViewImports
  • إنشاء ترحيل جديد لإنشاء قاعدة بيانات تلقائيًا بتنسيق جديد

لذلك ، نقوم بنقل فئة ApplicationDBContext من مشروع WebApp إلى HabrDB.

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

نعم ، في هذا المشروع لا توجد حزم ضرورية ، الآن سنقوم بتسليمها.

من خلال nuget ، في مشروع HabrDB ، تحتاج إلى تثبيت Microsoft.AspNetCore.Identity.EntityFrameworkCore.

صورة

نضغط على المصباح الكهربائي ويتيح لنا تثبيت أحدث الإصدارات.

يأخذ ملف SecurityDBContext النهائي (يجب إعادة تسميته أيضًا) في هذا النموذج:

 using System; using System.Collections.Generic; using System.Text; using Microsoft.AspNetCore.Identity.EntityFrameworkCore; using Microsoft.EntityFrameworkCore; namespace HabrDB { public class SecurityDBContext : IdentityDbContext { public SecurityDBContext(DbContextOptions<SecurityDBContext> options) : base(options) { } } } 

بعد التجميع ، نقوم بمعالجة الملف بعناية بأخطاء ، وحقًا - قمنا بحذف ApplicationDBContext واستبدله بـ SecurityDBContext. الآن تحتاج إلى استبدال كافة الارتباطات إلى ApplicationDBContext في المشروع بأكمله بـ SecurityDBContext.

بعد ذلك ، ظهرت مثلثات صفراء في مشروعي على مراجع من WebApp ، والتي تشير إلى أن بعض الروابط تعمل بشكل غير صحيح. قمت بتنظيف المشروع (بناء -> حل نظيف) ، وأغلقت المشروع ، وحذفت جميع دلائل Debug و Release و Obj و Bin من مجلد المشروع ، وبعد ذلك فتحت المشروع مرة أخرى ، لبعض الوقت بحثت عن الروابط الضرورية على الإنترنت وقمت بتحميلها ، والمثلثات اختفى - ثم كل شيء على ما يرام.

الآن احذف مجلد البيانات من مشروع WebApp ، وحذف قواعد البيانات الخاصة بنا (Habr1_Local و Habr1_Production) في نافذة SQL Server Object Explorer وقم بتشغيل المشروع. نحاول تسجيل الدخول - والآن بدلاً من عرض تطبيق الترحيل ، يقدم خطأ.

صورة

هذا صحيح ، لقد حذفنا مجلد البيانات حيث كانت جميع عمليات الترحيل والآن لا يعرف إطار العمل ما يجب القيام به. لكنها كانت رائعة جدا؟ لماذا؟ ثم ماذا سنوسع الآن فئة المستخدم.

أضف ملفًا جديدًا إلى مشروع HabrDB:
ApplicationUser.cs

نرثها من IdentityUser

 using Microsoft.AspNetCore.Identity; using System; using System.Collections.Generic; using System.Text; namespace HabrDB { public class ApplicationUser:IdentityUser { public String NickName { get; set; } public DateTime BirthDate { get; set; } public String PassportNumber { get; set; } } } 

في ملف SecurityDBContext في رأس الفصل ، أضف:

 public class SecurityDBContext : IdentityDbContext<ApplicationUser> 

يعد ذلك ضروريًا حتى يعلم EntityFramework أنه الآن عند إنشاء قاعدة البيانات ، يلزمك استخدام نموذج المستخدم المتقدم من فئة AppllicationUser بدلاً من النموذج القياسي.

يأخذ الأسلوب ConfigureServices في ملف Startup.cs النموذج:

 public void ConfigureServices(IServiceCollection services) { String ConnStr = ""; #if DEBUG ConnStr = "Habr1_Local"; #else ConnStr = "Habr1_Production"; #endif services.AddDbContext<SecurityDBContext>(options => options.UseSqlServer( Configuration.GetConnectionString(ConnStr))); services.AddDefaultIdentity<ApplicationUser>(options => options.SignIn.RequireConfirmedAccount = true) .AddEntityFrameworkStores<SecurityDBContext>(); services.AddControllersWithViews(); services.AddRazorPages(); } 

(استبدال IdentityUser بـ ApplicationUser)

في ملف _ViewImports.cshtml ، أضف السطر

باستخدام HabrDB

الآن ستشاهد جميع المشاهدات مشروعنا مع القاعدة ولن تحتاج إلى الكتابة باستخدام HabrDB في بداية المشاهدات.

في ملف _LoginPartial.cshtml ، قم بتغيير كل IdentityUser إلى ApplicationUser.

باستخدام Microsoft.AspNetCore.Identity
حقن SignInManager
حقن UserManager

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

الآن القيام بالهجرة.

تحتاج إلى فتح وحدة التحكم في حزمة Manager ، وحدد مشروع DB / HabrDB والكتابة إليه

 add-migration initial 

إليك ما حصلت عليه:

صورة

ظهر بابا Migrations في مشروع HabrDB وفيه ملفات ستسمح لنا بإنشاء قاعدة البيانات الخاصة بنا تلقائيًا ، ولكن الآن مع حقولنا الإضافية - NickName ، BirthDate ، PassportNumber.

دعنا نجرب كيف تعمل - فلنعمل ونحاول تسجيل الدخول (لا تقم بالتسجيل ، سيكون عليك إدخال كلمات مرور معقدة هناك):

صورة

لقد عرض علي القيام بالهجرة ووافقت - هذه هي قاعدتنا:

صورة

مع هذه المرحلة ، كل شيء

لقطة: Snap_4_Security جاهز

إنشاء الطلقة الخامسة.

الأهداف:

  • جعل اختبار سلسلة اتصال العمل
  • إنشاء مشروع اختبار قاعدة البيانات
  • جعل مشروع الاختبار إنشاء قاعدة واختبار شيء مفيد

نقر بزر الماوس الأيمن فوق DB daddy ، وقم بإنشاء مشروع جديد - MSTest .net core.
إعادة تسمية الملف الوحيد في هذا المشروع إلى DBTest والتفكير ...
كذلك سيكون من الصعب.

المشكلة

كيف يمكننا إنشاء سياق مضمون للتواصل مع قاعدة بيانات الاختبار ، أي استخدام ConnectionString ليس فقط من مشروع آخر (WebApp) ، ولكن أيضًا الاتصال بطريقة أو بأخرى مع تكوينات Release / Debug؟ .. هل يمكن إنشاء تكوين جديد ، اختبار على سبيل المثال ؟

لا ، هذه خسارة محتملة للبيانات - بطريقة ما ننسى أننا ننسى التبديل من تكوين الاختبار ، انقر فوق الزر "تشغيل كافة الاختبارات" - وهناك حذف قاعدة البيانات بأكملها ... لا ، هذا الخيار لا يعمل ...

لذلك ، سنخلق سياق اختبار بشكل واضح!

افتح الملف HabrDBContext وقم بتغيير محتوياته إلى:

 using System; using System.IO; using Microsoft.EntityFrameworkCore; using Microsoft.Extensions.Configuration; namespace HabrDB { public class HabrDBContext:DbContext { public String ConnectionString = ""; public IConfigurationRoot Configuration { get; set; } public HabrDBContext CreateTestContext() { DirectoryInfo info = new DirectoryInfo(Directory.GetCurrentDirectory()); DirectoryInfo temp = info.Parent.Parent.Parent.Parent; String CurDir = Path.Combine(temp.ToString(), "WebApp"); String ConnStr = "Habr1_Test"; Configuration = new ConfigurationBuilder().SetBasePath(CurDir).AddJsonFile("appsettings.json").Build(); var builder = new DbContextOptionsBuilder<HabrDBContext>(); var connectionString = Configuration.GetConnectionString(ConnStr); builder.UseSqlServer(connectionString); ConnectionString = connectionString; return this; } } } 

من خلال Nuget ، أضف المكتبات التالية إلى قاعدة البيانات:

 Microsoft.AspNetCore.Identity.EntityFrameworkCore Microsoft.EntityFrameworkCore Microsoft.EntityFrameworkCore.SqlServer Microsoft.Extensions.Configuration.FileExtensions Microsoft.Extensions.Configuration.Json 

أسلوب CreateTestContext ببساطة يمكن فقط إرجاع سلسلة الاتصال المسمى Habr1_Test. تحتاج إلى أن تأخذه في مشروع آخر. من أجل عدم توصيل المشروع من خلال المرجع لأننا نحتاج إلى إعداد واحد فقط ، فإننا نطلب من المنشئ إنشاء خيارات بناءً على سلسلة الاتصال المعينة ، ولهذا فإننا نتعرف على سلسلة الأدلة من الدليل حيث يتم ترجمة مشروع الاختبار إلى دليل جذر المشروع ، وجمع المسار إلى مشروع WebApp من الأجزاء ، يرجى إضافة ملف التكوين الخاص بنا إلى appettings.json حيث يتم تخزين إعداداتنا ، وتجميعها في التكوين. علاوة على هذا التكوين ، نأخذ connectionString ونتذكره (سنحتاج هذا لاحقًا).

في المشروعات الكبيرة ، يمكنك نقل الإعدادات إلى مشروع منفصل مع سلاسل أو DLL أو كائن للوصول إلى بيانات الإعدادات مع التخزين المؤقت. في الوقت الحالي ، يكفي اتباع نهج أبسط.

لماذا هذا

يمكنك إجراء اختبار ConnectionString مباشرة في مشروع الاختبار وتهيئة قاعدة البيانات به ، يمكنك إنشاء اختبار ConnectionString في قاعدة البيانات. ولكن بعد ذلك سيكون هناك شيء واحد غير سارة: سيتم تخزين جميع سلاسل الاتصال في أماكن مختلفة. لكنني شخصياً أعرف أن رأسي مليء بالثغرات ويمكنني أن أنسى تغيير شيء ما ، على سبيل المثال ، عندما يتغير اسم قاعدة البيانات أو الخادم ، وبذلك يكون لدي كل خطوط الاتصال في مكان واحد ، أقوم بذلك. الآن ، من خلال تغيير appettings.json ملف التكوين يمكنك إدارة جميع الاتصالات.

الآن دعونا نحاول القيام بشيء مفيد مع القاعدة ، على سبيل المثال ، إنشاء شيء ما.

في مشروع HabrDB ، قمتُ بإنشاء مجلد DBClasses ، ولن يكون هناك سوى فئات جدول قاعدة البيانات هناك. نعمل من خلال الكود أولاً ، إذا كان بإمكاني تخيل ما أقوم به ، فعادةً ما يكون ذلك مناسبًا لي.

قم بإنشاء جدول مثل هذا:

 using System; using System.Collections.Generic; using System.ComponentModel.DataAnnotations; using System.ComponentModel.DataAnnotations.Schema; using System.Text; namespace HabrDB.DBClasses { [Table("Phones", Schema ="Habr")] public class Phone { [Key] public int Id { get; set; } public String Model { get; set; } public DateTime DayZero { get; set; } } } 

من أجل الجمال ، اسمح لي بالصف "الهاتف" والجدول الموجود في قاعدة البيانات في صيغة الجمع - "الهواتف". لذلك ، أود أن أشير في سمة الجدول إلى ما يجب تسميته في جدولي وفي أي مساحة اسم يجب أن تكون في قاعدة البيانات (في SQL يسمى هذا المخطط).

الآن هناك سؤال جمالي آخر: هنا سيكون لدينا مجموعة من أساليب البنية التحتية المختلفة في فئة قاعدة البيانات ، وبعد ذلك سيكون لدينا جداول مختلفة وكل هذا في كومة واحدة - يمكنك إنشاء فئات جزئية.

أضف الكلمة جزئية بعد فئة الكلمة في ملف HabrDBContext.cs - مثل هذا:

 public partial class HabrDBContext:DbContext 

قم بإنشاء نسخة من ملف HabrDBContext.cs - ببساطة CTRL + C - CTRL + V على الملف ، قم بإنشاء نسخة منه ، وقم بتغيير اسم الملف المصدر إلى HabrDBContext_Infrastructure.cs ، والملف الجديد إلى HabrDBContext_Data.cs

في الجديد ، اكتب:

 using HabrDB.DBClasses; using Microsoft.EntityFrameworkCore; namespace HabrDB { public partial class HabrDBContext:DbContext { public DbSet<Phone> Phones { get; set; } } } 

الآن الأمر جميل - نحن نعمل مع البيانات في مكان ما ، مع البنية التحتية في مكان آخر. الملفات مختلفة ، لكن الفصل هو نفسه - البيئة نفسها ستجمعها من عدة إلى واحد عند بناء المشروع.

حسنا ، جربه!

استبدل الكود في صف الاختبار الوحيد بـ:

 using HabrDB; using HabrDB.DBClasses; using Microsoft.VisualStudio.TestTools.UnitTesting; using System; using System.Collections.Generic; using System.Linq; namespace DBTest { [TestClass] public class DBTest { [TestMethod] public void TestMethod1() { String PhoneName = "Nokia"; DateTime now = DateTime.Now; HabrDBContext db = new HabrDBContext().CreateTestContext(); db.Database.EnsureCreated(); List<Phone> Phones = db.Phones.ToList(); Assert.AreEqual(0, Phones.Count); Phone ph = new Phone(); ph.Model = PhoneName; ph.DayZero = now; db.Phones.Add(ph); db.SaveChanges(); Phone ph1 = db.Phones.Single(); Assert.AreEqual(PhoneName, ph1.Model); Assert.AreEqual(now, ph1.DayZero); } } } 

انقر فوق زر التشغيل في علامة التبويب اختبار Explorer (أو اختبار -> تشغيل جميع الاختبارات) و ...

صورة

خطأ!هؤلاء هم على.

نقرأ ما يكتبون لنا:

 Message: Test method DBTest.DBTest.TestMethod1 threw exception: System.InvalidOperationException: No database provider has been configured for this DbContext. A provider can be configured by overriding the DbContext.OnConfiguring method or by using AddDbContext on the application service provider. If AddDbContext is used, then also ensure that your DbContext type accepts a DbContextOptions<TContext> object in its constructor and passes it to the base constructor for DbContext. 

نوع من الهراء باللغة الإنجليزية ...
حسنًا ، سنعمل بشكل عشوائي!

هل يمكن نسخ هذه الوظيفة إلى ملف HabrDBContext_Infrastructure.cs؟ لنجربها!

 protected override void OnConfiguring(DbContextOptionsBuilder optionsBuilder) { String ConnStr = ""; if (Configuration == null) { #if DEBUG ConnStr = "Habr1_Local"; #else ConnStr= "Habr1_Production"; #endif Configuration = new ConfigurationBuilder() .SetBasePath(Directory.GetCurrentDirectory()) .AddJsonFile("appsettings.json").Build(); ConnectionString = Configuration.GetConnectionString(ConnStr); } optionsBuilder.UseSqlServer(ConnectionString); } 

نبدأ ...

كان هذا محظوظا!
تم إنشاء قاعدة جديدة وفيه - طاولتنا!

صورة

لماذا هكذا؟

إذا وضعت بضع نقاط توقف على الدالتين OnConfiguring و CreateTestContext ، فسترى أن الأسلوب CreateTestContext يسمى أولاً ويحفظ سلسلة الاتصال في كائن ConnectionString. كل شيء يبدو موافق. ولكن بعد ذلك يحاول شخص ما الاتصال بـ OnConfiguring ... من هو؟ دعونا نرى مكدس الاستدعاءات - نعم هذا هو خط db.Database.EnsureCreated () من الاختبار! الحقيقة هي أنه ليس لدينا قاعدة على هذا النحو حتى الآن - إن طريقة EnsureCreated تنشئها. ولكن هذه الطريقة لم تعد تأخذ المعلمات ، ويجب الحفاظ على السياق بطريقة ما بين الاستدعاءات إلى المنشئ و EnsureCreated. بالإضافة إلى ذلك ، عندما نستخدم هذا السياق من المشروع نفسه (وليس في الاختبار ، ولكن في الموقع على سبيل المثال) ، ستحاول جميع أنواع برامج middlware و DI وآليات جذابة أيضًا تسميته ، لذلك سنتنبأ بكل شيء مقدمًا - كل من يتصل بقاعدة بياناتنا .إذا كان يريد الاتصال OnConfiguring - سيكون لديه مثل هذه الفرصة. لقد قدمنا ​​لكل شيء.

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

لقطة: Snap_5_ContextCrafting جاهزة

، أولاً ، اكتب DAL - طبقة الوصول إلى البيانات.

قم بإنشاء نسخة من ملف HabrDBContext_Data.cs ، واطلق عليها اسم HabrDBContext_DAL.cs
واكتبها:

 using HabrDB.DBClasses; using Microsoft.EntityFrameworkCore; using System.Collections.Generic; using System.Threading.Tasks; namespace HabrDB { public partial class HabrDBContext:DbContext { public async Task<int> AddPhone(Phone ph) { this.Phones.Add(ph); int res = await this.SaveChangesAsync(); return res; } public async Task<List<Phone>> GetAllPhones() { List<Phone> phones = await this.Phones.ToListAsync(); return phones; } } } 

هذا هو التفاف على بياناتنا. لا أريد إذا قمت بتغيير شيء في الواجهات للوصول إلى قاعدة البيانات ، للتعامل مع البحث في جميع أنحاء المشروع الذي اتصلت به. لذلك ، سوف نجعل طبقة تجريدية - طبقة الوصول إلى البيانات. سيكون الوسيط بين آليات القاعدة و MVC للموقع أو آليات أخرى. و- صدفة؟ - لدينا على الفور كائنات اختبار! سبب آخر لاستخدام الاختبارات هو أنها تثير حلول ذات اتصال منخفض.
تغيير رمز الوظيفة في الاختبارات - وبالنسبة لأحدهم يغير اسمه. الآن نحن نعرف ما نختبره!
إضافة هاتف!

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

رمز وظيفة الاختبار الجديد:

 [TestMethod] public void AddPhone_Test() { String PhoneName = "Nokia"; DateTime now = DateTime.Now; HabrDBContext db = new HabrDBContext().CreateTestContext(); db.Database.EnsureCreated(); List<Phone> Phones = db.GetAllPhones().Result; Assert.AreEqual(0, Phones.Count); Phone ph = new Phone(); ph.Model = PhoneName; ph.DayZero = now; db.AddPhone(ph); Phone ph1 = db.Phones.Single(); Assert.AreEqual(PhoneName, ph1.Model); Assert.AreEqual(now, ph1.DayZero); } 

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

صورة

ما هو db.GetAllPhones (). النتيجة؟ الحقيقة هي أن وظائف DAL لدينا غير متزامنة. لكن طريقة الاختبار في حد ذاتها عادية ، وبالتالي لا يمكن استدعاءها. دعونا نحاول حذف البيانات ، وجعل الطريقة غير متزامنة ونرى ما سيحدث.

لقد أصبحت مهمتنا مهمة غير متزامنة - وإلا لن يبدأ الاختبار للتو ، وحيثما توجد دعوة لطرق غير متزامنة - تحتاج إلى الانتظار قبلها

 [TestMethod] public async Task AddPhone_Test() { String PhoneName = "Nokia"; DateTime now = DateTime.Now; HabrDBContext db = new HabrDBContext().CreateTestContext(); db.Database.EnsureCreated(); List<Phone> Phones = await db.GetAllPhones(); Assert.AreEqual(0, Phones.Count); Phone ph = new Phone(); ph.Model = PhoneName; ph.DayZero = now; await db.AddPhone(ph); Phone ph1 = db.Phones.Single(); Assert.AreEqual(PhoneName, ph1.Model); Assert.AreEqual(now, ph1.DayZero); } 

من المهم أن تعيد جميع وظائف الاختبار التي توجد فيها مكالمات غير متزامنة مهمة وليس لاغية - وإلا فإن الاختبارات لن تبدأ أو لن تنتظر عودة البيانات غير المتزامنة والمضي قدماً وسيكون هناك خطأ.

لذلك ، أكثر أو أقل من الأعمال.

لقطة: Snap_6_Dal

وماذا ، هل من الضروري حذف البيانات يدويًا؟ بالطبع لا!

نحتاج إلى وظيفة حذف الجداول من قاعدة البيانات ...

سيكون من الرائع استخدام db.Database.EnsureDeleted ... وهو بالفعل ممكن!

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

هذا هو الأول.

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

والثالث: ربما في اختبار واحد سنحتاج إلى حذف بعض الجداول أو عدة مرات وإعادة إنشائها ، مع ترك الجداول الأخرى سليمة.

دعنا نحاول استدعاء وظيفة db.Database.EnsureDeleted ، اضغط على القوس وشاهد ما يقبله ، وما إذا كان يحتوي على أحمال زائدة ...

صورة

نعم ، ليس كثيرًا ...
حسنًا ، حسنًا ، دعنا نكتب وظائفنا.

أضف فوراً مشروعًا جديدًا إلى الحل ، (في الحل نفسه) على الحل بالزر الأيمن ، أضف -> .net معيار C # ، واسمه الامتدادات. تأكد من أنه كان الإصدار 2.1.

بعد ذلك ، تحتاج إلى إعادة تسمية الملف الوحيد في مشروع Extensions إلى DBContextExtensions.cs ووضع التعليمة البرمجية التالية هناك:

 using Microsoft.EntityFrameworkCore; using System; using System.Linq; using Microsoft.EntityFrameworkCore.Infrastructure; namespace Extensions { public static class DBContextExtensions { public static int EnsureDeleted<TEntity>(this DatabaseFacade db, DbSet<TEntity> set) where TEntity : class { TableDescription Table = GetTableName(set); int res = 0; try { res = db.ExecuteSqlRaw($"DROP TABLE [{Table.Schema}].[{Table.TableName}];"); } catch (Exception) { } return res; } public static TableDescription GetTableName<T>(this DbSet<T> dbSet) where T : class { var dbContext = dbSet.GetDbContext(); var model = dbContext.Model; var entityTypes = model.GetEntityTypes(); var entityType = entityTypes.First(t => t.ClrType == typeof(T)); var tableNameAnnotation = entityType.GetAnnotation("Relational:TableName"); var tableSchemaAnnotation = entityType.GetAnnotation("Relational:Schema"); var tableName = tableNameAnnotation.Value.ToString(); var schemaName = tableSchemaAnnotation.Value.ToString(); return new TableDescription { Schema = schemaName, TableName = tableName }; } public static DbContext GetDbContext<T>(this DbSet<T> dbSet) where T : class { var infrastructure = dbSet as IInfrastructure<IServiceProvider>; var serviceProvider = infrastructure.Instance; var currentDbContext = serviceProvider.GetService(typeof(ICurrentDbContext)) as ICurrentDbContext; return currentDbContext.Context; } } public class TableDescription { public String Schema { get; set; } public String TableName { get; set; } } } 

أضف Microsoft.EntityFrameworkCore من Nuget
وحزمة أخرى ، Microsoft.EntityFrameworkCore.Relational

Extensions ، هي آلية مريحة للغاية. باستخدامه ، يمكنك إضافة وظائف إضافية إلى كائن الفئة ، وهو ما قمنا به - هذه الكلمة الرئيسية قبل نوع المعلمة الأولى

 public static int EnsureDeleted<TEntity>(this DatabaseFacade db, DbSet<TEntity> set) where TEntity : class 

يشير إلى نوع الموسعة. هذا يعني أنه بعد أن كتبنا هذا ، فإن كائن DatabaseFacade سيكون لديه طريقة جديدة - EnsureDeleted مع المعلمة ، والتي ستكون وظيفتنا التي كتبناها للتو! حيث يشير TEntity: class في النهاية إلى أن TEntity له قيد - وهو قيد قيد الاستخدام ، وإذا حاولنا تعميمه ليس بواسطة فصل ، ولكن بواسطة شيء آخر ، فسيحدث خطأ في الترجمة.

أتوقع سؤالك المنطقي - لماذا هذا؟

بعد ذلك ، تحتاج وظيفة GetTableName التي يتم استدعاؤها من EnsureDeleted إلى هذا التقييد.
لماذا تحتاج هذا التقييد؟

بعد ذلك ، ما هي وظيفة GetDbContext ، والتي تسمى من GetTableName ، تحتاج أيضًا إلى هذا التقييد ...

ولماذا تحتاج هذا التقييد ؟؟؟ !!! تسأل في نغمات مرتفعة ، وسوف تكون على حق ...

dbSet الذي نحاول توسيعه باستخدام أسلوب GetDbContext في السطر

 Public static DbContext GetDbContext<T>(this DbSet<T> dbSet) where T : class 

, T , class …

, — , — EnsureDeleted . . DBContext , , , , , ( GetTableName), , EnsureDeleted — ! removetable - , - SQL … - !

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

أضف الطريقة للاختبارات (في النهاية)

 [TestMethod] public void DeleteTable_Test() { HabrDBContext db = new HabrDBContext().CreateTestContext(); db.Database.EnsureDeleted(db.Phones); } 

تشغيل ، تحقق ، الزفير ...
تشغيل مرة أخرى - يجب أن تعمل.

الآن ، في كل مرة تقوم فيها بإجراء الاختبارات ، سيتم إنشاء الجداول اللازمة ، ثم يتم حذفها.

لقطة: Snap_7_Extensions

إلى آفاق جديدة للبواسير

ولكن هناك مشكلة واحدة - كائن HabrDBContext غير ثابت معنا (يتم إنشاؤه في كل طريقة اختبار). (حسنًا ، نعم ، يجب عزل فكرة Kagbe عن اختبارات الوحدة. ونحن نحاول كسرها! من الجيد ألا ترى الشرطة ...)

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

 HabrDBContext db = new HabrDBContext().CreateTestContext(); db.Database.EnsureCreated(); 

واتصل بالطريقة الأخيرة

 HabrDBContext db = new HabrDBContext().CreateTestContext(); db.Database.EnsureDeleted(db.Phones); 

وبعض الجداول الأخرى ...

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

الطبقات لديها مثل هذه الميزة المثيرة للاهتمام - أعضاء ثابتة.

يبدو الأمر كما لو كنت تتخيل رسمًا وكائنًا تم إنشاؤه من هذا الرسم ، وتبين أن العضو الثابت هو عضو غير متأصل في الكائن الذي تم إنشاؤه من الرسم ، ولكن هذا الرسم نفسه.

هذا مثير للاهتمام بالفعل ...
الآن دعونا نحاول!

تسمى فئة الاختبار الخاصة بنا DBTest ، قم بإنشاء كائن ثابت له - db

 using HabrDB; using HabrDB.DBClasses; using Microsoft.VisualStudio.TestTools.UnitTesting; using System; using System.Collections.Generic; using System.Linq; using System.Threading.Tasks; using Extensions; namespace DBTest { [TestClass] public class DBTest { public static HabrDBContext db; [TestMethod] public void AA0_init() { db = new HabrDBContext().CreateTestContext(); db.Database.EnsureCreated(); } [TestMethod] public async Task AddPhone_Test() { String PhoneName = "Nokia"; DateTime now = DateTime.Now; List<Phone> Phones = await db.GetAllPhones(); Assert.AreEqual(0, Phones.Count); Phone ph = new Phone(); ph.Model = PhoneName; ph.DayZero = now; await db.AddPhone(ph); Phone ph1 = db.Phones.Single(); Assert.AreEqual(PhoneName, ph1.Model); Assert.AreEqual(now, ph1.DayZero); } [TestMethod] public void DeleteTable_Test() { db.Database.EnsureDeleted(db.Phones); } } } 

إنه يعمل!

ولكن هناك شيء غبي لعدد الطرق مثل هذا.
هناك حل!
سمات اختبار خاص!
في مشروع الاختبار ، قم بإنشاء فئة جديدة تسمى DBTestBase ، ورث فئة DBTest الخاصة بنا منه.
من DBTest تحتاج إلى إزالة كل شيء باستثناء:

 using HabrDB; using HabrDB.DBClasses; using Microsoft.VisualStudio.TestTools.UnitTesting; using System; using System.Collections.Generic; using System.Linq; using System.Threading.Tasks; using Extensions; namespace DBTest { [TestClass] public class DBTest:DBTestBase { [TestMethod] public async Task AddPhone_Test() { String PhoneName = "Nokia"; DateTime now = DateTime.Now; List<Phone> Phones = await db.GetAllPhones(); Assert.AreEqual(0, Phones.Count); Phone ph = new Phone(); ph.Model = PhoneName; ph.DayZero = now; await db.AddPhone(ph); Phone ph1 = db.Phones.Single(); Assert.AreEqual(PhoneName, ph1.Model); Assert.AreEqual(now, ph1.DayZero); } [ClassCleanup] public static void DeleteTable() { db.Database.EnsureDeleted(db.Phones); } } } 

محتويات فئة DBTestBase:

 using HabrDB; using Microsoft.VisualStudio.TestTools.UnitTesting; namespace DBTest { [TestClass] public class DBTestBase { public static HabrDBContext db{ get; set; } /// <summary> /// Executes once before the test run. (Optional) /// </summary> /// <param name="context"></param> [AssemblyInitialize] public static void AssemblyInit(TestContext context) { db = new HabrDBContext().CreateTestContext(); db.Database.EnsureCreated(); } /// <summary> /// Executes before this class creation /// </summary> /// <param name="context"></param> [ClassInitialize] public static void TestFixtureSetup(TestContext context) { } /// <summary> /// Executes Before each test /// </summary> [TestInitialize] public void Setup() { } /// <summary> /// Executes once after the test run /// </summary> [AssemblyCleanup] public static void AssemblyCleanup() { } /// <summary> /// Runs once after all tests in this class are executed. /// Not guaranteed that it executes instantly after all tests from the class. /// </summary> [ClassCleanup] public static void TestFixtureTearDown() { } /// <summary> /// Executes after each test /// </summary> [TestCleanup] public void TearDown() { //db.Database.EnsureDeleted();//don`t call! delete database instead of tables! } } } 

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

أضف السمة [ClassCleanup] إلى أي طريقة في أي فصل - واحصل على اختبار "أنظف" - مثل هذه الوظيفة التي ستقوم بشيء ما بعد اكتمال جميع اختبارات هذه الفئة.

على سبيل المثال ، سيتم حذف جميع الجداول التي تم إنشاؤها في هذا الاختبار دون لمس قاعدة البيانات نفسها.

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

يمكنك أيضًا إنشاء قوائم تشغيل من الوظائف.

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

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

وبالتالي ، يمكننا تقليد بعض إجراءات المستخدم من العمليات التجارية الحقيقية.

الشيء الرئيسي الذي يجب تذكره هو أنه خارج سمات الاختبار هذه ، تسمى الاختبارات بالترتيب الأبجدي.

لذلك ، إذا كنت بحاجة إلى نوع من التسلسل الساكن ، فيجب عليك تسميته:

T1_AddPhone_Test ،
T2_RemovePhone_Test ،
وما إلى ذلك ...

حسنًا ، الآن لا يمكنك القلق بشأن قاعدة البيانات الخاصة بنا - سيتم اختبار كل شيء بالكامل!
والآن حان وقت النوم! لا يزال لدي فتاة هناك لم تختبر ... اختبار

ناجح! وداعا للجميع!

مشروع مستودع الجيت: https://github.com/3263927/Habr_1

اكتب التعليقات إذا كان هذا الموضوع مثيرًا للاهتمام ، وسأكتب منشورًا حول اختبار الهوية والأدوار والمطالبات والمصادقة ثلاثية الأبعاد وكتابة TypeFilterAttribute الخاص بي (لأن التخزين المؤقت القياسي واحدًا وإذا قمت بإزالة شخص ما من الدور ، فسيظل له هذا الدور حتى يتزوج. الزواج لن يخرج حتى يتم اختباره !: /)

يمكنني بالفعل ، لقد نجحت في الاختبار
صورة

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


All Articles