تست مرحله مهمی در چرخه عمر توسعه نرم افزار است. این کار تضمین می کند کدی که نوشته اید قبل از اینکه قسمت بعدی پروژه خود را پیاده سازی همانطور که طراحی شده است کار می کند. هر بار که نوشتن یک جز نرم افزاری را به پایان می رسانید، باید یک تست نیز بنویسید تا بررسی کنید که رفتار آن با انتظارات شما مطابقت دارد. این فرآیند به حفظ کیفیت کد شما کمک می کند. بنابراین بیاید برای آموزش تست نویسی در لاراول وقت بگذرایم!
وقتی در مورد تست کردن در لاراول صحبت می کنیم، معمولا دو چیز را در نظر می گیریم: یونیت تست و فیچر تست. یونیت تست فقط بر روی یک قطعه کوچک از کد (معمولا متدی در داخل یک کلاس) تمرکز میکند، در حالی که فیچر تست بررسی میکند آیا یک ویژگی خاص، که ممکن است متشکل از چندین متد یا کلاس در ارتباط با یکدیگر باشد، به روشی که شما طراحی کردهاید کار میکند یا خیر.
در این آموزش قصد داریم در مورد نحوه نوشتن یونیت تست ها در پروژه لاراول یا آموزش تست نویسی در لاراول با استفاده از PHPUnit، محبوب ترین پکیج یونیت تست برای پروژه های PHP صحبت کنیم. این پکیج به خوبی با لاراول پیکربندی شده است، بنابراین پس از ایجاد یک پروژه جدید لاراول نیازی به پیکربندی اضافی نیست.
نکته ای که باید به آن توجه کنید این است که اگر چه PHPUnit در اصل برای یونیت تست طراحی شده است، اگر از آن برای فیچر تست نیز استفاده کنید هیچ مشکلی ندارد. این پکیج شامل بسیاری از assert های قدرتمند است که می تواند در بسیاری از حالت های مختلف تست کردن مفید باشد.
در ادامه این آموزش، ویژگی های یونیت تست در لاراول را خواهید آموخت:
قبل از ادامه این آموزش، مطمئن شوید که شرایط زیر را دارید:
بیایید با ایجاد یک پروژه جدید لاراول از ابتدا شروع کنیم. تیم لاراول ابزاری به نام Laravel Sail ارائه کرده است که به ما اجازه می دهد تا زمانی که Docker را نصب و اجرا می کنیم، بدون توجه به سیستم عاملی که استفاده می کنیم، به سرعت یک پروژه را راه اندازی کنیم.
ترمینال را باز کنید و دستور زیر را برای ایجاد یک پروژه جدید اجرا کنید:
curl -s https://laravel.build/<your_project_name> | bash
با این کار یک پروژه لاراول جدید در دایرکتوری که دستور را در آن اجرا کرده اید ایجاد می شود. بعد، وارد پروژه ای که به تازگی ایجاد کرده اید شوید:
cd <your_project_name>
در دایرکتوری پروژه، دستور sail up را مطابق شکل زیر اجرا کنید. مطمئن شوید که هر نمونه ای از Redis یا MySQL را در صورتی که روی دستگاه شما اجرا می شود متوقف کنید، در غیر این صورت ممکن است با خطاهایی در مورد در دسترس نبودن پورت ها مواجه شوید.
./vendor/bin/sail up
Laravel Sail به طور خودکار هر آنچه را که نیاز دارید در داخل یک کانتینر داکر نصب می کند، مانند پایگاه داده، Redis، و حتی یک سرور ایمیل، و تنها کاری که باید انجام دهیم این است که منتظر بمانیم. (build کردن) ساخت برای اولین بار ممکن است چند دقیقه طول بکشد، اما build های بعدی بسیار سریعتر خواهند بود. اگر خروجی زیر را مشاهده کردید به این معنی است که Laravel Sail با موفقیت راه اندازی شده است.
خروجی:
Starting code_mailhog_1 ... Starting code_mailhog_1 ... done Starting code_redis_1 ... done Starting code_meilisearch_1 ... done Starting code_mysql_1 ... done Creating code_laravel.test_1 ... done . . . laravel.test_1 | Starting Laravel development server: http://0.0.0.0:80 laravel.test_1 | [Thu Jun 2 17:37:32 2022] PHP 8.1.5 Development Server (http://0.0.0.0:80) started
قبل از شروع تست نویسی، بیایید نگاهی به تست های پیش فرضی که لاراول برای ما ارائه کرده است بیاندازیم. این کار به ما کمک می کند تا بفهمیم تست در لاراول چگونه کار می کند.
یک فایل phpunit.xml در دایرکتوری root پروژه وجود دارد. این فایل پیکربندی برای PHPUnit، و یک فریم ورک آزمایشی برای برنامه های PHP است. مطابق شکل زیر به سه بخش اصلی تقسیم می شود:
<?xml version="1.0" encoding="UTF-8"?> <phpunit xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xsi:noNamespaceSchemaLocation="./vendor/phpunit/phpunit/phpunit.xsd" bootstrap="vendor/autoload.php" colors="true"> <testsuites> <testsuite name="Unit"> <directory suffix="Test.php">./tests/Unit</directory> </testsuite> <testsuite name="Feature"> <directory suffix="Test.php">./tests/Feature</directory> </testsuite> </testsuites> <coverage processUncoveredFiles="true"> <include> <directory suffix=".php">./app</directory> </include> </coverage> <php> <env name="APP_ENV" value="testing" /> <env name="BCRYPT_ROUNDS" value="4" /> <env name="CACHE_DRIVER" value="array" /> <env name="DB_DATABASE" value="testing" /> <env name="MAIL_MAILER" value="array" /> <env name="QUEUE_CONNECTION" value="sync" /> <env name="SESSION_DRIVER" value="array" /> <env name="TELESCOPE_ENABLED" value="false" /> </php> </phpunit>
در قسمت <testsuites>
دو نوع تست مختلف برای ما از پیش تعریف شده است. این بخش به PHPUnit میگوید که تست هایی را که در دایرکتوریهای tests/Unit/.
و tests/Feature/.
ذخیره میشوند، اجرا کند. در مرحله بعدی این آموزش نگاه دقیق تری به هر دو دایرکتوری خواهیم داشت.
در مرحله بعد، بخش <coverage>
جایی است که ما تعریف می کنیم چه کد و چه دایرکتوری هایی در تست پوشش داده شده اند و چگونه پوشش داده می شوند. برای این آموزش مهم نیست، بنابراین می توانیم آن را همانطور که هست رها کنیم.
در نهایت در قسمت <php>
می توانیم متغیرهای محیطی را برای محیط تست تعریف کنیم. هنگام اجرای یک آزمایش، متغیرهای تعریف شده در اینجا، متغیرهای تعریف شده در فایل .env
را که در ریشه پروژه نیز وجود دارد، بازنویسی می کنند.
دایرکتوری tests جایی است که ما تمام فایل های آزمایشی خود را ذخیره می کنیم. تست های ویژگی باید در دایرکتوری tests/Feature
باشد، در حالی که یونیت تست ها باید در پوشه tests/Unit
باشد. PHPUnit به طور خودکار تست هایی را که در داخل این دایرکتوری ها ذخیره شده اند، همانطور که در فایل phpunit.xml
تعریف شده است، جستجو و اجرا می کند. فایل های CreatesApplication.php
و TestCase.php
برنامه را قبل از اجرای تست ها بوت می کنند. ما نیازی به تغییر چیزی در این فایل ها نداریم.
بیایید به یک نمونه تست نگاهی بیندازیم. فایل tests/Unit/ExampleTest.php/.
را در ویرایشگر متن خود باز کنید:
code ./tests/Unit/ExampleTest.php
tests/Unit/ExampleTest.php/.
<?php namespace Tests\Unit; use PHPUnit\Framework\TestCase; class ExampleTest extends TestCase { /** * A basic test example. * * @return void */ public function test_that_true_is_true() { $this->assertTrue(true); } }
این تست نمونه کاملا ابتدایی و ساده است زیرا درست بودن تست را بررسی می کند. متد ()assertTrue
منتظر دستوری است که مقدار بولی true را برمی گرداند. این متد یک assert نامیده می شود و PHPUnit بسیاری از assert های مفید دیگر را در اختیار ما قرار می دهد که در ادامه این مقاله به آنها پرداخته خواهد شد. شما می توانید لیست کاملی از تمام assert های PHPUnit را در اسناد رسمی آنها بیابید.
برای اجرای این تست، یک ترمینال جدید را باز کنید (به طوری که Laravel Sail همچنان در حال اجرا باشد) و دستور زیر را اجرا کنید. مطمئن شوید که در دایرکتوری اصلی پروژه خود هستید.
./vendor/bin/phpunit
Time: 00:00.123, Memory: 20.00 MB OK (2 tests, 2 assertions)
این خروجی نشان می دهد که لاراول دو تست و دو assert را اجرا کرده است (تست نمونه دیگری در دایرکتوری Feature وجود دارد که شامل assert دیگری است) و هر دو موفقیت آمیز هستند.
ما میتوانیم با تغییر آرگومان ارسال شده به متد ()assertTrue مطابق شکل زیر باعث شکست تست شویم:
tests/Unit/ExampleTest.php/.
. . . public function test_that_true_is_true() { $this->assertTrue("this is a string"); } . . .
حالا دوباره تست را اجرا کنید:
./vendor/bin/phpunit
خروجی:
Time: 00:00.141, Memory: 20.00 MB There was 1 failure: 1) Tests\Unit\ExampleTest::test_that_true_is_true Failed asserting that 'this is a string' is true. /Users/erichu/Documents/GitHub/laravel-unit-test/code/tests/Unit/ExampleTest.php:16 FAILURES! Tests: 2, Assertions: 2, Failures: 1.
وقتی تغییرات بالا را لغو کردید، تست باید دوباره پذیرفته شود. در مرحله بعد، بیایید با ایجاد تست دیگری در زیر تابع ()test_that_true_is_true
مورد هیجان انگیزتری را امتحان کنیم:
tests/Unit/ExampleTest.php/.
<?php namespace Tests\Unit; use PHPUnit\Framework\TestCase; class ExampleTest extends TestCase { . . . /** * A basic test example. * * @return void */ public function test_that_name_is_jack() { $name = "John"; // $name = "Jack"; $this->assertTrue($name == "Jack"); } }
از آنجایی که متغیر name$
با John مقداردهی شده است، بدیهی است که John برابر با Jack نیست، و این تست با شکست مواجه خواهد شد.
این تست را با دستور زیر اجرا کنید:
./vendor/bin/phpunit
خروجی:
Time: 00:00.008, Memory: 8.00 MB There was 1 failure: 1) Tests\Unit\ExampleTest::test_that_name_is_jack Failed asserting that false is true. /Users/erichu/Documents/GitHub/laravel-unit-test/code/tests/Unit/ExampleTest.php:29 FAILURES! Tests: 2, Assertions: 2, Failures: 1.
قبل از اینکه به مرحله بعدی ادامه دهید، name$
را به "Jack" تغییر دهید تا در تست های بعدی با شکست مواجه نشوید.
در این بخش، نحوه ایجاد تست های خود را از ابتدا یاد خواهیم گرفت. کد زیر کدی است که ما تست خواهیم کرد. اتاقی را تعریف می کند که شامل چند نفر است. میتوانیم فرد جدیدی را به اتاق اضافه کنیم، فردی را از اتاق خارج کنیم و بررسی کنیم که آیا فردی در اتاق است یا خیر.
یک فایل app/Room.php
جدید در ویرایشگر خود ایجاد کنید و کد زیر را در فایل قرار دهید:
code app/Room.php
app/Room.php/.
<?php namespace App; class Room { /** * @var array */ protected $people = []; /** * Constructor. Fill the room with the given people. * * @param array $people */ public function __construct($people = []) { $this->people = $people; } /** * Check if the specified person is in the room. * * @param string $person * @return bool */ public function has($person) { return in_array($person, $this->people); } /** * Add a new person to the room. * * @param string $person * @return array */ public function add($person) { array_push($this->people, $person); return $this->people; } /** * Remove a person from the room. * * @param string $person * @return array */ public function remove($person) { if (($key = array_search($person, $this->people)) !== false) { unset($this->people[$key]); } return $this->people; } }
اکنون، بیایید یک یونیت تست جدید با دستور زیر ایجاد کنیم:
./vendor/bin/sail php artisan make:test RoomTest --unit
خروجی:
Test created successfully.
دستور بالا یک فایل RoomTest.php
در دایرکتوری tests/Unit
ایجاد می کند و آن را با مقداری کد از پیش نوشته شده پر می کند:
tests/Unit/RoomTest.php/.
<?php namespace Tests\Unit; use PHPUnit\Framework\TestCase; class RoomTest extends TestCase { /** * A basic unit test example. * * @return void */ public function test_example() { $this->assertTrue(true); } }
assert های ()assertTrue و ()assertFalse
ادامه دهید و فایل tests/Unit/RoomTest.php
ایجاد شده را باز کنید و محتویات آن را به صورت زیر تغییر دهید:
tests/Unit/RoomTest.php/.
<?php namespace Tests\Unit; use PHPUnit\Framework\TestCase; // Import the room use App\Room; class RoomTest extends TestCase { /** * Test the has() method in Room class * * @return void */ public function test_room_has() { $room = new Room(["Jack", "Peter", "Amy"]); // Create a new room $this->assertTrue($room->has("Jack")); // Expect true $this->assertFalse($room->has("Eric")); // Expect false } }
ما قبلا متد ()assertTrue
را در مرحله 2 دیدهایم. متد ()assertFalse
به همین روش کار میکند با این تفاوت که به یک مقدار false نیاز دارد. در این مثال، "Jack" در اتاق است و "Eric" نیست، بنابراین room->has("Jack")$
مقدار true و room->has("Eric")$
مقدار false را برمیگرداند، که باعث میشود هر دو assertion پذیرفته شوند (pass شوند).
با اجرای دستور زیر می توانید آن را تست کنید. توجه داشته باشید که میتوانیم با تعیین مسیر فایل مانند این، یک فایل تست خاص را اجرا کنیم:
./vendor/bin/phpunit tests/Unit/RoomTest.php
خروجی:
Time: 00:00.012, Memory: 10.00 MB OK (1 tests, 2 assertions)
بیایید تست دیگری برای بررسی رفتار متد add بنویسیم. به جای ()assertTrue
یا ()assertFalse
، راه دیگری را امتحان خواهیم کرد. یک شخص جدید به اتاق اضافه می کنیم، سپس با استفاده از متد ()assertContains
بررسی می کنیم که آیا اتاق این شخص خاص را دارد یا خیر.
تابع زیر را به کلاس RoomTest در متد ()test_room_has
اضافه کنید:
tests/Unit/RoomTest.php/.
. . . /** * Test the add() method in Room class * * @return void */ public function test_room_add() { $room = new Room(["Jack"]); // Create a new room $this->assertContains("Peter", $room->add("Peter")); }
()assertContains
دو پارامتر دارد: اولی مقداری است که انتظار داریم آرایه باشد، و دومین پارامتر مقداری است که قرار است داشته باشد.
در مثال ما، یک اتاق جدید که فقط شامل Jack است ایجاد می شود و سپس room->add("Peter")$
را به اتاق اضافه می کند. در نهایت، از ()assertContains
استفاده می کنیم تا بررسی کنیم که آیا Peter در اتاق است یا خیر.
تست را با دستور زیر اجرا کنید:
./vendor/bin/phpunit tests/Unit/RoomTest.php
باید توجه داشته باشید که تست با موفقیت انجام می شود:
خروجی:
Time: 00:00.007, Memory: 8.00 MB OK (2 tests, 3 assertions)
لاراول همچنین یک ابزار خط فرمان قدرتمند به نام artisan ارائه می دهد که می توانیم از آن برای اجرای تست های خود نیز استفاده کنیم. دستور artisan خروجی دقیق تری ارائه می دهد:
./vendor/bin/sail php artisan test
خروجی:
PASS Tests\Unit\ExampleTest ✓ that true is true ✓ that name is jack PASS Tests\Unit\RoomTest ✓ room has ✓ room add Tests: 4 passed Time: 1.47s
با کمک دستور artisan این امکان برای ما وجود دارد که بررسی کنیم چه مقدار از کد ما تحت پوشش تست ها است. ابتدا باید یک پسوند PHP به نام Xdebug را با پیروی از این دستورالعمل ها نصب کنیم.
هنگامی که Xdebug نصب شد، باید یک متغیر محیطی جدید در فایل .env اضافه کنیم تا حالت coverage آن را فعال کنیم:
env.
APP_NAME=Laravel APP_ENV=local APP_KEY=base64:S07501pjvBOoBrOPMFMUg5J4Gyurq9FKDLWuIkwt5tk= APP_DEBUG=true APP_URL=http://app.test . . . SCOUT_DRIVER=meilisearch MEILISEARCH_HOST=http://meilisearch:7700 SAIL_XDEBUG_MODE=develop,debug,coverage
پس از ذخیره فایل env.، باید Laravel Sail را با فشار دادن Ctrl-C در ترمینال متوقف کرده و دوباره راه اندازی کنید و vendor/bin/sail up/.
را یک بار دیگر اجرا کنید. اکنون، میتوانیم دستور artisan test را با پرچم --coverage اجرا کنیم تا ببینیم چه مقدار از کد ما تحت پوشش تستها است:
./vendor/bin/sail php artisan test --coverage
خروجی:
PASS Tests\Unit\ExampleTest ✓ that true is true ✓ that name is jack PASS Tests\Unit\RoomTest ✓ room has ✓ room add Tests: 4 passed Time: 1.17s Console/Kernel ........................................... 100.0 % Exceptions/Handler ....................................... 100.0 % Http/Controllers/Controller .............................. 100.0 % Http/Kernel .............................................. 100.0 % Http/Middleware/Authenticate ................................ 0.0 % Http/Middleware/EncryptCookies ........................... 100.0 % Http/Middleware/PreventRequestsDuringMaintenance ......... 100.0 % Http/Middleware/RedirectIfAuthenticated ..................... 0.0 % Http/Middleware/TrimStrings .............................. 100.0 % Http/Middleware/TrustHosts .................................. 0.0 % Http/Middleware/TrustProxies ............................. 100.0 % Http/Middleware/VerifyCsrfToken .......................... 100.0 % Models/User .............................................. 100.0 % Providers/AppServiceProvider ............................. 100.0 % Providers/AuthServiceProvider ............................ 100.0 % Providers/BroadcastServiceProvider .......................... 0.0 % Providers/EventServiceProvider ........................... 100.0 % Providers/RouteServiceProvider 32..37, 49 .................. 33.3 % Room ..................................................... 100.0 % Total Coverage ............................................. 48.4 %
همانطور که می بینید، درصد پوشش کد برای هر فایل در زیر نتایج آزمون ذکر شده است و ما 48.4٪ پوشش تست کلی برای برنامه خود داریم.
تست نهایی ما برای کلاس Room شامل تست متد ()remove
با ()assertCount
است. این متد مشابه ()assertContains
عمل می کند، با این تفاوت که اولین پارامتر باید تعداد آیتم های آرایه باشد.
تابع زیر را در زیر متد ()test_room_add
اضافه کنید:
tests/Unit/RoomTest.php/.
/** * Test the add() method in Room class * * @return void */ public function test_room_remove() { $room = new Room(["Jack", "Peter"]); // Create a new room $this->assertCount(1, $room->remove("Peter")); }
تابع بالا یک اتاق جدید با دو عضو ایجاد می کند و از متد ()assertCount
برای آزمایش اینکه آیا حذف یک عضو از اتاق تعداد اعضای آن را به 1 کاهش می دهد یا خیر استفاده می کند.
برای مشاهده نتایج دوباره تست را تکرار کنید:
./vendor/bin/sail php artisan test ./tests/Unit/RoomTest.php
خروجی:
PASS Tests\Unit\RoomTest ✓ room has ✓ room add ✓ room remove Tests: 3 passed Time: 1.47s
در یک برنامه وب واقعی، احتمالا مانند مرحله قبل نیازی به آزمایش آرایه های ساده نخواهیم داشت. در عوض، احتمالا با API ها، درخواست های HTTP و پاسخ ها سر و کار خواهیم داشت. لاراول همچنین نمونه ای از این نوع تست را ارائه می دهد.
ادامه دهید و فایل ./tests/Feature/ExampleTest.php
را در ویرایشگر متن خود باز کنید:
code ./tests/Feature/ExampleTest.php
tests/Feature/ExampleTest.php/.
public function test_the_application_returns_a_successful_response() { $response = $this->get('/'); $response->assertStatus(200); }
در این مثال، یک درخواست GET به URL روت ارسال می شود و پاسخ برگشتی به متغیر response اختصاص داده می شود. در خط زیر، بررسی می کنیم که آیا پاسخ دارای کد موفقیت 200 با assertStatus است.
گاهی اوقات، یک endpoint از ما می خواهد که برخی از پارامترهای کوئری را ارسال کنیم. در چنین مواردی می توانیم این کار را انجام دهیم:
public function test_the_application_returns_a_successful_response() { $response = $this->post('/user', ['name' => 'Amy']); $response->assertStatus(200); }
فقط تست status پاسخ برای یک برنامه واقعی کافی نیست. معمولا باید body پاسخ را آزمایش کنیم تا مطمئن شویم داده هایی که به ما بازگردانده می شوند معتبر هستند و با انتظارات مطابقت دارند. در بیشتر موارد، داده های پاسخ یا در قالب JSON یا HTML هستند. در دو بخش بعدی، نحوه آزمایش هر دو را به تفصیل مورد بحث قرار خواهیم داد.
بکاند و فرانتاند اکثر برنامههای کاربردی وب مدرن معمولا جداگانه اجرا میشوند و دادهها در قالب JSON بین آنها به اشتراک گذاشته میشود. لاراول برای آزمایش اینکه دادههای JSON دریافت شده از endpoint معتبر و دقیق هستند، کمکهایی به ما ارائه میدهد. به عنوان مثال، متد ()assertJson
برای اطمینان از اعتبار داده های JSON ارائه شده است.
بیایید نگاهی به مثال زیر بیاندازیم. فایل routes/web.php
را باز کنید و یک مسیر جدید ایجاد کنید. وقتی درخواست GET برای آن ارسال شد، کاری میکنیم این مسیر دادههای JSON را برگرداند.
code routes/web.php
routes/web.php/.
. . . Route::get('/json-test', function () { return response()->json([ 'name' => 'Jone', 'updated' => true, ]); });
با استفاده از دستور زیر یک تست لاراول برای این مسیر ایجاد کنید:
./vendor/bin/sail php artisan make:test JSONTest
خروجی:
Test created successfully.
tests/Feature/JSONTest.php/.
جدید ایجاد شده را باز کنید و محتویات آن را به صورت زیر تغییر دهید:
tests/Feature/JSONTest.php/.
<?php namespace Tests\Feature; use Illuminate\Foundation\Testing\RefreshDatabase; use Illuminate\Foundation\Testing\WithFaker; use Tests\TestCase; class JSONTest extends TestCase { /** * A basic feature test example. * * @return void */ public function test_json() { $response = $this->get('/json-test'); $response->assertStatus(200); $response->assertJson([ 'updated' => true, ]); } }
در این تست، یک درخواست GET به مسیر json-test/
می فرستیم و پاسخ آن را به در متغیر $response
قرار میدهیم. در مرحله بعد، ما تست می کنیم که آیا status پاسخ 200 است، و آیا داده های پاسخ حاوی مقدار 'updated' => true
است یا نه.
اگر اکنون این تست را اجرا کنید، هر دو assert باید قبول شوند:
./vendor/bin/sail php artisan test ./tests/Feature/JSONTest.php
خروجی:
PASS Tests\Feature\JSONTest ✓ json Tests: 1 passed Time: 0.69s
تست بالا فقط بررسی میکند که پاسخ JSON دارای property موردنظر باشد. در مواردی که میخواهید دادههای JSON دقیقا مطابقت داشته باشند، میتوانید از ()assertExactJson
مانند این استفاده کنید:
. . . public function test_json() { $response = $this->get('/json-test'); $response->assertStatus(200); $response->assertExactJson([ 'updated' => true, ]); } . . .
پس از انجام تغییرات فوق، تست با شکست مواجه خواهد شد زیرا JSON ارائه شده دقیقا با پاسخ مطابقت ندارد.
./vendor/bin/sail php artisan test ./tests/Feature/JSONTest.php
خروجی:
FAIL Tests\Feature\JSONTest ⨯ json --- • Tests\Feature\JSONTest > json Failed asserting that two strings are equal. at tests/Feature/JSONTest.php:25 21▕ // $response->assertJson([ 22▕ // 'created' => true, 23▕ // ]); 24▕ $response->assertExactJson([ ➜ 25▕ 'created' => true, 26▕ ]); 27▕ } 28▕ } --- Expected +++ Actual @@ @@ '{\n - "created": true\n + "created": true,\n + "name": "Abigail"\n }' Tests: 1 failed Time: 0.76s
گاهی اوقات، ما انتظار داریم یک مسیر به جای JSON یک مقدار HTML را برگرداند. لاراول همچنین قادر است پاسخ های HTML را برای ما تست کند تا بتوانیم بررسی کنیم که مسیر به درستی کار می کند.
قبل از نوشتن یک تست جدید، بیایید ویو و روتر مورد آزمایش را ایجاد کنیم. به دایرکتوری ./resources/views
بروید و یک فایل جدید به نام test.blade.php
ایجاد کنید.
code ./resources/views/test.blade.php
resources/views/test.blade.php/.
<!DOCTYPE html> <html lang="en"> <head> <meta charset="UTF-8"> <meta http-equiv="X-UA-Compatible" content="IE=edge"> <meta name="viewport" content="width=device-width, initial-scale=1.0"> <title>Document</title> </head> <body> <p>The name is {{ $name }}.</p> </body> </html>
این ویو یک متغیر name را نیاز دارد و این متغیر را می توان از روتر تهیه کرد. به فایل ./routes/web.php
بروید و مسیر زیر را ایجاد کنید:
routes/web.php/.
. . . Route::get('/view-test', function () { return view('test', ['name' => 'Taylor']); });
در مرحله بعد، بیایید یک کلاس تست جدید برای ویویی که ایجاد کردیم بسازیم و آن را ViewTest.php
بنامیم:
./vendor/bin/sail php artisan make:test ViewTest
خروجی:
Test created successfully.
فایل tests/Feature/ViewTest.php/.
را باز کرده و تست زیر را ایجاد کنید:
code ./tests/Feature/ViewTest.php
tests/Feature/ViewTest.php/.
. . . class ViewTest extends TestCase { /** * Test if the returned HTML page contains the name Taylor. * * @return void */ public function test_if_view_contains_Taylor() { $response = $this->get('/view-test'); $response->assertStatus(200); $response->assertSee('Taylor'); $response->assertSee('<p>The name is Taylor.</p>', false); } }
در این مثال، ابتدا از URL /view-test بازدید می کنیم و پاسخ را به متغیر در $response
قرار می دهیم. سپس از ()assertStatus
استفاده می کنیم تا بفهمیم پاسخ دارای کد موفقیت 200 است.
در خط زیر، از متد assertSee برای آزمایش اینکه آیا صفحه HTML برگشتی حاوی کلمه Taylor است یا نه استفاده کردیم. این assert به طور خودکار از رشته داده شده escape می کند مگر اینکه همانطور که در assert سوم مشاهده می شود به عنوان پارامتر دوم false را ارسال کنید. اگر اجازه escape به رشته داده شود، با تگ <p> به عنوان <p> رفتار میشود و نه به عنوان یک تگ HTML.
تست را با دستور زیر اجرا کنید:
./vendor/bin/sail php artisan test ./tests/Feature/ViewTest.php
خروجی:
PASS Tests\Feature\ViewTest ✓ if view contains taylor Tests: 1 passed Time: 0.72s
assert های مشابه عبارتند از ()assertSeeInOrder()،assertSeeText
، و غیره. جزئیات این assert ها را می توانید در اینجا بیابید.
تا اینجا، نحوه نوشتن یونیت تست های پایه را مورد بحث قرار دادیم و سپس چند استراتژی برای تست پاسخهای HTTP در برنامههای کاربردی دنیای واقعی نشان دادیم. بیایید با آزمایش کدی که از پایگاه داده دریافت میکند یا داده ها را در آن قرار میدهد، آن را یک قدم جلوتر ببریم.
ما می دانیم که برای بیشتر برنامه ها، ما نیاز به ذخیره اطلاعات در یک پایگاه داده داریم، بنابراین بسیار مهم است مطمئن شویم که پایگاه داده همانطور که طراحی شده است کار می کند قبل از اینکه برنامه خود را برای مرحله تولید آماده کنیم. برای انجام این کار، اولین قدم تولید برخی داده های فیک در داخل پایگاه داده ما است.
Laravel Sail قبلا هنگامی که دستور ./vendor/bin/sail up
را اجرا کردیم، از تنظیمات پایگاه داده برای ما مراقبت کرده بود. به طور پیش فرض، لاراول یک مدل کاربر (./app/Models/User.php
) و فایل مهاجرت مربوطه (./database/migrations/2014_10_12_000000_create_users_table.php
) را در اختیار ما قرار می دهد. در این بخش، از این مدل کاربر برای نشان دادن نحوه آزمایش پایگاه داده در لاراول استفاده خواهیم کرد.
ادامه دهید و مهاجرت ها را به طور مستقیم اجرا کنید. به یاد داشته باشید که ما باید دستور را در لاراول Sail اجرا کنیم:
./vendor/bin/sail php artisan migrate
خروجی:
Migration table created successfully. Migrating: 2014_10_12_000000_create_users_table Migrated: 2014_10_12_000000_create_users_table (56.59ms) Migrating: 2014_10_12_100000_create_password_resets_table Migrated: 2014_10_12_100000_create_password_resets_table (30.96ms) Migrating: 2019_08_19_000000_create_failed_jobs_table Migrated: 2019_08_19_000000_create_failed_jobs_table (32.18ms) Migrating: 2019_12_14_000001_create_personal_access_tokens_table Migrated: 2019_12_14_000001_create_personal_access_tokens_table (63.55ms)
در حال حاضر، پایگاه داده هنوز خالی است، اما می توانیم از یک factory برای تولید برخی داده ها برای تست استفاده کنیم. در اینجا دستور artisan برای تولید یک factory جدید آمده است:
./vendor/bin/sail php artisan make:factory PostFactory
خروجی:
Factory created successfully.
با این حال، لاراول یک مثال برای مدل User در اختیار ما قرار می دهد. فایل ./database/factories/UserFactory.php
را در ویرایشگر متن خود باز کنید و محتویات آن را بررسی کنید:
code ./database/factories/UserFactory.php
database/factories/UserFactory.php/.
<?php namespace Database\Factories; use Illuminate\Database\Eloquent\Factories\Factory; use Illuminate\Support\Str; /** * @extends \Illuminate\Database\Eloquent\Factories\Factory<\App\Models\User> */ class UserFactory extends Factory { /** * Define the model's default state. * * @return array<string, mixed> */ public function definition() { return [ 'name' => $this->faker->name(), 'email' => $this->faker->unique()->safeEmail(), 'email_verified_at' => now(), 'password' => '$2y$10$92IXUNpkjO0rOQ5byMi.Ye4oKoEa3Ro9llC/.og/at2.uheWG/igi', // password 'remember_token' => Str::random(10), ]; } . . . }
فایل factory به لاراول می گوید که چه نوع داده ای باید فیلد مربوطه را پر کند. در این مثال، فیلدهای نام و ایمیل با استفاده از کتابخانه Faker PHP ایجاد میشوند، email_verified_at
با زمان فعلی مقداردهی میشود، رمز عبور یک رشته رمزگذاری شده است، و memory_token
بهطور تصادفی بهعنوان رشتهای حاوی 10 کاراکتر تولید میشود.
برای استفاده از این factory برای تولید دادههای آزمایشی، باید اطمینان حاصل کنیم که آن را به مدل کاربر متصل میکنیم. فایل ./app/Models/User.php
را باز کنید و مطمئن شوید که مدل کاربر از HasFactory استفاده می کند:
app/Models/User.php/.
<?php namespace App\Models; use Illuminate\Contracts\Auth\MustVerifyEmail; use Illuminate\Database\Eloquent\Factories\HasFactory; use Illuminate\Foundation\Auth\User as Authenticatable; use Illuminate\Notifications\Notifiable; use Laravel\Sanctum\HasApiTokens; class User extends Authenticatable { use HasApiTokens, HasFactory, Notifiable; . . . }
لاراول به طور خودکار وارد database/factories/.
می شود و به دنبال نام کلاسی می گردد که با نام مدل با پسوند فکتوری(<model_name>Factory.php
) مطابقت داشته باشد.
در مرحله بعد، زمان آن است که یک تست برای جدول پایگاه داده کاربر ایجاد کنیم. دستور زیر را در ترمینال خود اجرا کنید:
./vendor/bin/sail php artisan make:test DatabaseTest
خروجی:
Test created successfully.
tests/Feature/DatabaseTest.php/.
را در ویرایشگر متن خود باز کنید و یک تست جدید برای پایگاه داده کاربر ایجاد کنید:
code ./tests/Feature/DatabaseTest.php
tests/Feature/DatabaseTest.php/.
<?php namespace Tests\Feature; use Illuminate\Foundation\Testing\RefreshDatabase; use Illuminate\Foundation\Testing\WithFaker; use Tests\TestCase; use App\Models\User; class DatabaseTest extends TestCase { use RefreshDatabase; /** * Test the user database. * * @return void */ public function test_user_database() { User::factory()->count(3)->create(); $this->assertDatabaseCount('users', 3); } }
در این مثال، ما از RefreshDatabase استفاده کردیم تا مطمئن شویم پس از هر تست، پایگاه داده به حالت اولیه برمیگردد. اگر فکر می کنید ضرورتی ندارد، می توانید این خط را کامنت کنید. همچنین باید مدل User را در فایل تست وارد کنیم.
در داخل تابع ()test_user_database
، سه رکورد را از طریق factory کاربر که به تازگی ایجاد کردهایم، در جدول کاربران وارد کردیم. سپس از ()assertDatabaseCount
استفاده میکنیم تا بررسی کنیم که سه رکورد در جدول کاربران وجود دارد یا نه.
البته، بسیاری از assert های مفید در دسترس ما هستند. به عنوان مثال، ()assertDatabaseHas
وجود یک رکورد خاص در جدول را تست می کند و ()assertDatabaseMissing
عدم وجود یک رکورد را تضمین می کند.
$this->assertDatabaseHas('users', [ 'email' => 'sally@example.com', ]); $this->assertDatabaseMissing('users', [ 'email' => 'sally@example.com', ]);
در نهایت، ما میتوانیم تست پایگاه داده را با استفاده از دستور زیر اجرا کنیم و معلوم کنیم که آن را تایید میکند:
./vendor/bin/sail php artisan test tests/Feature/DatabaseTest.php
PASS Tests\Feature\DatabaseTest ✓ user database Tests: 1 passed Time: 1.44s
علاوه بر یونیت تست و فیچر تست، تست مرورگر نیز وجود دارد که می تواند در شرایط خاص مفید باشد. این به ما اجازه می دهد تا رفتار کاربر را در یک مرورگر شبیه سازی کنیم، مانند تایپ چیزی در یک فرم یا فشار دادن یک دکمه. برای این کار باید از پکیجی به نام Laravel Dusk استفاده کنیم.
ادامه دهید و پکیج Laravel Dusk را با استفاده از Composer دانلود کنید:
./vendor/bin/sail composer require --dev laravel/dusk
خروجی:
Using version ^6.24 for laravel/dusk ./composer.json has been updated Running composer update laravel/dusk . . . Discovered Package: laravel/dusk Discovered Package: laravel/sail Discovered Package: laravel/sanctum Discovered Package: laravel/tinker Discovered Package: nesbot/carbon Discovered Package: nunomaduro/collision Discovered Package: spatie/laravel-ignition . . . Publishing complete.
Laravel Dusk به گوگل کروم و ChromeDriver وابسته است، و اگر از ابتدا این آموزش را دنبال کردید و از Laravel Sail برای مقداردهی اولیه پروژه خود استفاده کردید، ChromeDriver باید از قبل نصب شده باشد که بتوانیم کانتینر Docker را ایجاد کنیم. میتوانید این موضوع را با باز کردن فایل docker-compose.yml در پروژه خود تایید کنید و بررسی کنید که آیا کد زیر وجود دارد یا خیر:
. . . selenium: image: 'selenium/standalone-chrome' volumes: - '/dev/shm:/dev/shm' networks: - sail . . .
اگر از Apple Silicon Mac استفاده می کنید، باید به جای آن از ایمیج seleniarm/standalone-chromium
استفاده کنید:
. . . selenium: image: 'seleniarm/standalone-chromium' volumes: - '/dev/shm:/dev/shm' networks: - sail . . .
این تنظیمات اطمینان حاصل می کند که هنگام اجرای دستور ./vendor/bin/sail up
، تمام اجزای لازم نصب شده اند. اگر قبلا این کار را نکرده اید، باید گوگل کروم را خودتان نصب کنید.
وقتی آماده ادامه کار شدید، تست مرورگر را از طریق دستور artisan زیر انجام دهید. قبل از انجام این کار مطمئن شوید که Laravel Sail در پس زمینه در حال اجرا است.
./vendor/bin/sail dusk
خروجی:
Time: 00:02.588, Memory: 22.00 MB OK (1 test, 1 assertion)
اگر از ابتدا این آموزش را دنبال نکردید و پروژه لاراول را بدون داکر اجرا می کنید، باید ChromeDriver را با دستور duck:install
نصب کنید.
php artisan dusk:install
خروجی:
Dusk scaffolding installed successfully. Downloading ChromeDriver binaries... ChromeDriver binaries successfully installed for version 101.0.4951.41.
برای اجرای تست مرورگر بدون Laravel Sail از دستور زیر استفاده کنید و خروجی مانند قبل خواهد بود:
php artisan dusk
شما باید یک پوشه Browser را در پوشه tests پیدا کنید، و در داخل Browser، یک فایل ExampleTest.php
برای ما ارائه شده است، بنابراین اجازه دهید نگاهی دقیقتر بیندازیم:
tests/Browser/ExampleTest.php/.
<?php namespace Tests\Browser; use Illuminate\Foundation\Testing\DatabaseMigrations; use Laravel\Dusk\Browser; use Tests\DuskTestCase; class ExampleTest extends DuskTestCase { public function testBasicExample() { $this->browse(function (Browser $browser) { $browser->visit('/')->assertSee('Laravel'); }); } }
این فایل بسیار شبیه به یونیت تست یا پراپرتی تست است، با این تفاوت که ما از متد ()browse برای شبیه سازی یک مرورگر واقعی استفاده می کنیم. URL روت بازدید می شود و سپس از متد ()assertSee
برای بررسی وجود کلمه Laravel در صفحه وب استفاده می شود.
ما همچنین می توانیم با Laravel Dusk کارهای چالش برانگیزتری انجام دهیم. در مثال زیر مسیر login/ بازدید شده و فرم موجود در صفحه پر شده است. لاراول فیلد ورودی را با ویژگی name که با ایمیل مقداردی شده است پیدا میکند و عبارت 'example@test.com'
را تایپ میکند و سپس همین کار را برای فیلد رمز عبور قبل از فشار دادن دکمه ورود برای ارسال فرم انجام میدهد.
$this->browse(function ($browser) use ($user) { $browser->visit('/login') ->type('email', 'example@test.com') ->type('password', 'thisisapassword') ->press('Login'); });
Laravel Dusk روش های دیگری را برای تعامل با صفحه وب شما در اختیار ما قرار می دهد، اما من نمی توانم همه آنها را در اینجا فهرست کنم. اگر علاقه مند به کاوش بیشتر هستید، اسناد آنها را مطالعه کنید.
در این قسمت از آموزش به مفهوم ماکینگ و نحوه انجام آن در اپلیکیشن لاراول می پردازیم. گاهی اوقات، زمانی که ما در حال آزمایش کد خود هستیم، میخواهیم از اجرای بخشی از کد جلوگیری کنیم تا تست را سریع نگه داریم و از عوارض جانبی جلوگیری کنیم. به عنوان مثال، زمانی که ما در حال آزمایش کدی هستیم که با یک API تعامل دارد، معمولا نمیخواهیم درخواستی از API واقعی برای جلوگیری از خراب شدن تست به دلیل شرایط شبکه و در دسترس بودن سرویس ارائه کنیم. ما می توانیم پاسخ API را با متد fake() در چنین مواردی ماکینگ کنیم.
بیایید با ایجاد یک تست جدید شروع کنیم:
./vendor/bin/sail php artisan make:test MockTest
خروجی:
Test created successfully.
سپس، تست ()test_mock_http
را به فایل MockTest.php
که به تازگی ایجاد کردیم اضافه کنید:
tests/Feature/MockTest.php/.
<?php namespace Tests\Feature; use Illuminate\Foundation\Testing\RefreshDatabase; use Illuminate\Foundation\Testing\WithFaker; use Tests\TestCase; use Illuminate\Support\Facades\Http; class MockTest extends TestCase { /** * A basic feature test example. * * @return void */ public function test_mock_http() { // This api is supposed to return a list of countries in JSON format, // we can mock it so that it only returns Italy. Http::fake([ 'https://restcountries.com/v3.1/all' => Http::response( [ 'name' => 'Italy', 'code' => 'IT' ], 200 ), ]); $response = Http::get('https://restcountries.com/v3.1/all'); $this->assertJsonStringEqualsJsonString( $response->body(), json_encode([ 'name' => 'Italy', 'code' => 'IT' ],) ); } }
توجه داشته باشید که ما از ویو Http لاراول برای ایجاد درخواست های HTTP در اینجا استفاده می کنیم و این مثال API قرار است لیستی از کشورها را در قالب JSON برگرداند. در تست بالا، ما آن را مجبور میکنیم تا دادههای JSON ویژه و یک کد پاسخ 200 را برگرداند، و از متد assertJsonStringEqualsJsonString()
برای بررسی صحت آن استفاده میکنیم.
این تست را با دستور زیر اجرا کنید:
./vendor/bin/sail php artisan test ./tests/Feature/MockTest.php
خروجی:
PASS Tests\Feature\MockTest ✓ mock http Tests: 1 passed Time: 0.86s
میتوانید از این تکنیک برای آزمایش هر قطعه کدی که با برخی API تعامل دارد استفاده کنید تا از درخواستهای شبکه در آزمایشهای واحد خود دوری کنید.
در بخش پایانی آموزش، نحوه راهاندازی خط لوله یکپارچهسازی پیوسته برای آزمایش کد خود با اقدامات GitHub را نشان خواهم داد. قبل از ادامه، مطمئن شوید که Git را روی سیستم خود نصب کرده اید.
ابتدا یک ترمینال جدید باز کنید و یک ریپازیتوری git را در ریشه پروژه خود از طریق دستور زیر مقداردهی کنید:
git init -b main
خروجی:
Initialized empty Git repository in /<path_to_your_project_root_directory>/.git/
تمام فایل های دایرکتوری را به ریپازیتوری لوکال git اختصاص دهید:
git add . && git commit -m "initial commit"
خروجی:
[main 1e1c3e1] initial commit 83 files changed, 11070 insertions(+) create mode 100644 .editorconfig create mode 100644 .env.example . . .
در مرحله بعد، ما باید این ریپازیتوری لوکال را در GitHub پوش (push) دهیم. یک ریپازیتوری جدید برای پروژه خود در GitHub ایجاد کنید:
URL ریموت را در این ریپازیتوری کپی کنید.
در مرحله بعد، ما باید این ریپازیتوری لوکال را در GitHub پوش کنیم. یک ریپازیتوری جدید برای پروژه خود در GitHub ایجاد کنید:
به ترمینال برگردید و تغییرات خود را به کنترل از راه دور GitHub که با استفاده از دستورات زیر ایجاد کردیم push کنید:
git remote add origin <remote_url>
git branch -M main
git push -u origin main
خروجی:
Enumerating objects: 108, done. Counting objects: 100% (108/108), done. Delta compression using up to 10 threads Compressing objects: 100% (89/89), done. Writing objects: 100% (107/107), 71.63 KiB | 6.51 MiB/s, done. Total 107 (delta 7), reused 0 (delta 0), pack-reused 0 remote: Resolving deltas: 100% (7/7), done. To https://github.com/ericnanhu/git-test.git eb84310..1e1c3e1 main -> main Branch 'main' set up to track remote branch 'main' from 'origin'.
در مرحله بعد، برای ایجاد یک پایپ لاین CI، باید یک دایرکتوری .github/workflows
را در دایرکتوری root پروژه خود ایجاد کنیم. GitHub Actions به طور خودکار فایل های ورک فلو را در این فهرست جستجو می کند.
mkdir -p .github/workflows
یک فایل test.yml در دایرکتوری .github/workflows
ایجاد کنید و آن را در ویرایشگر متن خود باز کنید:
code ./github/workflows/test.yml
کد زیر را در فایل قرار دهید:
github/workflows/test.yml./.
name: Laravel Test on: push: branches: [ main ] pull_request: branches: [ main ] jobs: laravel-tests: runs-on: ubuntu-latest steps: - uses: shivammathur/setup-php@15c43e89cdef867065b0213be354c2841860869e with: php-version: '8.1' - uses: actions/checkout@v3 - name: Copy .env run: php -r "file_exists('.env') || copy('.env.example', '.env');" - name: Composer Update run: composer update - name: Install Dependencies run: composer install -q --no-ansi --no-interaction --no-scripts --no-progress --prefer-dist - name: Generate key run: php artisan key:generate - name: Directory Permissions run: chmod -R 777 storage bootstrap/cache - name: Create Database run: | mkdir -p database touch database/database.sqlite - name: Execute tests (Unit and Feature tests) via PHPUnit env: DB_CONNECTION: sqlite DB_DATABASE: database/database.sqlite run: vendor/bin/phpunit
این فایل yml در اصل مجموعهای از دستورات را تعریف میکند که هر بار که درخواست pull را به main branch پوش کنیم یا درخواست میکنیم اجرا میشوند. هنگامی که چنین رویدادی شناسایی شد، دستورات لیست شده در بخش jobs.laravel-tests.steps
به ترتیب توسط GitHub اجرا می شوند. ویژگی runs-on مشخص میکند که محیط تست آخرین نسخه اوبونتو خواهد بود و تمام دستورات داخل مراحل دارای یک نام و یک ویژگی اجرا هستند که دستور اجرا را مشخص میکند.
مرحله اول PHP 8.1 را در محیط تست نصب می کند و مرحله دوم ریپازیتوری GitHub را بررسی می کند تا گردش کار بتواند به آن دسترسی داشته باشد. در مرحله بعد، اگر فایل .env.example
وجود نداشته باشد، در فایل .env
کپی می شود و composer به روز می شود و برای نصب وابستگی های لازم استفاده می شود. دستور php artisan key:generate
برای تنظیم مقدار APP_KEY
در فایل env شما استفاده می شود و تنظیمات دایرکتوری و پایگاه داده لازم در دو مرحله بعدی انجام می شود. در نهایت تست ها از طریق phpunit اجرا می شوند.
ادامه دهید و تغییراتی را در پروژه خود ایجاد کنید، سپس آنها را متعهد کنید و به شاخه اصلی فشار دهید، به طور خودکار این خط لوله را راه اندازی می کند. اگر مشکلی پیش نیاید، همه مشاغل باید موفق باشند. می توانید نتیجه را با مراجعه به تب Actions ریپازیتوری GitHub خود مشاهده کنید.
تست کد شما یک مرحله تغییر ناپذیر در چرخه عمر توسعه نرم افزار است. هنگامی که شما در حال ایجاد یک برنامه لاراول هستید، بهتر است برای هر کنترلر، تست های پراپرتی/یونیت، برای هر جدول پایگاه داده و تست مرورگر برای هر صفحه وب بنویسید. این فرآیند یکپارچگی کد شما را تضمین می کند.
در این آموزش، در مورد اصول اولیه یونیت تست در لاراول را بررسی کردیم. ما نحوه راهاندازی محیطهای آزمایشی مختلف را در فایل پیکربندی PHPUnit phpunit.xml
، نحوه ایجاد و اجرای آزمایشها با استفاده از دستورات ترمینال، و نحوه آزمایش کد از طریق assert ها را مطالعه کردیم.
همچنین درباره نحوه آزمایش response های HTTP که برای برنامههای کاربردی وب در دنیای واقعی عملیتر است، صحبت کردیم و در مورد تست پایگاه داده و نحوه تولید دادههای فیک برای تست صحبت کردیم. در نهایت، به طور خلاصه تست مرورگر را معرفی کردیم که کاربران واقعی را در یک مرورگر واقعی شبیهسازی میکند، و مفهوم ماکینگ، که به ما امکان میدهد عملکرد یک قطعه کد را نادیده بگیریم تا بتوانیم بخشهای دیگر برنامه خود را آزمایش کنیم، را بررسی کردیم.
لطفا توجه داشته باشید که این آموزش شامل همه چیز در مورد تست در لاراول نیست، فقط در را برای شروع و یادگیری بیشتر برای شما باز می کند. می توانید اطلاعات بیشتری در مورد تست را در اسناد رسمی لاراول بخوانید.
در این قسمت، به پرسشهای تخصصی شما دربارهی محتوای مقاله پاسخ داده نمیشود. سوالات خود را اینجا بپرسید.