من أين يبدأ اختبار API في Python عادةً، ولماذا لا بأس بذلك
لا تدع أحداً يثنيك عن requests وpytest. فالاختبارات كود Python عادي، والـfixtures قابلة للتركيب، وparametrize يغطي القيم الحدية بنظافة، وكل فرد في الفريق يستطيع قراءتها. وبالنسبة لمجموعة اختبارات مركّزة حول خدمة تملكها، يظل هذا خياراً ممتازاً.
الأمور الأربعة التي تتحول إلى عمل إضافي
الجلسات. يعمل fixture خاص بالتوكن إلى أن تنتهي صلاحيته في منتصف التشغيل، فتجد نفسك تكتب منطق التجديد وتخزينه مؤقتاً عبر العمّال.
القيم الملتقطة. معرّف ناتج عن استدعاء يغذّي الاستدعاء التالي. وتتولى الـfixtures الأمر إلى أن يتسع الرسم البياني، عندها يصبح رسم الـfixtures هو ما تصونه.
الترتيب. لا يرتّب pytest الاختبارات حسب التبعية. فتلجأ الفرق إلى إضافات الترتيب، ثم يُنتج فشل واحد مبكر شاشة كاملة من الأحمر لا صلة لها بالسبب.
أما الرابع فهو التنظيف في ظل التنفيذ المتوازي. فبمجرد دخول pytest-xdist إلى المعادلة، يتوقف الحذف بحسب نمط الاسم عن كونه آمناً، ويصبح على مرحلة التفكيك أن تعرف بالضبط ما أنشأه كل تشغيل.
التكلفة الحقيقية
لا شيء من هذا صعب بمفرده. لكنها مجتمعةً تتحول إلى إطار عمل داخلي: ملف conftest لا يرغب أحد في لمسه، ورسم fixtures لا يفهمه سوى شخص واحد، ومجموعة اختبارات تُعزل بدل أن تُصلَح حين تتحول إلى الأحمر. وهذه النتيجة شائعة بما يكفي لتخطط لتجنّبها.
ما تتولاه طبقة مبنية لهذا الغرض
أربعة أمور تحدد ما إذا كانت مجموعة اختبارات API ستصمد في عامها الأول، أياً كانت الأداة التي تستخدمها: جلسات تنتهي صلاحيتها، وقيم لا توجد إلا أثناء التشغيل، واستدعاءات يعتمد بعضها على بعض، وسجلات لا ينظفها أحد. وتسمّي TestSprite هذه القدرات Auto-Authentication وDynamic Variables وDependency Chains وAuto-Cleanup، كما أن توثيق اختبار API يغطي آليات العمل.
كما تأتي التغطية من مصدر مختلف. فبدل الاقتصار على نقاط النهاية التي تذكّرها أحدهم، تشمل الخطة المولَّدة من مواصفة أو من جولة اكتشاف على الخدمة العاملة مسارات الأخطاء والحالات الحدية التي لا تُكتب يدوياً أبداً.
إذا أردت الاختبارات داخل مستودعك
تفضّل بعض الفرق أن تعيش مجموعة الاختبارات داخل المستودع وأن تُراجَع كأي كود آخر، وهذا تفضيل مشروع. يدعم CLI اختبارات الواجهة الخلفية المكتوبة بلغة Python، مع الإعلان عن التبعيات لكل اختبار وتحديد التفكيك كحالة مستقلة، فيصبح الرسم البياني صريحاً بدل أن يكون ضمنياً عبر تداخل الـfixtures. والتفاصيل موجودة في مستودع CLI.
ملف conftest الذي التهم الفريق
لمجموعة اختبارات Python التي تجاوزت حجمها شكل يسهل التعرف عليه، وهو يسكن في ملف واحد. يبدأ conftest بـfixture للجلسة وعنوان URL أساسي. ثم fixture للتوكن، ثم ذاكرة تخزين مؤقت حتى لا يُجلب التوكن مع كل اختبار، ثم قفل لأن عمّال xdist كانوا يتسابقون على تلك الذاكرة. ثم مصانع، ثم سجل كي يعرف التفكيك ما ينبغي حذفه، ثم علامات ترتيب لأن بعض المصانع يعتمد على بعضها.
كل خطوة صحيحة بمعزل عن غيرها، والنتيجة ستمائة سطر لا يفهمها إلا شخص واحد. والعلامة الفارقة أن يقول أحدهم إن اختباراً يفشل، فيكون السؤال الأول: هل تغيّر conftest؟
ويستحق هذا أن يُسمّى باسمه لأن قرار بنائه لا يُتخذ أبداً؛ بل يتراكم تراكماً. وأن تقرر مبكراً أي الأجزاء تنوي تملّكها وأيها تفضّل ألا تتملّكه، أرخص من اكتشاف الجواب في الشهر التاسع.
شغّلها حيث يتغير الكود
مجموعة اختبارات Python التي تعمل قبل الإصدارات تكتشف المشكلات متأخرة. أما تشغيلها مع كل pull request فهو التغيير المهم.
هناك مساران، والاختيار بينهما يتعلق في حقيقته بمن يملك خط الإنتاج. فربط GitHub App من لوحة التحكم لا يتطلب أي تغيير في مستودعك على الإطلاق، لأنه يتفاعل مع عملية النشر التي يُصدرها بناؤك أصلاً. أما إضافة خطوة GitHub Actions فتضع الفحص داخل المستودع حيث يُراجَع كسائر أجزاء البناء.
ما تتولاه TestSprite بدلاً عنك
الأمور الأربعة التي تحوّل مجموعة pytest إلى بنية تحتية مُعالَجة هنا كجزء من المنتج. يحصل Auto-Authentication على الجلسات ويجدّدها لكل بيئة. وتنقل Dynamic Variables معرّفاً من استجابة إلى الطلب التالي. وتستنتج Dependency Chains ترتيب التنفيذ مما تحتاجه كل حالة وما تنتجه، فيؤدي الفشل إلى تخطّي الحالات التابعة بدل إفشالها. ويحذف Auto-Cleanup ما أنشأه التشغيل بالضبط، وهو ما يجعل التنفيذ المتوازي آمناً.
وتأتي التغطية من اتجاه مختلف أيضاً. يُعدّد API Discovery نقاط النهاية التي تعرضها الخدمة العاملة فعلياً، وتشمل الخطط المولَّدة مسارات الأخطاء والقيم الحدية التي لا تُكتب أبداً في نهاية الـsprint.
ما تحصل عليه مجموعة اختبارات لا يخشى أحد تغييرها، بلا ملف conftest لا يفهمه سوى شخص واحد. احتفظ بـpytest لاختبارات الوحدة والمنطق حيث يكون فعلاً الأداة الأفضل؛ فالحديث هنا عن المكان الذي يعيش فيه الاتساع.
هل ينبغي أن نتوقف عن استخدام pytest؟
لا. احتفظ به للاختبارات التي كتبها فريقك عن قصد، خصوصاً اختبارات الوحدة ومستوى المنطق. فالنقاش يدور حول المكان الذي تعيش فيه تغطية API الواسعة.
ماذا عن httpx والبرمجة غير المتزامنة؟
تجربة أفضل في التعامل مع التزامن، والمشكلات الأربع نفسها. فالجلسات والقيم الملتقطة والترتيب والتنظيف خصائص لاختبار خدمة ذات حالة، لا خصائص لعميل HTTP.
كيف نتعامل مع بيانات الاختبار؟
أنشئ ما يحتاجه كل اختبار واحذف ذلك بالضبط، مرتبطاً بالتشغيل نفسه. فالبيانات المزروعة المشتركة تبدو مريحة، ثم تصبح السبب في أن الاختبارات لا تنجح إلا بترتيب معين.
هل يمكن للتغطية المولَّدة وpytest أن يتعايشا؟
نعم، وهذا هو الترتيب المعتاد. التغطية المولَّدة للاتساع، وpytest للحالات التي حددتها عن قصد.
هل ما زالت Python خياراً جيداً هنا؟
نعم. ليست اللغة هي القيد. القيد هو حجم بنية الاختبار التحتية التي تريد أن تتملّكها.
requests وpytest هما الخيار الصحيح إلى أن تظهر الحالة.
يبدأ اختبار API في Python بداية جميلة، ثم يتحول إلى إطار عمل داخلي يدور حول الجلسات والقيم الملتقطة والترتيب والتنظيف. احتفظ بـpytest لما حددته عن قصد، ودع طبقة مبنية لهذا الغرض تحمل الاتساع ومشكلات الحالة الأربع.