الاستخدامات غير المعروفة لـ `test::nginx`

API7.ai

November 24, 2022

OpenResty (NGINX + Lua)

في المقالتين السابقتين، أتقنت معظم استخدامات test::nginx، وأعتقد أنك تستطيع فهم معظم مجموعات حالات الاختبار في مشروع OpenResty. هذا أكثر من كافٍ لتعلم OpenResty والمكتبات المحيطة به.

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

مقال اليوم سيكون على الأرجح الجزء الأقل "شعبية" في السلسلة لأنه شيء لم يشاركه أحد من قبل. خذ lua-nginx-module، الوحدة الأساسية في OpenResty، كمثال، حيث يوجد أكثر من 70 مساهمًا حول العالم، ولكن ليس كل مساهم قد كتب حالة اختبار. لذا إذا قرأت مقال اليوم، فإن فهمك لـ test::nginx سيدخل ضمن أفضل 100 عالميًا.

التصحيح في الاختبار

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

ONLY

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

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

=== TEST 1: sanity
=== TEST 2: get
--- ONLY

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

ومع ذلك، هذا مناسب فقط عندما تقوم بالتصحيح. لذا، عندما يجد أمر prove قسم ONLY، سيذكرك بعدم نسيان إزالته عند إرسال الكود الخاص بك.

SKIP

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

=== TEST 1: sanity
=== TEST 2: get
--- SKIP

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

LAST

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

=== TEST 1: sanity
=== TEST 2: get
--- LAST
=== TEST 3: set

قد تتساءل، أفهم أهمية ONLY و SKIP، ولكن ما فائدة LAST؟ في الواقع، أحيانًا تكون حالات الاختبار لديك معتمدة على بعضها، وتحتاج إلى تنفيذ حالات الاختبار الأولى قبل أن تكون الاختبارات اللاحقة ذات معنى. لذا، في هذه الحالة، LAST مفيد جدًا عند استمرار التصحيح.

plan

من بين جميع وظائف test::nginx، تعتبر plan واحدة من أكثرها إثارة للجنون وصعوبة في الفهم. وهي مشتقة من وحدة Perl Test::Plan، ووثائقها غير موجودة في test::nginx، والعثور على شرح لها ليس بالأمر السهل. لذلك، سأقدمها في الجزء المبكر. لقد رأيت عدة مساهمين في كود OpenResty وقعوا في هذا الفخ ولم يتمكنوا حتى من الخروج منه.

إليك مثال على تكوين مشابه يمكنك رؤيته في بداية كل ملف في مجموعة اختبارات OpenResty الرسمية:

plan tests => repeat_each() * (3 * blocks());

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

في هذا المثال، إذا كانت قيمة repeat_each هي 2 وهناك 10 حالات اختبار، فإن قيمة plan يجب أن تكون 2 x 3 x 10 = 60. الشيء الوحيد الذي قد تشعر بالارتباك بشأنه هو معنى الرقم 3، الذي يبدو كرقم سحري!

لا تقلق، دعنا نستمر في النظر إلى المثال، ستتمكن من فهمه في لحظة. أولاً، هل يمكنك معرفة القيمة الصحيحة لـ plan في حالة الاختبار التالية؟

=== TEST 1: sanity
--- config
    location /t {
        content_by_lua_block {
            ngx.say("hello")
        }
    }
--- request
GET /t
--- response_body
hello

أعتقد أن الجميع سيستنتج أن plan = 1، حيث أن الاختبار يتحقق فقط من response_body.

ولكن هذا ليس صحيحًا! الإجابة الصحيحة هي أن plan = 2. لماذا؟ لأن test::nginx لديها تحقق ضمني، أي --- error_code: 200، والذي يتحقق مما إذا كان رمز الاستجابة HTTP هو 200 بشكل افتراضي.

إذن، الرقم السحري 3 أعلاه يعني حقًا أن كل اختبار يتم التحقق منه بشكل صريح مرتين، على سبيل المثال لـ body و error log، وبشكل ضمني لـ response code.

نظرًا لأن هذا عرضة للخطأ، أوصي بإيقاف plan باستخدام الطريقة التالية.

use Test::Nginx::Socket 'no_plan';

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

plan tests => repeat_each() * (3 * blocks()) + 2;

هذه أيضًا الطريقة الرسمية التي سيتم استخدامها.

المعالج المسبق

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

في هذه الحالة، يمكنك استخدام توجيه add_block_preprocessor لإضافة جزء من كود Perl، مثل التالي:

add_block_preprocessor(sub {
    my $block = shift;

    if (!defined $block->config) {
        $block->set_value("config", <<'_END_');
    location = /t {
        echo $arg_a;
    }
    _END_
    }
});

يضيف هذا المعالج المسبق قسم config إلى جميع حالات الاختبار، والمحتوى هو location /t، بحيث يمكنك في حالات الاختبار اللاحقة حذف config والوصول إليه مباشرة.

=== TEST 1:
--- request
    GET /t?a=3
--- response_body
3

=== TEST 2:
--- request
    GET /t?a=blah
--- response_body
blah

الوظائف المخصصة

بالإضافة إلى إضافة كود Perl إلى المعالج المسبق، يمكنك أيضًا إضافة وظائف Perl بشكل تعسفي، أو ما نسميه الوظائف المخصصة، قبل وظيفة run_tests.

إليك مثال يضيف وظيفة تقرأ ملفًا وتدمجها مع توجيه eval لتنفيذ POST ملف:

sub read_file {
    my $infile = shift;
    open my $in, $infile
        or die "cannot open $infile for reading: $!";
    my $content = do { local $/; <$in> };
    close $in;
    $content;
}

our $CONTENT = read_file("t/test.jpg");

run_tests;

__DATA__

=== TEST 1: sanity
--- request eval
"POST /\n$::CONTENT"

الخلط

بالإضافة إلى ما سبق، لدى test::nginx فخ غير معروف: يتم تنفيذ حالات الاختبار بشكل عشوائي بشكل افتراضي، بدلاً من اتباع ترتيب وترقيم حالات الاختبار.

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

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

لذا، نصيحتي هي: يرجى إيقاف هذه الميزة. يمكنك إيقافها باستخدام السطرين التاليين من الكود:

no_shuffle();
run_tests;

على وجه الخصوص، يتم استخدام no_shuffle لتعطيل العشوائية ويسمح للاختبارات بالتشغيل بشكل صارم وفقًا لترتيب حالات الاختبار.

reindex

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

لحسن الحظ، لدينا أداة تلقائية، reindex، للقيام بهذه الأشياء المملة، والتي يتم إخفاؤها في مشروع openresty-devel-utils. نظرًا لعدم وجود وثائق عنها، فإن عددًا قليلاً فقط من الأشخاص يعرفون عنها.

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

الخلاصة

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

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