عائق في المساهمة في الكود: `test::nginx`

API7.ai

November 17, 2022

OpenResty (NGINX + Lua)

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

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

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

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

لنتحدث عن فلسفة OpenResty في الاختبار.

المفهوم

test::nginx هو جوهر بنية اختبار OpenResty، والذي يستخدمه OpenResty نفسه والمكتبات المحيطة بـ lua-resty لتنظيم وكتابة مجموعات الاختبار. إنه إطار اختبار ذو عتبة عالية جدًا. السبب هو أن test::nginx، على عكس أطر الاختبار الشائعة، لا يعتمد على التأكيدات ولا يستخدم لغة Lua، مما يتطلب من المطورين تعلم واستخدام test::nginx من الصفر وعكس معرفتهم السابقة بأطر الاختبار.

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

test::nginx يجمع بين Perl، والبيانات الموجهة، وDSL (لغة خاصة بالمجال). لنفس مجموعة حالات الاختبار، عن طريق التحكم في المعلمات والمتغيرات البيئية، يمكنك تحقيق تأثيرات مختلفة مثل التنفيذ العشوائي، التكرار المتعدد، اكتشاف تسرب الذاكرة، اختبار الضغط، إلخ.

التثبيت والأمثلة

قبل أن نستخدم test::nginx، لنتعلم كيفية تثبيته.

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

  1. أولاً، قم بتثبيت مدير حزم Perl cpanminus.
  2. ثم، قم بتثبيت test::nginx عبر cpanm.
sudo cpanm --notest Test::Nginx IPC::Run > build.log 2>&1 || (cat build.log && exit 1)
  1. بعد ذلك، قم باستنساخ أحدث الكود المصدري.
git clone https://github.com/openresty/test-nginx.git
  1. أخيرًا، قم بتحميل مكتبة test-nginx عبر أمر prove في Perl وقم بتشغيل مجموعة حالات الاختبار في الدليل /t.
prove -Itest-nginx/lib -r t

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

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


run_tests();

__DATA__

=== TEST 1: set Server
--- config
    location /foo {
        echo hi;
        more_set_headers 'Server: Foo';
    }
--- request
    GET /foo
--- response_headers
Server: Foo
--- response_body
hi

على الرغم من أن test::nginx مكتوب بلغة Perl ويعمل كواحد من الوحدات، هل يمكنك رؤية أي شيء في Perl أو أي لغة أخرى من الاختبار أعلاه؟ هذا صحيح. لأن test::nginx هو تنفيذ المؤلف الخاص لـ DSL باستخدام Perl، مجردة خصيصًا لاختبار NGINX وOpenResty.

لذلك، عندما نرى هذا النوع من الاختبار لأول مرة، على الأرجح لن نفهمه. ولكن لا تقلق؛ دعونا نحلل حالة الاختبار أعلاه.

أولاً، use Test::Nginx::Socket;، وهي طريقة Perl لاستدعاء المكتبات، تمامًا مثل require في Lua. هذا أيضًا يذكرنا بأن test::nginx هو برنامج Perl.

السطر الثاني، run_tests(); هو دالة Perl في test::nginx، وهي دالة الدخول لإطار الاختبار. إذا كنت تريد استدعاء أي دوال Perl أخرى في test::nginx، فيجب وضعها قبل run_tests لتكون فعالة.

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

التالي === TEST 1: set Server، عنوان حالة الاختبار، يشير إلى الغرض من هذا الاختبار، ولديه أداة تقوم بترقيم الحالات تلقائيًا داخليًا.

--- config هو حقل تكوين NGINX. في الحالة أعلاه، استخدمنا أوامر NGINX، وليس Lua، وإذا كنت تريد إضافة كود Lua، فستفعل ذلك هنا باستخدام توجيه مثل content_by_lua.

--- request يستخدم لمحاكاة طرف لإرسال طلب، يتبعه GET /foo، والذي يحدد طريقة وURI الطلب.

--- response_headers، يستخدم للتحقق من رؤوس الاستجابة. Server: Foo التالي يشير إلى header وvalue التي يجب أن تظهر في رؤوس الاستجابة. إذا لم تظهر، فسيفشل الاختبار.

الأخير --- response_body، يستخدم للتحقق من جسم الاستجابة. hi التالي هو السلسلة التي يجب أن تظهر في جسم الاستجابة؛ إذا لم تظهر، فسيفشل الاختبار.

حسنًا، هنا، انتهى تحليل أبسط حالة اختبار. لذا، فهم حالة الاختبار هو شرط مسبق لإكمال أعمال التطوير المتعلقة بـ OpenResty.

اكتب حالات الاختبار الخاصة بك

التالي، حان الوقت للدخول في الاختبار العملي. تذكر كيف اختبرنا خادم Memcached في المقالة السابقة؟ هذا صحيح؛ استخدمنا resty لإرسال الطلب يدويًا، والذي يتم تمثيله بالكود التالي.

resty -e 'local memcached = require "resty.memcached"
    local memc, err = memcached:new()

    memc:set_timeout(1000) -- 1 sec
    local ok, err = memc:connect("127.0.0.1", 11212)
    local ok, err = memc:set("dog", 32)
    if not ok then
        ngx.say("failed to set dog: ", err)
        return
    end

    local res, flags, err = memc:get("dog")
    ngx.say("dog: ", res)'

ولكن أليس إرساله يدويًا غير ذكي؟ لا تقلق. يمكننا محاولة تحويل الاختبارات اليدوية إلى اختبارات آلية بعد تعلم test::nginx. على سبيل المثال:

use Test::Nginx::Socket::Lua::Stream;

run_tests();

__DATA__
  
=== TEST 1: basic get and set
--- config
        location /test {
            content_by_lua_block {
                local memcached = require "resty.memcached"
                local memc, err = memcached:new()
                if not memc then
                    ngx.say("failed to instantiate memc: ", err)
                    return
                end

                memc:set_timeout(1000) -- 1 sec
                local ok, err = memc:connect("127.0.0.1", 11212)

                local ok, err = memc:set("dog", 32)
                if not ok then
                    ngx.say("failed to set dog: ", err)
                    return
                end

                local res, flags, err = memc:get("dog")
                ngx.say("dog: ", res)
            }
        }

--- stream_config
    lua_shared_dict memcached 100m;

--- stream_server_config
    listen 11212;
    content_by_lua_block {
        local m = require("memcached-server")
        m.go()
    }

--- request
GET /test
--- response_body
dog: 32
--- no_error_log
[error]

في حالة الاختبار هذه، أضفت --- stream_config، --- stream_server_config، --- no_error_log كعناصر تكوين، ولكنها في الأساس نفس الشيء، أي

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

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

الخلاصة

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

أخيرًا، سأترك لك سؤالًا. هل يمكنك تشغيل هذا الاختبار لـ Memcached محليًا؟ إذا كنت تستطيع إضافة حالة اختبار جديدة، فسيكون ذلك رائعًا.

Share article link