---
title: "طرق اختبار `test::nginx`: التكوين، إرسال الطلبات، والتعامل مع الردود - API7.ai"
description: "تشرح هذه المقالة كيفية استخدام test::nginx لإرسال الطلبات والتعامل مع الردود في حالة اختبار."
url: "https://api7.ai/ar/learning-center/openresty/all-inclusive-testing-methods-test-nginx"
---

# طرق اختبار \`test::nginx\`: التكوين، إرسال الطلبات، والتعامل مع الردود

API7.ai

November 18, 2022

[OpenResty (NGINX + Lua)](https://api7.ai/learning-center/openresty)

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

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

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

يوفر `test::nginx` الكثير من البدائيات الخاصة بـ DSL (لغة خاصة بالمجال). لقد قمت بتصنيف بسيط وفقًا لتكوين NGINX، وإرسال الطلبات، ومعالجة الاستجابات، وفحص السجلات. هذه الـ 20% من الوظائف يمكن أن تغطي 80% من سيناريوهات التطبيق، لذا يجب أن نتمكن منها بشكل جيد. أما بالنسبة للبدائيات الأخرى الأكثر تقدمًا واستخداماتها، فسنقدمها في المقال القادم.

## تكوين NGINX

لننظر أولاً إلى تكوين NGINX. البدائية في `test::nginx` التي تحتوي على الكلمة المفتاحية "config" مرتبطة بتكوين NGINX، مثل `config`، `stream_config`، `http_config`، إلخ.

وظائفها متشابهة: إدخال تكوين NGINX المحدد في سياقات NGINX المختلفة. يمكن أن تكون هذه التكوينات إما أوامر NGINX أو كود Lua مغلف في `content_by_lua_block`.

عند إجراء اختبارات الوحدة، تكون `config` هي البدائية الأكثر استخدامًا حيث نقوم بتحميل مكتبات Lua واستدعاء وظائف لاختبار الصندوق الأبيض. هنا مقتطف من كود الاختبار، والذي لا يمكن تشغيله بالكامل. وهو مأخوذ من مشروع مفتوح المصدر حقيقي، لذا إذا كنت مهتمًا، يمكنك النقر على [الرابط](https://github.com/apache/apisix/blob/master/t/plugin/key-auth.t#L11) لرؤية الاختبار الكامل، أو يمكنك محاولة تشغيله محليًا.

```
=== TEST 1: sanity
--- config
    location /t {
        content_by_lua_block {
            local plugin = require("apisix.plugins.key-auth")
            local ok, err = plugin.check_schema({key = 'test-key'})
            if not ok then
                ngx.say(err)
            end
            ngx.say("done")
        }
    }
```

الغرض من حالة الاختبار هذه هو اختبار ما إذا كانت وظيفة `check_schema` في ملف الكود `plugins.key-auth` تعمل بشكل صحيح. تستخدم أمر NGINX `content_by_lua_block` في `location /t` لطلب الوحدة المراد اختبارها واستدعاء الوظيفة التي تحتاج إلى التحقق مباشرة.

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

## إرسال الطلبات

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

### `request`

استمرارًا لحالة الاختبار أعلاه، إذا أردنا تشغيل كود اختبار الوحدة، فيجب علينا بدء طلب HTTP إلى العنوان `/t` المحدد في التكوين، كما هو موضح في كود الاختبار التالي:

```
--- request
GET /t
```

يرسل هذا الكود طلب `GET` إلى `/t` في بدائية الطلب. هنا، لم نحدد عنوان IP أو اسم النطاق أو المنفذ للوصول، ولم نحدد ما إذا كان `HTTP 1.0` أو `HTTP 1.1`. كل هذه التفاصيل مخفية بواسطة `test::nginx`، لذا لا داعي للاهتمام بها. هذه إحدى فوائد DSL - نحتاج فقط إلى التركيز على منطق الأعمال دون تشتيت الانتباه بكل التفاصيل.

أيضًا، يوفر هذا مرونة جزئية. على سبيل المثال، الإعداد الافتراضي هو بروتوكول `HTTP 1.1`، أو إذا أردنا اختبار `HTTP 1.0`، يمكننا التحديد بشكل منفصل:

```
--- request
GET /t  HTTP/1.0
```

بالإضافة إلى طريقة `GET`، يجب أيضًا دعم طريقة `POST`. في المثال التالي، يمكننا `POST` السلسلة `hello world` إلى العنوان المحدد.

```
--- request
POST /t  
hello world
```

مرة أخرى، يقوم `test::nginx` بحساب طول جسم الطلب لك هنا، ويضيف رؤوس الطلب `host` و`connection` لضمان أن هذا طلب عادي تلقائيًا.

بالطبع، يمكننا إضافة تعليقات لجعلها أكثر قابلية للقراءة. تلك التي تبدأ بـ `#` سيتم التعرف عليها كتعليقات كود.

```
--- request
# post request
POST /t  
hello world
```

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

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

```
--- request eval
"POST /t
hello\x00\x01\x02
world\x03\x04\xff"
```

في المثال الأول، نستخدم `eval` لتحديد أحرف غير قابلة للطباعة، وهي إحدى استخداماته. المحتوى بين علامتي التنصيص سيتم التعامل معه كسلسلة Perl ثم يتم تمريره إلى `request` كوسيطة.

هنا مثال أكثر إثارة للاهتمام:

```
--- request eval
"POST /t\n" . "a" x 1024
```

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

- في Perl، نستخدم نقطة لتمثيل دمج السلاسل. أليس هذا مشابهًا إلى حد ما لنقطتي `Lua`؟
- يشير الحرف الصغير `x` إلى عدد مرات تكرار الحرف. على سبيل المثال، `"a" x 1024` أعلاه تعني أن الحرف "a" يتكرر 1024 مرة.

لذا، المثال الثاني يعني أن طريقة `POST` ترسل طلبًا يحتوي على `1024` حرف `a` إلى العنوان `/t`.

### `pipelined_requests`

بعد فهم كيفية إرسال طلب واحد، دعنا ننظر إلى كيفية إرسال طلبات متعددة. في `test::nginx`، يمكننا استخدام البدائية `pipelined_requests` لإرسال طلبات متعددة بالتسلسل ضمن نفس اتصال `keep-alive`:

```
--- pipelined_requests eval
["GET /hello", "GET /world", "GET /foo", "GET /bar"]
```

على سبيل المثال، سيقوم هذا المثال بالوصول إلى هذه الواجهات الأربع بالتسلسل في نفس الاتصال. هناك ميزتان حول هذا:

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

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

يتعلق الأمر بطريقة تنفيذ `test::nginx`، والتي تعمل بشكل مختلف عما قد تعتقده. بعد كل حالة اختبار، يقوم `test::nginx` بإيقاف عملية NGINX الحالية، وتختفي جميع البيانات في الذاكرة. عند تشغيل حالة الاختبار التالية، يتم إنشاء `nginx.conf` من جديد، ويتم بدء `Worker` NGINX جديد. هذه الآلية تضمن أن حالات الاختبار لا تؤثر على بعضها البعض.

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

### `repeat_each`

لقد ذكرنا للتو حالة اختبار طلبات متعددة، فكيف يجب أن ننفذ نفس الاختبار عدة مرات؟

بالنسبة لهذه المشكلة، يوفر `test::nginx` إعدادًا عامًا: `repeat_each`، وهي وظيفة Perl تكون افتراضيًا `repeat_each(1)`، مما يشير إلى أن حالة الاختبار ستتم مرة واحدة فقط. لذا في حالات الاختبار السابقة، لا نزعج أنفسنا بتعيينها بشكل منفصل.

بطبيعة الحال، يمكننا تعيينها قبل وظيفة `run_test()`، على سبيل المثال، بتغيير الوسيطة إلى `2`.

```
repeat_each(2);
run_tests();
```

ثم، يتم تشغيل كل حالة اختبار مرتين، وهكذا.

### `more_headers`

بعد الحديث عن جسم الطلب، دعنا ننظر إلى رؤوس الطلب. كما ذكرنا أعلاه، يقوم `test::nginx` بإرسال الطلب مع رؤوس `host` و`connection` بشكل افتراضي. ماذا عن رؤوس الطلب الأخرى؟

تم تصميم `more_headers` خصيصًا للقيام بذلك.

```
--- more_headers
X-Foo: blah
```

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

```
--- more_headers
X-Foo: 3
User-Agent: openresty
```

## معالجة الاستجابات

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

### `response_body`

نظير بدائية الطلب هو `response_body`، وفيما يلي مثال على استخدامهما معًا:

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

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

```
--- response_body_like
^he\w+$
```

هذا يسمح لك بالمرونة الكبيرة مع جسم الاستجابة. بالإضافة إلى ذلك، يدعم `test::nginx` أيضًا عمليات `unlike`:

```
--- response_body_unlike
^he\w+$
```

في هذه المرحلة، إذا كان جسم الاستجابة هو `hello`، فلن ينجح الاختبار.

على نفس المنوال، بعد فهم الكشف عن طلب واحد، دعنا ننظر إلى الكشف عن طلبات متعددة. هنا مثال على كيفية استخدامه مع `pipelined_requests`:

```
--- pipelined_requests eval
["GET /hello", "GET /world", "GET /foo", "GET /bar"]
--- response_body eval
["hello", "world", "oo", "bar"]
```

بالطبع، الشيء المهم الذي يجب ملاحظته هنا هو أنه بقدر ما ترسل من طلبات، تحتاج إلى وجود استجابات مقابلة لها.

### `response_headers`

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

```
--- response_headers
X-RateLimit-Limit: 2
X-RateLimit-Remaining: 1
```

مثل الكشف عن جسم الاستجابة، تدعم رؤوس الاستجابة أيضًا التعبيرات العادية وعمليات `unlike`، مثل `response_headers_like`، `raw_response_headers_like`، و`raw_response_headers_unlike`.

### `error_code`

الثالث هو رمز الاستجابة. يدعم الكشف عن رمز الاستجابة المقارنة المباشرة ويدعم أيضًا عمليات `like`، مثل المثالين التاليين:

```
--- error_code: 302
```

```
--- error_code_like: ^(?:500)?$
```

في حالة الطلبات المتعددة، يجب التحقق من `error_code` عدة مرات:

```
--- pipelined_requests eval
["GET /hello", "GET /hello", "GET /hello", "GET /hello"]
--- error_code eval
[200, 200, 503, 503]
```

### `error_log`

عنصر الاختبار الأخير هو سجل الأخطاء. في معظم حالات الاختبار، لا يتم إنشاء سجل أخطاء. يمكننا استخدام `no_error_log` للكشف:

```
--- no_error_log
[error]
```

في المثال أعلاه، إذا ظهرت السلسلة `[error]` في `error.log` الخاص بـ NGINX، فسيفشل الاختبار. هذه ميزة شائعة جدًا، ويوصى بإضافة الكشف عن سجل الأخطاء إلى جميع اختباراتك العادية.

```
--- error_log
hello world
```

التكوين أعلاه يكشف عن وجود `hello world` في `error.log`. بالطبع، يمكنك استخدام `eval` المضمن في كود Perl لتنفيذ الكشف باستخدام التعبيرات العادية، مثل التالي:

```
--- error_log eval
qr/\[notice\] .*?  \d+ hello world/
```

## الخلاصة

اليوم، نتعلم كيفية إرسال الطلبات واختبار الاستجابات في `test::nginx`، بما في ذلك جسم الطلب، الرأس، رمز حالة الاستجابة، وسجل الأخطاء. يمكننا تنفيذ مجموعة كاملة من حالات الاختبار باستخدام مجموعة هذه البدائيات.

أخيرًا، هنا سؤال للتفكير: ما هي مزايا وعيوب `test::nginx`، وهي لغة DSL مجردة؟ لا تتردد في ترك تعليقات والمناقشة معي، وأنت أيضًا مرحب بك لمشاركة هذه المقالة للتواصل والتفكير معًا.

Share article link

Share

Related Posts

[![OpenResty الأسئلة الشائعة | هيكل الشبكة للاختبار، الميزات المتعلقة بـ SSL، DSL، أداة \`ab\`](https://static.api7.ai/2022/12/02/63896c177fde1.png)](https://api7.ai/learning-center/openresty/faq-3)

## [OpenResty الأسئلة الشائعة | هيكل الشبكة للاختبار، الميزات المتعلقة بـ SSL، DSL، أداة \`ab\`](https://api7.ai/learning-center/openresty/faq-3)

[![الاستخدامات غير المعروفة لـ \`test::nginx\`](https://static.api7.ai/2022/11/25/63802a7fb4697.png)](https://api7.ai/learning-center/openresty/little-known-usage-of-test-nginx)

## [الاستخدامات غير المعروفة لـ \`test::nginx\`](https://api7.ai/learning-center/openresty/little-known-usage-of-test-nginx)

Table of Contents

- [تكوين NGINX](#%D8%AA%D9%83%D9%88%D9%8A%D9%86-nginx)

- [إرسال الطلبات](#%D8%A5%D8%B1%D8%B3%D8%A7%D9%84-%D8%A7%D9%84%D8%B7%D9%84%D8%A8%D8%A7%D8%AA)

  - [`request`](#request)
  - [`pipelined_requests`](#pipelined_requests)
  - [`repeat_each`](#repeat_each)
  - [`more_headers`](#more_headers)

- [معالجة الاستجابات](#%D9%85%D8%B9%D8%A7%D9%84%D8%AC%D8%A9-%D8%A7%D9%84%D8%A7%D8%B3%D8%AA%D8%AC%D8%A7%D8%A8%D8%A7%D8%AA)

  - [`response_body`](#response_body)
  - [`response_headers`](#response_headers)
  - [`error_code`](#error_code)
  - [`error_log`](#error_log)

- [الخلاصة](#%D8%A7%D9%84%D8%AE%D9%84%D8%A7%D8%B5%D8%A9)
