عائق في المساهمة في الكود: `test::nginx`
API7.ai
November 17, 2022
الاختبار جزء أساسي من تطوير البرمجيات. لقد أصبح مفهوم تطوير البرمجيات الموجه بالاختبار (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
أيضًا. هناك أربع خطوات.
- أولاً، قم بتثبيت مدير حزم Perl
cpanminus
. - ثم، قم بتثبيت
test::nginx
عبرcpanm
.
sudo cpanm --notest Test::Nginx IPC::Run > build.log 2>&1 || (cat build.log && exit 1)
- بعد ذلك، قم باستنساخ أحدث الكود المصدري.
git clone https://github.com/openresty/test-nginx.git
- أخيرًا، قم بتحميل مكتبة
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
محليًا؟ إذا كنت تستطيع إضافة حالة اختبار جديدة، فسيكون ذلك رائعًا.