Knex: كيفية كتابة اختبارات الوحدة للطرق التي تستخدم Knex.

تم إنشاؤها على ٢١ مايو ٢٠١٧  ·  28تعليقات  ·  مصدر: knex/knex

(نُشر في الأصل في # 1659 ، تم نقله هنا لمزيد من المناقشة.)

أشعر بالحيرة تجاه هذا الأمر - مستندات knex تغطي الأجزاء المجردة من المعاملات ، ولكن عندما أستخدم google "باستخدام knex.js لاختبار الوحدة" و "ava.js + knex" و "قاعدة بيانات ava.js +" ، لم أتمكن من العثور على أي شيء مفيد بشكل خاص لإظهار طريقة جيدة ، لذلك عدت إلى تجربتي في Ruby حيث يعد تغليف اختبار الوحدة في معاملة تقنية شائعة لإعادة ضبط قاعدة البيانات بعد كل اختبار.

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

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

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

الطريقة التي تمنيت / توقعت أن يعمل بها هي:

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

2) أحتاج بعد ذلك إلى الوحدة التي أريد اختبارها وتمرير الكائن trx إلى مُنشئ الوحدة النمطية حتى يتم استخدامه بواسطة الوحدة ، مما يؤدي إلى حدوث جميع الاستعلامات داخل المعاملة.

3) بعد إرجاع طريقة الوحدة النمطية (أو تلقي خطأ) ، أقوم بتشغيل تأكيداتي على الحالة الناتجة لقاعدة البيانات ، ثم اتصل بـ trx.rollback() للتراجع عن كل شيء من البداية للتحضير للاختبار التالي.

لذلك ، هذا ما أحاول تحقيقه وكيف أنوي تحقيقه أصلاً. أنا حريص على معرفة المزيد عن:

1) لماذا أسيء فهم كيفية عمل Knex.js وما ينبغي استخدامه.

2) أفضل الممارسات لكتابة اختبارات الوحدات الذرية للكود الذي يلامس قاعدة البيانات.

question

التعليق الأكثر فائدة

لا - لم أحصل على شيء. ملخص بالترتيب الزمني للمصادر:

2016-04-21 https://medium.com/@jomaora/knex -bookshelf-mocks-and-unit-tests-cca627565d3

