ما هي أفضل استراتيجية للتطبيقات المستندة إلى قاعدة بيانات اختبار الوحدة؟

StackOverflow https://stackoverflow.com/questions/145131

سؤال

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

لكن اختبار ORM وقاعدة البيانات نفسها كان دائمًا محفوفًا بالمشاكل والتسويات.

على مر السنين، جربت بعض الاستراتيجيات، ولم يرضيني أي منها تمامًا.

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

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

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

  • استخدم خادم قاعدة بيانات وهمي، وتأكد فقط من أن ORM يرسل الاستعلامات الصحيحة استجابةً لاستدعاء أسلوب محدد.

ما هي الاستراتيجيات التي استخدمتها لاختبار التطبيقات المعتمدة على قاعدة البيانات، إن وجدت؟ما هو الأفضل بالنسبة لك؟

هل كانت مفيدة؟

المحلول

لقد استخدمت بالفعل نهجك الأول وحققت بعض النجاح، ولكن بطرق مختلفة قليلاً أعتقد أنها ستحل بعض مشكلاتك:

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

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

  3. بالنسبة لمجموعتي، يتم إدخال المستخدم على مستوى التطبيق (وليس ديسيبل) لذلك يتم اختبار ذلك عبر اختبارات الوحدة القياسية.

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

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

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

نصائح أخرى

أقوم دائمًا بإجراء اختبارات على قاعدة بيانات في الذاكرة (HSQLDB أو Derby) لهذه الأسباب:

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

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

يتم تحميل البيانات من SQL أو قالب DB أو تفريغ/نسخة احتياطية.أفضّل عمليات التفريغ إذا كانت بتنسيق قابل للقراءة لأنه يمكنني وضعها في VCS.إذا لم ينجح ذلك، أستخدم ملف CSV أو XML.إذا اضطررت إلى تحميل كميات هائلة من البيانات ...أنا لا.لن تضطر أبدًا إلى تحميل كميات هائلة من البيانات :) ليس لاختبارات الوحدة.تعتبر اختبارات الأداء مشكلة أخرى ويتم تطبيق قواعد مختلفة.

لقد طرحت هذا السؤال لفترة طويلة، ولكن أعتقد أنه لا يوجد حل سحري لذلك.

ما أفعله حاليًا هو الاستهزاء بكائنات DAO والاحتفاظ بتمثيل في الذاكرة لمجموعة جيدة من الكائنات التي تمثل حالات مثيرة للاهتمام من البيانات التي يمكن أن تعيش في قاعدة البيانات.

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

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

على الرغم من أنه لا يوجد شك في أن هذا الأسلوب يعمل على تحسين التغطية، إلا أن هناك بعض العيوب، حيث يجب أن تكون أقرب ما يمكن إلى ANSI SQL لجعله يعمل مع نظام إدارة قواعد البيانات الحالي لديك والبديل المضمن.

بغض النظر عما تعتقد أنه أكثر ملاءمة للتعليمات البرمجية الخاصة بك، فهناك عدد قليل من المشاريع التي قد تجعل الأمر أسهل، مثل DbUnit.

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

حتى إذا كنت تريد فقط اختبار التكامل الخاص بك ORM، فاحذر من أن ORM يُصدر سلسلة معقدة جدًا من الاستعلامات إلى قاعدة البيانات الخاصة بك، والتي قد تختلف في

  • بناء الجملة
  • تعقيد
  • طلب (!)

إن الاستهزاء بكل ذلك لإنتاج بيانات وهمية معقولة أمر صعب للغاية، إلا إذا كنت تقوم بالفعل ببناء قاعدة بيانات صغيرة داخل نسختك الوهمية، والتي تفسر عبارات SQL المنقولة.بعد قولي هذا، استخدم قاعدة بيانات معروفة لاختبار التكامل والتي يمكنك إعادة تعيينها بسهولة باستخدام بيانات معروفة، والتي يمكنك إجراء اختبارات التكامل الخاصة بك عليها.

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

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

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

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

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

والغرض الرئيسي هو جعل البيانات المستخدمة عن طريق الاختبار

  1. قريب جدا من الاختبار
  2. صريح (استخدام ملفات SQL للبيانات يجعل من الصعب جدًا معرفة جزء البيانات المستخدم في أي اختبار)
  3. عزل الاختبارات عن التغييرات غير ذات الصلة.

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

ولإعطاء فكرة عما يعنيه ذلك عمليًا، فكر في اختبار بعض DAO التي تعمل معها Commentق ل Postكتب بواسطة Authors.من أجل اختبار عمليات CRUD لمثل هذه DAO، يجب إنشاء بعض البيانات في قاعدة البيانات.سيبدو الاختبار كما يلي:

@Test
public void savedCommentCanBeRead() {
    // Builder is needed to declaratively specify the entity with all attributes relevant
    // for this specific test
    // Missing attributes are generated with reasonable values
    // factory's responsibility is to create entity (and all entities required by it
    //  in our example Author) in the DB
    Post post = factory.create(PostBuilder.post());

    Comment comment = CommentBuilder.comment().forPost(post).build();

    sut.save(comment);

    Comment savedComment = sut.get(comment.getId());

    // this checks fields that are directly stored
    assertThat(saveComment, fieldwiseEqualTo(comment));
    // if there are some fields that are generated during save check them separately
    assertThat(saveComment.getGeneratedField(), equalTo(expectedValue));        
}

يتميز هذا بالعديد من المزايا مقارنة ببرامج SQL النصية أو ملفات XML مع بيانات الاختبار:

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

التراجع مقابل الالتزام

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

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

بالنسبة للمشروع القائم على JDBC (بشكل مباشر أو غير مباشر، على سبيل المثال.JPA، EJB، ...) لا يمكنك عمل نموذج بالحجم الطبيعي لقاعدة البيانات بأكملها (في مثل هذه الحالة سيكون من الأفضل استخدام قاعدة بيانات اختبارية على نظام RDBMS حقيقي)، ولكن نموذج بالحجم الطبيعي فقط على مستوى JDBC.

الميزة هي التجريد الذي يأتي بهذه الطريقة، حيث أن بيانات JDBC (مجموعة النتائج، عدد التحديثات، التحذير، ...) هي نفسها مهما كانت الواجهة الخلفية:قاعدة بيانات المنتج الخاصة بك، أو قاعدة بيانات اختبارية، أو مجرد بعض بيانات النموذج المتوفرة لكل حالة اختبار.

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

Acolyte هو إطار العمل الخاص بي الذي يتضمن برنامج تشغيل JDBC وأداة مساعدة لهذا النوع من النماذج بالحجم الطبيعي: http://acolyte.eu.org .

مرخصة بموجب: CC-BY-SA مع الإسناد
لا تنتمي إلى StackOverflow
scroll top