You are browsing the book for Symfony 5.0 which is not maintained anymore. Code examples might not work anymore, even in a project using Symfony 5.0.
Consider reading the book for Symfony 5.2 instead.
خطوة 17: الإختبار
الإختبار¶
حيث اننا نقوم بإضافه المزيد من الوظائف للتطبيق. يبدو انه الوقت المناسب للتحدث عن الاختبارات (Tests)
حقيقة مضحكة: لقد وجدت خطأ أثناء كتابة الاختبارات في هذا الفصل.
سيمفوني يعتمد علي PHPUnit لاختبار الوحدات. لنقم بتنصيبه
1 | $ symfony composer req phpunit --dev
|
كتابة وحدات الاختبار¶
SpamChecker
هو أول شئ سنقوم بكتابة إختبارات له. لصنع وحدة :
1 | $ symfony console make:unit-test SpamCheckerTest
|
اختبار كاشف الزيف تحدي. حيث اننا بالتاكيد لا نريد ان نرسل لـ Akismet API. لذلك سنصنع mock للـAPI
لنقوم بكتابة أول اختبار عندما تعطينا الـ API خطأ:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 | --- a/tests/SpamCheckerTest.php
+++ b/tests/SpamCheckerTest.php
@@ -2,12 +2,26 @@
namespace App\Tests;
+use App\Entity\Comment;
+use App\SpamChecker;
use PHPUnit\Framework\TestCase;
+use Symfony\Component\HttpClient\MockHttpClient;
+use Symfony\Component\HttpClient\Response\MockResponse;
+use Symfony\Contracts\HttpClient\ResponseInterface;
class SpamCheckerTest extends TestCase
{
- public function testSomething()
+ public function testSpamScoreWithInvalidRequest()
{
- $this->assertTrue(true);
+ $comment = new Comment();
+ $comment->setCreatedAtValue();
+ $context = [];
+
+ $client = new MockHttpClient([new MockResponse('invalid', ['response_headers' => ['x-akismet-debug-help: Invalid key']])]);
+ $checker = new SpamChecker($client, 'abcde');
+
+ $this->expectException(\RuntimeException::class);
+ $this->expectExceptionMessage('Unable to check for spam: invalid (Invalid key).');
+ $checker->getSpamScore($comment, $context);
}
}
|
كائن الـ``MockHttpClient`` يمكننا من تقليد اي خادم HTTP. حيث يستقبل مصفوفه من كائنات MockResponse
التي تحتوي علي المحتوي المتوقع و رؤوس الاستجابه (Response headers)
بعد ذلك، نستدعي دالة getSpamScore()
و نتحقق من ظهور الخطآ عن طريق دالة expectException()
من PHPUnit
قم بتشغيل الاختبارات للتحقق من نجاحها:
1 | $ symfony php bin/phpunit
|
لنقم بإضافة اختبارات للمسار السعيد:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 | --- a/tests/SpamCheckerTest.php
+++ b/tests/SpamCheckerTest.php
@@ -24,4 +24,32 @@ class SpamCheckerTest extends TestCase
$this->expectExceptionMessage('Unable to check for spam: invalid (Invalid key).');
$checker->getSpamScore($comment, $context);
}
+
+ /**
+ * @dataProvider getComments
+ */
+ public function testSpamScore(int $expectedScore, ResponseInterface $response, Comment $comment, array $context)
+ {
+ $client = new MockHttpClient([$response]);
+ $checker = new SpamChecker($client, 'abcde');
+
+ $score = $checker->getSpamScore($comment, $context);
+ $this->assertSame($expectedScore, $score);
+ }
+
+ public function getComments(): iterable
+ {
+ $comment = new Comment();
+ $comment->setCreatedAtValue();
+ $context = [];
+
+ $response = new MockResponse('', ['response_headers' => ['x-akismet-pro-tip: discard']]);
+ yield 'blatant_spam' => [2, $response, $comment, $context];
+
+ $response = new MockResponse('true');
+ yield 'spam' => [1, $response, $comment, $context];
+
+ $response = new MockResponse('false');
+ yield 'ham' => [0, $response, $comment, $context];
+ }
}
|
يسمح موفر البيانات (PHPUnit data providers) باستخدام نفس الاختبار لتجربة اكثر من حالة.
لنكتب الاختبار الوظيفي للـمتحكمات (Controllers)¶
اختبار وحدات التحكم يختلف قليلا عن اختبار كائن PHP "عادي" حيث اننا نريد ان نشغل وحدات التحكم في سياق طلب من الخادم (HTTP Request)
لنقم بتنصيب بعض الحزم الإضافية التي نحتاجها للاختبارات الوظيفية:
1 | $ symfony composer req browser-kit --dev
|
لنصنع اختبار وظيفي لوحده تحكم المؤتمرات:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 | namespace App\Tests\Controller;
use Symfony\Bundle\FrameworkBundle\Test\WebTestCase;
class ConferenceControllerTest extends WebTestCase
{
public function testIndex()
{
$client = static::createClient();
$client->request('GET', '/');
$this->assertResponseIsSuccessful();
$this->assertSelectorTextContains('h2', 'Give your feedback');
}
}
|
الإختبار الأول يتحقق ان الصفحة الرئيسية تعطي استجابه بكود 200.
متغير الـ `$client`
يحاكي متصفح الانترنت. ف بدلا من ارسال طلبات HTTP للخادم، يقوم بإرسالها الي تطبيق سيمفوني مباشرة. هذه الإستراتيجيه لها فوائد كثيره: إنها اسرع من الطلبات بين الخادم و العميل، و ايضا تسمح باستكشاف حاله الخدمات بعد كل طلب (HTTP Request)
التاكيدات مثل assertResponseIsSuccessful
موجوده فوق PHPUnit لتسهل عليك العمل. يوجد الكثير من التاكيدات المعرفه من سيمفوني
Tip
قمنا باستخدام /
كرابط بدلا من صنعه عن طريق وحدة التوجيه (Router). تم ذلك عن قصد لأن اختبار الروابط للمستخدم النهائي هو جزء مما نريد اختباره. فلو قمت بتغيير مسار الرابط لاحقا. سيفشل الاختبار كتذكير لطيف انه يجب عليك تحويل المستخدم من الرابط القديم للرابط الجديد حتي تكون لطيف مع محركات البحث و المواقع التي تستخدم الرابط القديم لموقعك.
Note
كان يمكننا صنع الاختبار عن طريق حزمة الصانع:
1 | $ symfony console make:functional-test Controller\\ConferenceController
|
اختبارات PHPUnit يتم تشغلها في بيئه اختبار منفصله. يجب ان نضع قيمة AKISMET_KEY
السريه لبيئة الاختبار:
قم بتشغيل الاختبارات الجديدة فقط عن طريق تمرير مسار كائن الاختبار:
1 | $ symfony php bin/phpunit tests/Controller/ConferenceControllerTest.php
|
Tip
عندما يفشل اختبار، قد يكون من المفيد التحقق من كائن الاستجابه (Response object). يمكنك ان تصل اليه عن طريق $client->getResponse()
و echo
لتري كيف يبدو.
تعريف التركيبات¶
لتتمكن من اختبار قائمة التعليقات، ترقيم الصفحات، و نموذج التعليقات، نحتاج الى ملئ قاعدة البيانات ببعض التعليقات. ونريد أن تكون البيانات مستقرة عند تشغيل الإختبارات كي تنجح عن التشغيل. التركيبات تحديدا هي ما نحتاجه.
لنقوم بتنصيب حزمة تركيبات Doctrine:
1 | $ symfony composer req orm-fixtures --dev
|
مجلد جديد src/DataFixtures/
تم انشاؤه اثناء تنصيب الحزمه مع كائن بسيط. جاهز للتعديل. لنقوم باضافه مؤتمرين و تعليق واحد الان:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 | --- a/src/DataFixtures/AppFixtures.php
+++ b/src/DataFixtures/AppFixtures.php
@@ -2,6 +2,8 @@
namespace App\DataFixtures;
+use App\Entity\Comment;
+use App\Entity\Conference;
use Doctrine\Bundle\FixturesBundle\Fixture;
use Doctrine\Persistence\ObjectManager;
@@ -9,8 +11,24 @@ class AppFixtures extends Fixture
{
public function load(ObjectManager $manager)
{
- // $product = new Product();
- // $manager->persist($product);
+ $amsterdam = new Conference();
+ $amsterdam->setCity('Amsterdam');
+ $amsterdam->setYear('2019');
+ $amsterdam->setIsInternational(true);
+ $manager->persist($amsterdam);
+
+ $paris = new Conference();
+ $paris->setCity('Paris');
+ $paris->setYear('2020');
+ $paris->setIsInternational(false);
+ $manager->persist($paris);
+
+ $comment1 = new Comment();
+ $comment1->setConference($amsterdam);
+ $comment1->setAuthor('Fabien');
+ $comment1->setEmail('[email protected]');
+ $comment1->setText('This was a great conference.');
+ $manager->persist($comment1);
$manager->flush();
}
|
عندما نقوم بجلب التركيبات، كل البيانات سيتم مسحها; ايضا المستخدم admin. لتجنب ذلك، لنقوم بإضافة مستخدم admin في التركيبات:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 | --- a/src/DataFixtures/AppFixtures.php
+++ b/src/DataFixtures/AppFixtures.php
@@ -2,13 +2,22 @@
namespace App\DataFixtures;
+use App\Entity\Admin;
use App\Entity\Comment;
use App\Entity\Conference;
use Doctrine\Bundle\FixturesBundle\Fixture;
use Doctrine\Persistence\ObjectManager;
+use Symfony\Component\Security\Core\Encoder\EncoderFactoryInterface;
class AppFixtures extends Fixture
{
+ private $encoderFactory;
+
+ public function __construct(EncoderFactoryInterface $encoderFactory)
+ {
+ $this->encoderFactory = $encoderFactory;
+ }
+
public function load(ObjectManager $manager)
{
$amsterdam = new Conference();
@@ -30,6 +39,12 @@ class AppFixtures extends Fixture
$comment1->setText('This was a great conference.');
$manager->persist($comment1);
+ $admin = new Admin();
+ $admin->setRoles(['ROLE_ADMIN']);
+ $admin->setUsername('admin');
+ $admin->setPassword($this->encoderFactory->getEncoder(Admin::class)->encodePassword('admin', null));
+ $manager->persist($admin);
+
$manager->flush();
}
}
|
Tip
اذا كنت لا تتذكر أي خدمة تحتاجها لمهمة معينة. استخدم debug:autowiring
مع بعض الكلمات:
1 | $ symfony console debug:autowiring encoder
|
تحميل التركيبات¶
تحميل التركيبات الي قاعدة البيانات. تحذير هده العمليه ستقوم بحذف كل البيانات الموجودة في قاعده البيانات (إذا كنت تريد تجنب ذلك، اكمل القراءه)
1 | $ symfony console doctrine:fixtures:load
|
استخراج البيانات من الموقع في الاختبارات الوظيفية¶
كما رأينا من قبل، الـ (HTTP Client) المتسخدم في الاختبارات يحاكي متصفح الانترنت. لذلك نستطيع التنقل في الموقع كاننا نستخدم المتصفح الغير مرئي (headless browser).
لنقوم بإضافة اختبار يضغط علي رابط المؤتمر من الصفحة الرئيسية:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 | --- a/tests/Controller/ConferenceControllerTest.php
+++ b/tests/Controller/ConferenceControllerTest.php
@@ -14,4 +14,19 @@ class ConferenceControllerTest extends WebTestCase
$this->assertResponseIsSuccessful();
$this->assertSelectorTextContains('h2', 'Give your feedback');
}
+
+ public function testConferencePage()
+ {
+ $client = static::createClient();
+ $crawler = $client->request('GET', '/');
+
+ $this->assertCount(2, $crawler->filter('h4'));
+
+ $client->clickLink('View');
+
+ $this->assertPageTitleContains('Amsterdam');
+ $this->assertResponseIsSuccessful();
+ $this->assertSelectorTextContains('h2', 'Amsterdam 2019');
+ $this->assertSelectorExists('div:contains("There are 1 comments")');
+ }
}
|
لنقم بوصف ماذا حدث في الاختبار بشكل بسيط:
- كما الاختبار الأول، قمنا بالذهاب للصفحة الرئيسية؛
- دالة الطلب
request()
تعطي كائن زاحفCrawler
يساعدنا في إيجاد عناصر في الصفحة (مثل الروابط، النماذج، اي شئ يمكن الوصول اليه بمحدد CSS او XPath)؛ - بفضل محدد CSS، نقوم بتاكيد ان لدينا مؤتمرين فقط في الصفحة الرئيسية؛
- بعد ذلك نضغط علي عرض الرابط "View" (بما انه لا يمكن الضغط علي اكثر من رابط في وقت واحد. سيمفوني تلقائي يختار أول عنصر من القائمة)؛
- قمنا بالتاكد من عنوان الصفحة، المحتوي و
<h2>
للتاكد من اننا في الصفحة الصحيحة (يمكننا ايضا التاكد من ان رابط الصفحة مطابق للرابط المتوقع)؛ - في النهايه، قمنا بالتاكد انه يوجد تعليق واحد في الصفحه.
div:contains()
ليس من المحددات (CSS selector)، ولكن سيمفوني لديها بعض الاضافات، تم استعارتها من jQuery.
بدلا من الضغط علي النص (مثل View
)، كان يمكننا تحديد الرابط عن طريق محدد css:
1 | $client->click($crawler->filter('h4 + p a')->link());
|
لنتاكد من أن الاختبار الجديد أخضر:
1 | $ symfony php bin/phpunit tests/Controller/ConferenceControllerTest.php
|
لنعمل مع اختبار قاعدة البيانات¶
بشكل افتراضي; الاختبارات يتم تشغيلها في بيئه "اختبار" سيمفوني كما هو معرف في ملف``phpunit.xml.dist``:
1 2 3 4 5 | <phpunit>
<php>
<server name="APP_ENV" value="test" force="true" />
</php>
</phpunit>
|
اذا كنت تريد استخدام قاعدة بيانات مختلفه للاختبارات، قم بتغيير DATABASE_URL
في ملف .env.test
:
1 2 3 4 5 6 7 8 | --- a/.env.test
+++ b/.env.test
@@ -1,4 +1,5 @@
# define your env variables for the test env here
+DATABASE_URL=postgres://main:[email protected]:32773/test?sslmode=disable&charset=utf8
KERNEL_CLASS='App\Kernel'
APP_SECRET='$ecretf0rt3st'
SYMFONY_DEPRECATIONS_HELPER=999999
|
تحميل التركيبات لاختبار قاعدة البيانات:
1 | $ APP_ENV=test symfony console doctrine:fixtures:load
|
لباقي الخطوات، لن نقوم بإعادة تعريف الـ DATABASE_URL
. حيث ان استخدام نفس قاعدة البيانات لبيئة الـ dev
مع الاختبارات له بعض المميزات سنتعرف عليها في القسم التالي.
التعامل مع النماذج في الاختبارات الوظيفية¶
هل تريد الانتقال للمستوي التالي؟ جرب ان تقوم بإضافه تعليق وصورة علي مؤتمر من الاختبار عن طريق محاكاة تقديم نموذج Form submission
. يبدو طموح، اليس كذلك؟ تفقد الكود المطلوب: ليس اصعب من ما قمنا بكتابته مسبقا:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 | --- a/tests/Controller/ConferenceControllerTest.php
+++ b/tests/Controller/ConferenceControllerTest.php
@@ -29,4 +29,19 @@ class ConferenceControllerTest extends WebTestCase
$this->assertSelectorTextContains('h2', 'Amsterdam 2019');
$this->assertSelectorExists('div:contains("There are 1 comments")');
}
+
+ public function testCommentSubmission()
+ {
+ $client = static::createClient();
+ $client->request('GET', '/conference/amsterdam-2019');
+ $client->submitForm('Submit', [
+ 'comment_form[author]' => 'Fabien',
+ 'comment_form[text]' => 'Some feedback from an automated functional test',
+ 'comment_form[email]' => '[email protected]',
+ 'comment_form[photo]' => dirname(__DIR__, 2).'/public/images/under-construction.gif',
+ ]);
+ $this->assertResponseRedirects();
+ $client->followRedirect();
+ $this->assertSelectorExists('div:contains("There are 2 comments")');
+ }
}
|
لتقديم نموذج عن طريق submitForm()
، قم بإيجاد اسماء المدخلات بفضل ادوات التطوير الخاصه بالمتصفح او عن طريق لوحه النموذج من محلل سيمفوني (Symfony Profiler). لاحظ الاستخدام الذكي لإعادة استخدام صوره تحت البناء (Under construction)!
قم بتشغيل الإختبارات مرة أخري للتاكد من أن كل شئ أخضر:
1 | $ symfony php bin/phpunit tests/Controller/ConferenceControllerTest.php
|
من أحد المميزات عند استخدام قاعدة البيانات الخاصة ببيئة "dev" للاختبارات هو أنه يمكنك فحص النتائج من المتصفح:

إعادة تحميل التركيبات¶
إذا قمت بتشغيل الاختبارات مره اخري، ستفشل، حيث انه هناك اكثر من تعليق في قاعدة البيانات، المتإكد الذي يتحقق من عدد التعليقات يفشل. نحتاج ان نعيد حاله قاعدة البيانات كل مره نقوم بتشغيل الاختبارات عن طريق إعادة تشغيل التركيبات:
1 2 | $ symfony console doctrine:fixtures:load
$ symfony php bin/phpunit tests/Controller/ConferenceControllerTest.php
|
أتمتة (Automating) خطوات العمل مع Makefile¶
من المزعج تذكر خطوات تشغيل الإختبارت في كل مره. يجب توثيقها علي الأقل. ولكن التوثيق ينبغي ان يكون ملاذنا الأخير. بدلاً من ذلك، ما رايك ان نجعلها اتوماتيكه؟ سوف تخدم كتوثيق و ايضاً تساعد المطورين الاخريين، وتجعل حياة المطوريين اسهل و اسرع
ان استخدام MakeFile
طريقة واحده لجعل الأوامر اوتوماتيكية
1 2 3 4 5 6 | SHELL := /bin/bash
tests:
symfony console doctrine:fixtures:load -n
symfony php bin/phpunit
.PHONY: tests
|
لاحظ الـ -n
في امر Doctrine، هو علم طبيعي في اوامر سيمفوني يجعلهم غير متفاعلين.
متي تريد ان تشغل الاختبارات، استخدم make tests
1 | $ make tests
|
إعادة ضبت قاعدة البيانات بعد كل اختبار¶
إعادة ضبت قاعدة البيانات بعد كل اختبار امر لطيف، ولكن الاختبارات المستقله افضل. لا نريد ان يكون لدينا اختبار يعتمد علي نتائج اختباره اخر قبله. تغيير ترتيب الاختبارات لا ينبغي ان يغير النتائج. كما سنعرف الان. هذه ليست القضيه التي نريدها الان.
قم بنقل اختبار testConferencePage
بعد testCommentSubmission
:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 | --- a/tests/Controller/ConferenceControllerTest.php
+++ b/tests/Controller/ConferenceControllerTest.php
@@ -15,21 +15,6 @@ class ConferenceControllerTest extends WebTestCase
$this->assertSelectorTextContains('h2', 'Give your feedback');
}
- public function testConferencePage()
- {
- $client = static::createClient();
- $crawler = $client->request('GET', '/');
-
- $this->assertCount(2, $crawler->filter('h4'));
-
- $client->clickLink('View');
-
- $this->assertPageTitleContains('Amsterdam');
- $this->assertResponseIsSuccessful();
- $this->assertSelectorTextContains('h2', 'Amsterdam 2019');
- $this->assertSelectorExists('div:contains("There are 1 comments")');
- }
-
public function testCommentSubmission()
{
$client = static::createClient();
@@ -44,4 +29,19 @@ class ConferenceControllerTest extends WebTestCase
$client->followRedirect();
$this->assertSelectorExists('div:contains("There are 2 comments")');
}
+
+ public function testConferencePage()
+ {
+ $client = static::createClient();
+ $crawler = $client->request('GET', '/');
+
+ $this->assertCount(2, $crawler->filter('h4'));
+
+ $client->clickLink('View');
+
+ $this->assertPageTitleContains('Amsterdam');
+ $this->assertResponseIsSuccessful();
+ $this->assertSelectorTextContains('h2', 'Amsterdam 2019');
+ $this->assertSelectorExists('div:contains("There are 1 comments")');
+ }
}
|
ستفشل الاختبارات الآن.
لتقوم بإعادة ضبت قاعدة البيانات بين الاختبارات، قم بتنصيب DoctrineTestBundle:
1 | $ symfony composer req "dama/doctrine-test-bundle:^6" --dev
|
ستحتاج ان توافق علي تنصيب الوصفه (بما انها ليست حزمه مدعومه "رسمياً"):
1 2 3 4 5 6 7 8 9 10 11 | Symfony operations: 1 recipe (d7f110145ba9f62430d1ad64d57ab069)
- WARNING dama/doctrine-test-bundle (>=4.0): From github.com/symfony/recipes-contrib:master
The recipe for this package comes from the "contrib" repository, which is open to community contributions.
Review the recipe at https://github.com/symfony/recipes-contrib/tree/master/dama/doctrine-test-bundle/4.0
Do you want to execute this recipe?
[y] Yes
[n] No
[a] Yes for all packages, only for the current installation session
[p] Yes permanently, never ask again for this project
(defaults to n): p
|
تشغيل مستمع PHPUnit:
1 2 3 4 5 6 7 8 9 10 11 12 13 | --- a/phpunit.xml.dist
+++ b/phpunit.xml.dist
@@ -27,6 +27,10 @@
</whitelist>
</filter>
+ <extensions>
+ <extension class="DAMA\DoctrineTestBundle\PHPUnit\PHPUnitExtension" />
+ </extensions>
+
<listeners>
<listener class="Symfony\Bridge\PhpUnit\SymfonyTestsListener" />
</listeners>
|
و انتهينا. اي تغييرات علي قاعدة البيانات اثناء الاختبارات تلقائيا يتم تجاهلها بعد الانتهاء من كل اختبار.
ينبغي ان تكون الاختبارات خضراء الآن:
1 | $ make tests
|
استخدام متصفح حقيقي للاختبارات الوظيفيه¶
الاختبارات الوظيفيه تستخدم متصفح مخصص يتصل بيسمفوني مباشرة. لكن يمكنك استخدام متصفح حقيقي يتصل بـ HTTP بفضل Panther من سيمفوني:
1 | $ symfony composer req panther --dev
|
يمكنك كتابه اختبارات تستخدم متصفح Google Chrome مع التغييرات الآتيه:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 | --- a/tests/Controller/ConferenceControllerTest.php
+++ b/tests/Controller/ConferenceControllerTest.php
@@ -2,13 +2,13 @@
namespace App\Tests\Controller;
-use Symfony\Bundle\FrameworkBundle\Test\WebTestCase;
+use Symfony\Component\Panther\PantherTestCase;
-class ConferenceControllerTest extends WebTestCase
+class ConferenceControllerTest extends PantherTestCase
{
public function testIndex()
{
- $client = static::createClient();
+ $client = static::createPantherClient(['external_base_uri' => $_SERVER['SYMFONY_PROJECT_DEFAULT_ROUTE_URL']]);
$client->request('GET', '/');
$this->assertResponseIsSuccessful();
|
المتغير SYMFONY_PROJECT_DEFAULT_ROUTE_URL
يحتوي علي رابط الخادم المحلي.
تشغيل اختبارات الصندوق الأسود الوظيفية باستخدام Blackfire¶
طريقه اخري لتشغيل الاختبارات الوظيفيه عن طريق Blackfire player. بالاضافه لما يمكنك فعله مع الاختبارات الوظيفيه. يمكنك ان تؤدي اختبارات الاداء.
تفقد خطوه الاداء "Performance" لمعرفه المزيد.
- « Previous خطوة 16: منع البريد العشوائي باستخدام واجهة برمجة التطبيقات (API)
- Next » خطوة 18: الذهاب المتزامن
This work, including the code samples, is licensed under a Creative Commons BY-NC-SA 4.0 license.