الإستراتيجية: استخدم محاكاة وهمية . لا أريد أن أسخر من قاعدة البيانات - أرغب في اختبار أساليبي مقابل قاعدة بيانات MySQL DB فعلية لضمان السلوك الصحيح - لكني ألقيت نظرة على mock-knex أي حال ... قد تكون أسوأ مكتبة مصممة لقد واجهت من أي وقت مضى. :(

2016-04-28 http://mherman.org/blog/2016/04/28/test-driven-development-with-node/

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

2015-09-23 http://stackoverflow.com/a/32749601/210867

الإستراتيجية: استخدم sqlite للاختبار ، وأنشئ / دمر قاعدة بيانات لكل اختبار. لقد غطيت كلا السببين اللذين لا يعجبني هذا النهج أعلاه.

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

ال 28 كومينتر

يبدو أنني أديت عملاً سيئًا في googling الليلة الماضية ، لأنني حاولت مرة أخرى "كيفية كتابة اختبارات الوحدة الذرية باستخدام knex.js" ، وحصلت على بعض النتائج المثيرة للاهتمام. سأقرأ من خلالهم الآن. سوف أنشر مرة أخرى إذا تعلمت شيئا.

لا - لم أحصل على شيء. ملخص بالترتيب الزمني للمصادر:

2016-04-21 https://medium.com/@jomaora/knex -bookshelf-mocks-and-unit-tests-cca627565d3

الإستراتيجية: استخدم محاكاة وهمية . لا أريد أن أسخر من قاعدة البيانات - أرغب في اختبار أساليبي مقابل قاعدة بيانات MySQL DB فعلية لضمان السلوك الصحيح - لكني ألقيت نظرة على mock-knex أي حال ... قد تكون أسوأ مكتبة مصممة لقد واجهت من أي وقت مضى. :(

2016-04-28 http://mherman.org/blog/2016/04/28/test-driven-development-with-node/

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

2015-09-23 http://stackoverflow.com/a/32749601/210867

الإستراتيجية: استخدم sqlite للاختبار ، وأنشئ / دمر قاعدة بيانات لكل اختبار. لقد غطيت كلا السببين اللذين لا يعجبني هذا النهج أعلاه.

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

لخص odigity بشكل جيد ممارسات الاختبار السيئة 👍

نحن نجري اختبارات "الوحدة" لدينا مثل هذا:

  1. بدء تشغيل النظام وتهيئة قاعدة البيانات وتشغيل عمليات الترحيل

  2. قبل كل اختبار نقوم باقتطاع جميع الجداول والتسلسلات (مع حزمة knex-db-manager)

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

  4. قم بإجراء اختبار واحد وانتقل إلى الخطوة 2

من خلال اختبارات e2e ، قمنا بتنفيذ طرق saveState / استعادة الحالة (مع pg_restore / pg_dump) ، والتي تتيح لنا العودة إلى حالة معينة أثناء التشغيل التجريبي ، لذلك لا يتعين علينا إعادة تشغيل التشغيل التجريبي في كل مرة يفشل فيها بعض الاختبار بعد تشغيل 20 دقائق من الاختبارات.

هذا مشابه لما بدأت به بالأمس لأنه بسيط ومباشر وكنت بحاجة إلى إحراز تقدم.

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

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

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

إلى حد كبير بنفس الطريقة التي وصفتها في OP. كيف وصفته يبدو قابلاً للتطبيق.

لقد فكرت في استخدام المعاملات لإعادة تعيين قاعدة البيانات في الاختبارات قبل عامين ، لكنني رفضتها لأنني أرغب في تجميع الاتصال للعمل في الاختبارات بنفس الطريقة التي تعمل بها في app + truncate / init وهي سريعة بما يكفي بالنسبة لنا.

هل لا توجد طريقة لتحقيق تقارب الاتصال طوال مدة المعاملة بحيث تستخدم جميع الاستعلامات التي يتم تشغيلها في دُفعة واحدة نفس الاتصال ، وبالتالي يمكن التفافها في معاملة واحدة؟

تعمل الاستراتيجية المبتورة ، لكنها قوة غاشمة للغاية. لدي حاليًا test.after.always () ربط في كل ملف اختبار يقوم باقتطاع الجداول المتأثرة بالاختبارات في هذا الملف (عادةً جدول واحد لكل ملف) ، لكن يمكنني التفكير في حالات الحافة التي قد تتعطل بسبب ذلك.

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

أخيرًا ، ما زلت غير واضح بشأن كيفية عمل المعاملات في Knex بالضبط. إذا قمت بإنشاء trx بـ knex.transaction() ، فهل سيتم تشغيل جميع الاستعلامات باستخدام trx تلقائيًا كجزء من المعاملة؟ هل يجب علي الالتزام / التراجع يدويًا؟ (بافتراض عدم ورود أخطاء.)

هل لا توجد طريقة لتحقيق تقارب الاتصال طوال مدة المعاملة بحيث تستخدم جميع الاستعلامات التي يتم تشغيلها في دُفعة واحدة نفس الاتصال ، وبالتالي يمكن التفافها في معاملة واحدة؟

لم افهم هذا

تعمل الاستراتيجية المبتورة ، لكنها قوة غاشمة للغاية. لدي حاليًا test.after.always () ربط في كل ملف اختبار يقوم باقتطاع الجداول المتأثرة بالاختبارات في هذا الملف (عادةً جدول واحد لكل ملف) ، لكن يمكنني التفكير في حالات الحافة التي قد تتعطل بسبب ذلك.

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

حتى إذا كنت تستخدم المعاملات لإعادة تعيين بيانات الاختبار الخاصة بك ، فلن تتمكن من إجراء اختبارات متعددة بشكل متوازٍ في الحالة العامة. ستكون تسلسلات المعرف مختلفة وقد تتوقف المعاملات وما إلى ذلك.

أخيرًا ، ما زلت غير واضح بشأن كيفية عمل المعاملات في Knex بالضبط. إذا قمت بإنشاء ملف trx باستخدام knex.transaction () ، فهل سيتم تشغيل جميع الاستعلامات باستخدام trx تلقائيًا كجزء من المعاملة؟ هل يجب علي الالتزام / التراجع يدويًا؟ (بافتراض عدم ورود أخطاء.)

تم العثور على هذا من الوثائق. إذا لم ترد الوعد من رد الاتصال في knex.transaction(callback) فأنت بحاجة إلى الالتزام / التراجع يدويًا. إذا أعاد الاتصال الوعد ، فسيتم استدعاء الالتزام / التراجع تلقائيًا. في حالتك ربما يتعين عليك التراجع يدويًا.

حتى إذا كنت تستخدم المعاملات لإعادة تعيين بيانات الاختبار الخاصة بك ، فلن تتمكن من إجراء اختبارات متعددة بشكل متوازٍ في الحالة العامة. ستكون تسلسلات المعرف مختلفة وقد تتوقف المعاملات وما إلى ذلك.

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

بالنسبة إلى التزامن ، أفترض أن جميع الاستعلامات التي يجب تجميعها في معاملة واحدة يجب أن تستخدم أيضًا اتصال قاعدة البيانات نفسه ، والذي يمكنني تحقيقه من خلال تعيين حجم مجموعة Knex الخاصة بي على 1. وسأحتاج بعد ذلك إلى إجراء الاختبارات في ملف تسلسلي باستخدام المُعدِّل .serial . ومع ذلك ، لا يزال لدي التزامن بين ملفات الاختبار (وهو العامل الأكثر أهمية للأداء) لأن Ava تقوم بتشغيل كل ملف اختبار في عملية فرعية منفصلة.

أعتقد أنني يجب أن أذهب فقط لتجربته.

لقد حصلت على العمل!

https://gist.github.com/odigity/7f37077de74964051d45c4ca80ec3250

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

قبل هوك

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

    • احفظ المقبض tx في مكان يمكن الوصول إليه من خلال الاختبار وخطاف after

    • استدعاء طريقة resolve المحفوظة لإبلاغ "إطار عمل الاختبار" أن الخطاف before قد اكتمل ، ويمكن أن يبدأ الاختبار

اختبار - قمت بتنفيذ استعلامين للإدراج باستخدام مقبض tx المحفوظ.

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

على التزامن

كنيكس

يتيح لك Knex تحديد خيارات لحجم تجمع الاتصال. إذا كنت بحاجة إلى التأكد من تشغيل جميع الاستعلامات في معاملة ما على نفس الاتصال (أفترض أننا نفعل ذلك) ، فيمكنك تحقيق ذلك عن طريق تعيين حجم التجمع على 1.

_ولكن انتظر ... _ تحقق من هذا التعليق في المصدر :

// We need to make a client object which always acquires the same
// connection and does not release back into the pool.
function makeTxClient(trx, client, connection) {

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

راجع للشغل - ربما يجب أن تعكس المستندات هذه الحقيقة الجوهرية والرائعة.

افا

_أعلم أن هذا خاص بـ Ava وليس خاصًا بـ Knex ، لكنه إطار عمل شائع ، والدروس قابلة للتطبيق على نطاق واسع على معظم إطارات العمل.

تدير Ava كل ملف اختبار في عملية منفصلة بشكل متزامن. داخل كل عملية ، يتم أيضًا تشغيل جميع الاختبارات بشكل متزامن. كلا شكلي التزامن غير قابلين للتعطيل باستخدام خيار --serial CLI (الذي يسلسل كل شيء) أو المعدل .serial على طريقة test (التي تسلسل الاختبارات المحددة قبل تشغيل الباقي بشكل متزامن ).

يتم إحتوائه

عندما أضع كل هذه الحقائق معًا:

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

  • يمكنني الاستمرار في الاستفادة من التزامن بين ملفات اختبار Ava ، نظرًا لأن كل ملف اختبار يعمل في عملية منفصلة ، وبالتالي سيكون لديه مجموعة اتصال Knex الخاصة به.

  • يمكنني الاستمرار في الاستفادة من التزامن داخل ملف اختبار Ava إذا تأكدت من أن تجمع الاتصال الخاص بي> = عدد الاختبارات في الملف. لا ينبغي أن تكون هذه مشكلة ، فأنا لا أضع عددًا كبيرًا من الاختبارات في ملف واحد ، والتي أحاول تجنبها على أي حال. أيضًا ، يمكن تعيين المجموعة بنطاق مثل 1-10 ، لذا فأنت لا تنشئ اتصالات لا تحتاج إليها ، ولكن لا يتعين عليك ضبط ثابت في كل ملف اختبار في كل مرة تقوم فيها بإضافة اختبار أو إزالته.


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

odigity yup ، تعتبر المعاملة في knex إلى حد كبير مجرد مؤشر للاتصال المخصص حيث يضيف knex تلقائيًا استعلام BEGIN عند إنشاء مثيل trx (في الواقع المعاملة في SQL بشكل عام هي مجرد استعلامات تذهب إلى نفس الاتصال مُسبقًا مع BEGIN).

إذا لم تخصص knex اتصالاً للمعاملات ، فلن تعمل المعاملات على الإطلاق.

استخدام مفاتيح uuid ليس سيئًا أيضًا ، تغيير الاصطدام غير محتمل حقًا ما لم يكن هناك بالفعل العديد من الصفوف. ("إذا كنت تستخدم UUID 128 بت ، فإن" تأثير عيد الميلاد "يخبرنا أنه من المحتمل حدوث تصادم بعد إنشاء 2 ^ 64 مفتاحًا ، بشرط أن يكون لديك 128 بتًا من الكون في كل مفتاح.")

أفترض أنك حصلت على إجاباتك ، لذا أغلق هذا 👍

آسف لإعادة الفتح ، لكنني ألاحظ الآن سلوكًا غير متوقع. على وجه التحديد ، إنه يعمل عندما لا يكون كذلك ، مما يعني أن فهمي يحتاج إلى تصحيح.

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

ومع ذلك ، إذا قمت بتعيين حجم التجمع على 1 وقمت بإجراء أربعة اختبارات في نفس الوقت ، كل منها:

  • يفتح صفقة
  • يدير الاستفسارات
  • يستدعي التراجع في النهاية

كلهم يعملون. لا ينبغي أن يعملوا - على الأقل عدد قليل منهم يجب أن يُغلق عليهم بسبب ندرة الاتصال - لكنهم جميعًا يعملون بشكل جيد ، في كل مرة.

هل يحتوي Knex على قائمة انتظار مضمنة للاستعلامات عندما لا تتوفر اتصالات في ذلك الوقت؟ إذا كان الأمر كذلك ، فلماذا يفشل المثال الأول الخاص بي ، بدلاً من انتظار انتهاء المعاملة قبل تنفيذ استعلام غير tx؟

أو تقوم Knex بطريقة ما بتعدد إرسال معاملات متزامنة متعددة على اتصال واحد؟

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

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

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

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

شكرًا ، هذا مفيد حقًا.

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

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

لقد وجدت هذا الموضوع مفيد جدا. من الجيد أن ترى أن الأشخاص الآخرين يهتمون أيضًا بكتابة الاختبارات التي لا تلوث حالة النظام. لقد بدأت مشروعًا يسمح للأشخاص بكتابة اختبارات للرمز الذي يستخدم knex والذي سيتراجع بعد اكتمال الاختبار.

https://github.com/bas080/knest

@ bas080 في الحالة العامة ، يحد هذا النهج من الأشياء التي يمكنك اختبارها (السماح للاختبار باستخدام معاملة واحدة فقط). سيمنع رمز الاختبار الذي يستخدم اتصالات متعددة / معاملات متزامنة. كما لا يمكن للمرء أن يختبر الكود الذي يقوم بارتكاب التزامات ضمنية (ليست حالات شائعة جدًا) بهذه الطريقة.

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

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

مرحبًا ، آسف لإعادة فتح هذه المشكلة ، لكنني بدأت مشروعًا جديدًا وهو عبارة عن cli لـ knex إلى قواعد بيانات متعددة تحتوي على بيانات مزيفة ، وأود أن تراها وتساعدني في بعض المساهمة

لاختبار الوحدة ، كنت أتحقق فقط من أن الاستعلامات المنفذة بواسطة Knex من غلاف SQL الخاص بي تم تشكيلها بشكل صحيح ، باستخدام toString() في نهاية سلسلة الاستعلام. لاختبار التكامل ، كنت أستخدم الإستراتيجية المذكورة أعلاه - وهي دورة: التراجع -> الترحيل -> البذور ، قبل كل اختبار. من المفهوم أن هذا قد يكون بطيئًا للغاية إذا لم تتمكن من الاحتفاظ ببياناتك الأولية صغيرة ، ولكنها قد تكون مناسبة للآخرين.

هذا للدورة: التراجع -> الهجرة -> البذور ، قبل كل اختبار.

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

يمكنك استخدام منظف ​​knex لاقتطاع جميع الطاولات بسهولة:

knexCleaner
    .clean(knex, { ignoreTables: ['knex_migrations', 'knex_migrations_lock'] })
    .then(() => knex.seed.run())

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

ricardograca هل يتعامل مع الحالات ذات المفاتيح الخارجية بشكل جيد؟ (بمعنى أن التنظيف لن يفشل لأن أمر الحذف خاطئ)

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

elhigu كيف يمكنك فعل ذلك؟

يعتمد على قاعدة البيانات ، ولكن على سبيل المثال مثل هذا: https://github.com/Vincit/knex-db-manager/blob/master/lib/PostgresDatabaseManager.js#L95

kibertoad نعم ، إنه يتعامل مع قيود المفتاح الخارجي على ما يرام ويحذف كل شيء.

odigity ماذا لو قمنا باختبار بنية كهذه:

// controller.js
const users = require('./usersModel.js');

module.exports.addUser = async ({ token, user }) => {
  // check token, or other logic
  return users.add(user);
};

// usersModel.js
const db = require('./db');

module.exports.add = async user => db('users').insert(user);

// db.js
module.exports = require('knex')({ /* config */});

أنا طريقتك يجب أن تحتوي جميع الوظائف على وسيط إضافي (على سبيل المثال trx ) لتمرير المعاملة إلى أدوات إنشاء الاستعلام الفعلية.
https://knexjs.org/#Builder -transacting

أعتقد أن الطريقة الصحيحة يجب أن تكون شيئًا من هذا القبيل:

  1. أنشئ trx بـ beforeEach .
  2. بطريقة ما حقنه. في المثال الخاص بي ، يُرجع require('./db') قيمة trx.
  3. قم بإجراء الاختبارات.
  4. التراجع عن trx في "afterEach".

لكن ، لا أعلم هل هذا ممكن مع knex؟
ماذا لو كان الكود يستخدم معاملات أخرى؟

أيضًا خيار آخر: ربما هناك بعض الوظائف التي تبدأ في تنفيذ الاستعلام. لذلك يمكننا تجاوزها لفرض التشغيل في معاملة اختبارية.

بعد يوم من قراءة كود knex ، جربت هذه الطريقة:

test.beforeEach(async t => {
    // if we use new 0.17 knex api knex.transaction().then - we can not handle rollback error
    // so we need to do it in old way
    // and add empty .catch to prevent unhandled rejection
    t.context.trx = await new Promise(resolve => db.transaction(trx => resolve(trx)).catch(() => {}));
    t.context.oldRunner = db.client.runner;
    db.client.runner = function(builder) {
        return t.context.oldRunner.call(t.context.trx.client, builder);
    };
    t.context.oldRaw = db.raw;
    db.raw = function(...args) {
        return t.context.oldRaw.call(this, ...args).transacting(t.context.trx);
    };
});

test.afterEach(async t => {
    db.raw = t.context.oldRaw;
    db.client.runner = t.context.oldRunner;
    await t.context.trx.rollback();
});

وهو يعمل نوعا ما. لذلك تجاوزت طرق .raw و .client.runner . .client.runner يتصل داخليا عندما يكون لديك .then منشئ استعلام. db في هذه الوظائف هو عميل knex knex({ /* config */}) .

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

هل كانت هذه الصفحة مفيدة؟
0 / 5 - 0 التقييمات