برنامه نویسی

قبل و بعد از اجرای تست Pest به لاراول دسترسی داشته باشید

Summarize this content to 400 words in Persian Lang
TL;DR: چگونه با شبیه سازی به اکوسیستم لاراول دسترسی پیدا کنیم beforeAll و afterAll روش های آزمایش آفت

نمونه پروژه لاراول را می توان در این مورد یافت مخزن Github. بیشتر بدانید کپسول، X یا بلواسکی.

من به استعاره ای نیاز داشتم تا مقاله خود را نشان دهم و آزمایش آفت را مشخص کنم.

کلود: تست آفت مانند همبرگر است. هسته ی آزمایش، پتی آبدار است: این قسمت اصلی است، بخشی که تمام طعم را به ارمغان می آورد. در اطراف آن، شما باید beforeEach و afterEach عملکردها، که به عنوان تاپینگ و سس عمل می کنند و طعم و زمینه را به هر لقمه اضافه می کنند. در نهایت، beforeAll و afterAll توابع مانند buns هستند: آنها ساختار را ارائه می دهند و همه چیز را در کنار هم نگه می دارند. همه اینها با هم یک تجربه تست کاملا متعادل را ایجاد می کند. به طور خلاصه، نوشتن تست آفت مانند درست کردن یک همبرگر خوب است: همه مواد مهم هستند و این هماهنگی آنهاست که تفاوت را ایجاد می کند.

هوم باشه با تشکر، کلود.

با توجه به مستندات PestPHP، $this متغیر در دسترس نیست beforeAll و afterAll روش های یک آزمون این به این دلیل است که این هوک ها قبل از اجرای هر آزمایشی اجرا می شوند:

beforeAll()

Executes the provided closure once before any tests are run within the current file, allowing you to perform any necessary setup or initialization that applies to all tests.

beforeAll(function () {
// Prepare something once before any of this file’s tests run…
});

It’s important to note that unlike the beforeEach() hook, the $this variable is not available in the beforeAll() hook. This is because the hook runs before any tests are executed, so there is no instance of the test class or object to which the variable could refer.

وارد حالت تمام صفحه شوید

از حالت تمام صفحه خارج شوید

برای درک بهتر این موضوع، توجه به این نکته مهم است که Pest بر روی PHPUnit ساخته شده است. آفت beforeAll و afterAll توابع بر اساس PHPUnit هستند setUp و tearDown روش هایی که در هر آزمون یک بار اجرا می شوند. اما در مورد Pest، این هوک ها فقط یک بار در هر فایل فراخوانی می شوند. این جادوی آفت است. تنها مسئله این است که در طول این تماس ها هیچ چیز در دسترس نیست.

بنابراین دسترسی به آن غیرممکن است $this، برنامه لاراول یا خواص آن در a beforeAll() یا afterAll() قلاب حداقل نه بدون راه حل خاص. این مقاله به بررسی این راه حل می پردازد.

از یک پروژه استاندارد لاراول، PHPUnit را با Pest جایگزین کنید.

composer remove –dev phpunit/phpunit

composer require –dev pestphp/pest –with-all-dependencies

vendor/bin/pest –init

وارد حالت تمام صفحه شوید

از حالت تمام صفحه خارج شوید

تست ها را با vendor/bin/pest.

> vendor/bin/pest

PASS Tests\Unit\ExampleTest
✓ that true is true

PASS Tests\Feature\ExampleTest
✓ the application returns a successful response

Tests: 2 passed (2 assertions)
Duration: 0.21s

وارد حالت تمام صفحه شوید

از حالت تمام صفحه خارج شوید

اگر پروژه از Vite استفاده می کند، a Vite manifest not found ممکن است خطا رخ دهد برای حل این مشکل، Vite باید در قسمت غیرفعال شود setUp روش از TestCase.php فایل

tests/TestCase.php

namespace Tests;

use Illuminate\Foundation\Testing\TestCase as BaseTestCase;

abstract class TestCase extends BaseTestCase
{
protected function setUp() : void
{
parent::setUp();

$this->withoutVite();
}
}

وارد حالت تمام صفحه شوید

از حالت تمام صفحه خارج شوید

برای اهداف این مقاله، Feature دایرکتوری حذف می شود این Feature سپس مجموعه آزمایشی باید از آن حذف شود phpunit.xml فایل، همراه با خط مربوطه در Pest.php فایل در نهایت، Unit/ExampleTest.php فایل با Unit/BootloadableTest.php.

phpunit.xml

xml version=”1.0″ encoding=”UTF-8″?>
xmlns:xsi=”http://www.w3.org/2001/XMLSchema-instance”
xsi:noNamespaceSchemaLocation=”vendor/phpunit/phpunit/phpunit.xsd”
bootstrap=”vendor/autoload.php”
colors=”true”
>

name=”Unit”>
tests/Unit

app

وارد حالت تمام صفحه شوید

از حالت تمام صفحه خارج شوید

Pest.php

use Tests\TestCase;

pest()->extend( TestCase::class )->in( ‘Unit’ );

وارد حالت تمام صفحه شوید

از حالت تمام صفحه خارج شوید

Unit/BootloadableTest.php

it( “can say ‘Hello World !'”, function()
{
$message = ‘Hello World !’;

echo “$message\n”;

expect( $message )->toBeString()->toEqual( ‘Hello World !’ );
} );

وارد حالت تمام صفحه شوید

از حالت تمام صفحه خارج شوید

> vendor/bin/pest

Hello World !

PASS Tests\Unit\BootloadableTest
✓ it can say ‘Hello World !’

Tests: 1 passed (2 assertions)
Duration: 0.07s

وارد حالت تمام صفحه شوید

از حالت تمام صفحه خارج شوید

هدف این مقاله به اشتراک گذاشتن داده های یکسان در بین تست ها است که از یک اجرای یک فرمان تولید می شود php artisan migrate –seed.

برای انجام این کار، لازم است کمی تغییر دهید DatabaseSeeder.php فایل برای ایجاد دو User در هر seed.

database/seeders/DatabaseSeeder.php

namespace Database\Seeders;

use Illuminate\Database\Seeder;
use App\Models\User;

class DatabaseSeeder extends Seeder
{
public function run() : void
{
User::factory( 2 )->create();
}
}

وارد حالت تمام صفحه شوید

از حالت تمام صفحه خارج شوید

در شروع یک آزمون

tests/Unit/BootloadableTest.php

use App\Models\User;

it( “can dump users”, function()
{
$this->artisan( ‘migrate:fresh –seed’ );

dd( User::select( ‘name’, ’email’ )->get()->toArray() );
} );

وارد حالت تمام صفحه شوید

از حالت تمام صفحه خارج شوید

> vendor/bin/pest

array:2 [
0 => array:2 [
“name” => “Evie Cronin”
“email” => “ebert.paolo@example.org”
] 1 => array:2 [
“name” => “Heidi Dietrich”
“email” => “orempel@example.net”
] ] // tests/Unit/BootloadableTest.php:10

وارد حالت تمام صفحه شوید

از حالت تمام صفحه خارج شوید

در شروع دو تست یکسان، با اضافه کردن دستور Artisan در beforeEach() قلاب

tests/Unit/BootloadableTest.php

use App\Models\User;

beforeEach( function()
{
$this->artisan( ‘migrate:fresh –seed’ );
} );

it( “can dump users for the first time”, function()
{
dump( User::select( ‘name’, ’email’ )->get()->toArray() );
} );

it( “can dump users for the second time”, function()
{
dd( User::select( ‘name’, ’email’ )->get()->toArray() );
} );

وارد حالت تمام صفحه شوید

از حالت تمام صفحه خارج شوید

> vendor/bin/pest

array:2 [
0 => array:2 [
“name” => “Mr. Elmer Jerde I”
“email” => “hector.okuneva@example.net”
] 1 => array:2 [
“name” => “Prof. Rupert Toy”
“email” => “bosco.ferne@example.com”
] ] // tests/Unit/BootloadableTest.php:14
array:2 [
0 => array:2 [
“name” => “Nona Howe MD”
“email” => “zulauf.jarred@example.com”
] 1 => array:2 [
“name” => “Abbie O’Conner”
“email” => “lhowell@example.net”
] ] // tests/Unit/BootloadableTest.php:19

وارد حالت تمام صفحه شوید

از حالت تمام صفحه خارج شوید

نتیجه قابل پیش بینی است، همانطور که beforeEachهمانطور که از نامش پیداست، قبل از هر تست اجرا می شود. در این مورد، چرا از آن استفاده نکنید beforeAll به جای قلاب؟

tests/Unit/BootloadableTest.php روی خط 6

beforeAll( function()
{
$this->artisan( ‘migrate:fresh –seed’ );
} );

وارد حالت تمام صفحه شوید

از حالت تمام صفحه خارج شوید

> vendor/bin/pest

FAIL Tests\Unit\BootloadableTest
─────────────────────────────────────────
FAILED Tests\Unit\BootloadableTest >

Using $this when not in object context

وارد حالت تمام صفحه شوید

از حالت تمام صفحه خارج شوید

این $this متغیر در دسترس نیست beforeAll عملکرد، و نه نماها. به عنوان مثال، Artisan::call(‘migrate:fresh –seed’) در این زمینه نیز کار نمی کند. جایگزین: استفاده کنید Bootloadable صفت

خصیصه غلبه می کند setUp و tearDown توابع برای اضافه کردن دو روش جدید: initialize و finalize. این initialize روش یک بار اجرا می شود، مانند beforeAll، در حالی که finalize روش نیز یک بار اجرا می شود، مانند afterAll.

tests\Traits\Bootloadable.php

namespace Tests\Traits;

use Illuminate\Support\Arr;
use Illuminate\Support\Collection;
use Illuminate\Support\Str;
use Pest\TestSuite;

trait Bootloadable
{
private static int $count = 0;
private static Collection $tests;

protected function setUp() : void
{
parent::setUp();

if( ! self::$count )
{
$this->init();

if( method_exists( self::class, ‘initialize’ ) ) $this->initialize();
}

self::$count++;
}

protected function tearDown() : void
{
if( count( self::$tests ) == self::$count )
{
if( method_exists( self::class, ‘finalize’ ) ) $this->finalize();
}

parent::tearDown();
}

private function init() : void
{
$repository = TestSuite::getInstance()->tests;

$data = [];

foreach( $repository->getFilenames() as $file )
{
$factory = $repository->get( $file );

$filename = Str::of( $file )->basename()->explode( ‘.’ )->first();

if( $factory->class === self::class ) $data = [ …$data, …[ $filename => $factory->methods ] ];
}

$cases = Collection::make( Arr::dot( $data ) );

$only = $cases->filter( fn( $case ) => Collection::make( $case->groups )->contains( ‘__pest_only’ ) );

self::$tests = ( $only->isEmpty() ? $cases : $only )->keys()->map( fn( $key ) => Str::of( $key )->kebab );
}
}

وارد حالت تمام صفحه شوید

از حالت تمام صفحه خارج شوید

این setUp و tearDown روش ها محدود به تماس هستند initialize و finalize. منطق اصلی در آن قرار دارد init روش

این روش تعداد تست ها را فهرست می کند. این initialize روش زمانی فراخوانی می شود که شمارش اولیه 0 باشد، در حالی که متغیر استاتیک را افزایش می دهد $count. این finalize متد زمانی فراخوانی می شود که این تعداد به طول عدد برسد $tests آرایه، مقداردهی اولیه شده توسط init تابع

برای استفاده از initialize روش، باید صفت را به آن اضافه کنید TestCase و روش مربوطه را اجرا کنید.

tests/TestCase.php

namespace Tests;

use Illuminate\Foundation\Testing\TestCase as BaseTestCase;
use Tests\Traits\Bootloadable;

abstract class TestCase extends BaseTestCase
{
use Bootloadable;

protected function initialize() : void
{
$this->artisan( ‘migrate:fresh –seed’ );
}
}

وارد حالت تمام صفحه شوید

از حالت تمام صفحه خارج شوید

این initialize بنابراین روش می تواند جایگزین شود setUp روش در مورد $this->withoutVite، این پیکربندی را می توان مستقیماً به آن اضافه کرد initialize.

فراموش نکنید که قبلی را حذف کنید beforeAll() از BootloadableTest فایل سپس، دویدن vendor/bin/pest دو بار نتیجه زیر را به همراه خواهد داشت: دو مهاجرت و دانه های مختلف برای یک مجموعه آزمایشی.

> vendor/bin/pest

array:2 [
0 => array:2 [
“name” => “Prof. Moises Skiles IV”
“email” => “frederique.nitzsche@example.com”
] 1 => array:2 [
“name” => “Prof. Cleve Oberbrunner”
“email” => “madelynn.hane@example.net”
] ] // tests/Unit/BootloadableTest.php:8
array:2 [
0 => array:2 [
“name” => “Prof. Moises Skiles IV”
“email” => “frederique.nitzsche@example.com”
] 1 => array:2 [
“name” => “Prof. Cleve Oberbrunner”
“email” => “madelynn.hane@example.net”
] ] // tests/Unit/BootloadableTest.php:13

> vendor/bin/pest

array:2 [
0 => array:2 [
“name” => “Doug Marvin”
“email” => “nash.schoen@example.com”
] 1 => array:2 [
“name” => “Joaquin Jacobi”
“email” => “neoma38@example.net”
] ] // tests/Unit/BootloadableTest.php:8
array:2 [
0 => array:2 [
“name” => “Doug Marvin”
“email” => “nash.schoen@example.com”
] 1 => array:2 [
“name” => “Joaquin Jacobi”
“email” => “neoma38@example.net”
] ] // tests/Unit/BootloadableTest.php:13

وارد حالت تمام صفحه شوید

از حالت تمام صفحه خارج شوید

اکنون زمان دستکاری داده ها در آزمون های مختلف است.

بیایید تصور کنیم که تست اول نام کاربر اول را تغییر می‌دهد و تست دوم نام به‌روزرسانی‌شده از تست اول را تأیید می‌کند.

Tests/Unit/BootloadableTest.php

use App\Models\User;

beforeEach( function()
{
$this->user = User::first();

$this->new = “Capsules Codes”;
} );

it( “can modify first user name between two tests”, function()
{
$name = $this->user->name;

echo $this->user->name;

expect( $this->user->name )->toBe( $name );

$this->user->name = $this->new;

$this->user->save();
} );

it( “can verify first user name between two tests”, function()
{
echo ” > {$this->user->name} \n”;

expect( $this->user->name )->toBe( $this->new );
} );

وارد حالت تمام صفحه شوید

از حالت تمام صفحه خارج شوید

نتیجه پس از دو اعدام متوالی از vendor/bin/pest فرمان

> vendor/bin/pest

Esteban Raynor > Capsules Codes

PASS Tests\Unit\BootloadableTest
✓ it can modify first user name between two tests
✓ it can verify first user name between two tests

Tests: 2 passed (2 assertions)
Duration: 0.40s

> vendor/bin/pest

Demario Corkery > Capsules Codes

PASS Tests\Unit\BootloadableTest
✓ it can modify first user name between two tests
✓ it can verify first user name between two tests

Tests: 2 passed (2 assertions)
Duration: 0.39s

وارد حالت تمام صفحه شوید

از حالت تمام صفحه خارج شوید

اگر هر تست در فایل خودش قرار گیرد چه اتفاقی می افتد؟

Unit/FirstTest.php

use App\Models\User;

beforeEach( function()
{
$this->user = User::first();

$this->new = “Capsules Codes”;
} );

it( “can modify first user name between two tests”, function()
{
$name = $this->user->name;

echo “{$this->user->name} > $this->new \n”;

expect( $this->user->name )->toBe( $name );

$this->user->name = $this->new;

$this->user->save();
} );

وارد حالت تمام صفحه شوید

از حالت تمام صفحه خارج شوید

Unit/SecondTest.php

use App\Models\User;

beforeEach( function()
{
$this->user = User::first();

$this->new = “Capsules Codes”;
} );

it( “can verify first user name between two tests”, function()
{
expect( $this->user->name )->toBe( $this->new );
} );

وارد حالت تمام صفحه شوید

از حالت تمام صفحه خارج شوید

vendor/bin/pest

Mrs. Laurine Ebert V > Capsules Codes

PASS Tests\Unit\FirstTest
✓ it can modify first user name between two test files 0.08s

PASS Tests\Unit\SecondTest
✓ it can verify first user name between two test files 0.01s

Tests: 2 passed (2 assertions)
Duration: 0.13s

وارد حالت تمام صفحه شوید

از حالت تمام صفحه خارج شوید

بر خلاف Pest که اجرا می کند beforeAll و afterAll روش‌هایی در ابتدا و انتهای آزمایش‌های یک فایل، در اینجا بر روی یکTestCase اساس به جای فایل. برای اجرای initialize و finalize روش‌هایی که در ابتدا و انتهای آزمون‌ها برای یک فایل داده شده، کمی تغییر در Trait ضروری است.

tests/Traits/Bootloadable.php

namespace Tests\Traits;

use Illuminate\Support\Arr;
use Illuminate\Support\Collection;
use Illuminate\Support\Str;
use Pest\TestSuite;

trait Bootloadable
{
private static int $count = 0;
private static array $tests;
private static string $current;

protected function setUp() : void
{
parent::setUp();

self::$current = array_reverse( explode( ‘\\’, debug_backtrace( DEBUG_BACKTRACE_IGNORE_ARGS, 2 )[ 1 ][ ‘class’ ] ) )[ 0 ];

if( ! isset( self::$tests ) )
{
$this->init();
}

if( ! self::$count )
{
if( method_exists( self::class, ‘initialize’ ) ) $this->initialize( self::$current );
}

self::$count++;
}

protected function tearDown() : void
{
if( self::$tests[ self::$current ] == self::$count )
{
if( method_exists( self::class, ‘finalize’ ) ) $this->finalize( self::$current );

self::$count = 0;
}

parent::tearDown();
}

private function init() : void
{
$repository = TestSuite::getInstance()->tests;

$data = [];

foreach( $repository->getFilenames() as $file )
{
$factory = $repository->get( $file );

$filename = Str::of( $file )->basename()->explode( ‘.’ )->first();

if( $factory->class === self::class ) $data = [ …$data, …[ $filename => count( $factory->methods ) ] ];
}

self::$tests = $data;
}
}

وارد حالت تمام صفحه شوید

از حالت تمام صفحه خارج شوید

نام آزمون در حال اجرا با استفاده از بازیابی می شود debug_backtrace تابع
متغیر self::$tests اکنون یک آرایه انجمنی است، که در آن هر کلید نشان دهنده نام فایل است، و هر مقدار مربوط به تعداد تست هایی است که شامل آن است.
متغیر self::$count یک بار به صفر بازنشانی می شود finalize روش اجرا شده است.

مرحله آخر: اصلاح کنید TestCase فایل این امکان دسترسی به نام فایل موجود در آن را فراهم می کند.

tests/TestCase.php

namespace Tests;

use Illuminate\Foundation\Testing\TestCase as BaseTestCase;
use Tests\Traits\Bootloadable;
use Illuminate\Support\Facades\App;
use Illuminate\Support\Facades\File;

abstract class TestCase extends BaseTestCase
{
use Bootloadable;

protected function initialize( $filename ) : void
{
if( $filename == “FirstTest” )
{
$this->artisan( ‘migrate:fresh –seed’ );
}

if( $filename == “SecondTest” )
{
$this->artisan( ‘migrate:fresh –seed’ );
}
}

protected function finalize( $filename ) : void
{
if( $filename == “FirstTest” )
{
$this->artisan( ‘migrate:reset’ );
}

if( $filename == “SecondTest” )
{
$this->artisan( ‘migrate:reset’ );
}
}
}

وارد حالت تمام صفحه شوید

از حالت تمام صفحه خارج شوید

تست ها را می توان دوباره اجرا کرد.

> vendor/bin/test

Brice Fisher > Capsules Codes

PASS Tests\Unit\FirstTest
✓ it can modify first user name between two tests 0.36s

FAIL Tests\Unit\SecondTest
⨯ it can verify first user name between two tests 0.03s
─────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────
FAILED Tests\Unit\SecondTest > it can verify first user name between two tests
Failed asserting that two strings are identical.
-‘Capsules Codes’
+’Tremayne Spinka’

at tests/Unit/SecondTest.php:18
14▕
15▕
16▕ it( “can verify first user name between two tests”, function()
17▕ {
➜ 18▕ expect( $this->user->name )->toBe( $this->new );
19▕ } );
20▕

1 tests/Unit/SecondTest.php:18

Tests: 1 failed, 1 passed (2 assertions)
Duration: 0.43s

وارد حالت تمام صفحه شوید

از حالت تمام صفحه خارج شوید

به نظر می رسد که Brice Fisher تبدیل شده است Tremayne Spinka!

در اینجا یک نمای کلی از نتیجه به دست آمده با تکرار است BootloadableTest را به دو فایل مجزا تبدیل کنید. [ Make sure to replace “Capsules Codes” with “Pest PHP” in the second file ].

> vendor/bin/pest

Ernestine Dietrich III > Capsules Codes

PASS Tests\Unit\FirstTest
✓ it can modify first user name between two test files
✓ it can verify first user name between two test files

Dr. Nya Gusikowski > Pest PHP

PASS Tests\Unit\SecondTest
✓ it can modify first user name between two test files
✓ it can verify first user name between two test files

Tests: 4 passed (4 assertions)
Duration: 0.42s

وارد حالت تمام صفحه شوید

از حالت تمام صفحه خارج شوید

خوشحالم که این کمک کرد.

TL;DR: چگونه با شبیه سازی به اکوسیستم لاراول دسترسی پیدا کنیم beforeAll و afterAll روش های آزمایش آفت

نمونه پروژه لاراول را می توان در این مورد یافت مخزن Github. بیشتر بدانید کپسول، X یا بلواسکی.

من به استعاره ای نیاز داشتم تا مقاله خود را نشان دهم و آزمایش آفت را مشخص کنم.

کلود: تست آفت مانند همبرگر است. هسته ی آزمایش، پتی آبدار است: این قسمت اصلی است، بخشی که تمام طعم را به ارمغان می آورد. در اطراف آن، شما باید beforeEach و afterEach عملکردها، که به عنوان تاپینگ و سس عمل می کنند و طعم و زمینه را به هر لقمه اضافه می کنند. در نهایت، beforeAll و afterAll توابع مانند buns هستند: آنها ساختار را ارائه می دهند و همه چیز را در کنار هم نگه می دارند. همه اینها با هم یک تجربه تست کاملا متعادل را ایجاد می کند. به طور خلاصه، نوشتن تست آفت مانند درست کردن یک همبرگر خوب است: همه مواد مهم هستند و این هماهنگی آنهاست که تفاوت را ایجاد می کند.

هوم باشه با تشکر، کلود.

با توجه به مستندات PestPHP، $this متغیر در دسترس نیست beforeAll و afterAll روش های یک آزمون این به این دلیل است که این هوک ها قبل از اجرای هر آزمایشی اجرا می شوند:

beforeAll()

Executes the provided closure once before any tests are run within the current file, allowing you to perform any necessary setup or initialization that applies to all tests.

beforeAll(function () {
    // Prepare something once before any of this file's tests run...
});


It's important to note that unlike the beforeEach() hook, the $this variable is not available in the beforeAll() hook. This is because the hook runs before any tests are executed, so there is no instance of the test class or object to which the variable could refer.
وارد حالت تمام صفحه شوید

از حالت تمام صفحه خارج شوید

برای درک بهتر این موضوع، توجه به این نکته مهم است که Pest بر روی PHPUnit ساخته شده است. آفت beforeAll و afterAll توابع بر اساس PHPUnit هستند setUp و tearDown روش هایی که در هر آزمون یک بار اجرا می شوند. اما در مورد Pest، این هوک ها فقط یک بار در هر فایل فراخوانی می شوند. این جادوی آفت است. تنها مسئله این است که در طول این تماس ها هیچ چیز در دسترس نیست.

بنابراین دسترسی به آن غیرممکن است $this، برنامه لاراول یا خواص آن در a beforeAll() یا afterAll() قلاب حداقل نه بدون راه حل خاص. این مقاله به بررسی این راه حل می پردازد.

از یک پروژه استاندارد لاراول، PHPUnit را با Pest جایگزین کنید.

composer remove --dev phpunit/phpunit

composer require --dev pestphp/pest --with-all-dependencies

vendor/bin/pest --init
وارد حالت تمام صفحه شوید

از حالت تمام صفحه خارج شوید

تست ها را با vendor/bin/pest.

> vendor/bin/pest

PASS  Tests\Unit\ExampleTest
✓ that true is true

PASS  Tests\Feature\ExampleTest
✓ the application returns a successful response


Tests:    2 passed (2 assertions)
Duration: 0.21s
وارد حالت تمام صفحه شوید

از حالت تمام صفحه خارج شوید

اگر پروژه از Vite استفاده می کند، a Vite manifest not found ممکن است خطا رخ دهد برای حل این مشکل، Vite باید در قسمت غیرفعال شود setUp روش از TestCase.php فایل

tests/TestCase.php



namespace Tests;

use Illuminate\Foundation\Testing\TestCase as BaseTestCase;

abstract class TestCase extends BaseTestCase
{
    protected function setUp() : void
    {
        parent::setUp();

        $this->withoutVite();
    }
}
وارد حالت تمام صفحه شوید

از حالت تمام صفحه خارج شوید

برای اهداف این مقاله، Feature دایرکتوری حذف می شود این Feature سپس مجموعه آزمایشی باید از آن حذف شود phpunit.xml فایل، همراه با خط مربوطه در Pest.php فایل در نهایت، Unit/ExampleTest.php فایل با Unit/BootloadableTest.php.

phpunit.xml

xml version="1.0" encoding="UTF-8"?>
 xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
         xsi:noNamespaceSchemaLocation="vendor/phpunit/phpunit/phpunit.xsd"
         bootstrap="vendor/autoload.php"
         colors="true"
>
    
         name="Unit">
            tests/Unit
        
    
    
        
            app
        
    

وارد حالت تمام صفحه شوید

از حالت تمام صفحه خارج شوید

Pest.php




use Tests\TestCase;

pest()->extend( TestCase::class )->in( 'Unit' );

وارد حالت تمام صفحه شوید

از حالت تمام صفحه خارج شوید

Unit/BootloadableTest.php



it( "can say 'Hello World !'", function()
{
    $message = 'Hello World !';

    echo "$message\n";

    expect( $message )->toBeString()->toEqual( 'Hello World !' );
} );
وارد حالت تمام صفحه شوید

از حالت تمام صفحه خارج شوید

> vendor/bin/pest

Hello World !

PASS  Tests\Unit\BootloadableTest
✓ it can say 'Hello World !'                                                                                                                                                                                            

Tests:    1 passed (2 assertions)
Duration: 0.07s
وارد حالت تمام صفحه شوید

از حالت تمام صفحه خارج شوید

هدف این مقاله به اشتراک گذاشتن داده های یکسان در بین تست ها است که از یک اجرای یک فرمان تولید می شود php artisan migrate --seed.

برای انجام این کار، لازم است کمی تغییر دهید DatabaseSeeder.php فایل برای ایجاد دو User در هر seed.

database/seeders/DatabaseSeeder.php



namespace Database\Seeders;

use Illuminate\Database\Seeder;
use App\Models\User;

class DatabaseSeeder extends Seeder
{
    public function run() : void
    {
        User::factory( 2 )->create();
    }
}
وارد حالت تمام صفحه شوید

از حالت تمام صفحه خارج شوید

در شروع یک آزمون

tests/Unit/BootloadableTest.php



use App\Models\User;

it( "can dump users", function()
{
    $this->artisan( 'migrate:fresh --seed' );

    dd( User::select( 'name', 'email' )->get()->toArray() );
} );
وارد حالت تمام صفحه شوید

از حالت تمام صفحه خارج شوید

> vendor/bin/pest

array:2 [
  0 => array:2 [
    "name" => "Evie Cronin"
    "email" => "ebert.paolo@example.org"
  ]
  1 => array:2 [
    "name" => "Heidi Dietrich"
    "email" => "orempel@example.net"
  ]
] // tests/Unit/BootloadableTest.php:10
وارد حالت تمام صفحه شوید

از حالت تمام صفحه خارج شوید

در شروع دو تست یکسان، با اضافه کردن دستور Artisan در beforeEach() قلاب

tests/Unit/BootloadableTest.php



use App\Models\User;

beforeEach( function()
{
    $this->artisan( 'migrate:fresh --seed' );
} );

it( "can dump users for the first time", function()
{
    dump( User::select( 'name', 'email' )->get()->toArray() );
} );

it( "can dump users for the second time", function()
{
    dd( User::select( 'name', 'email' )->get()->toArray() );
} );
وارد حالت تمام صفحه شوید

از حالت تمام صفحه خارج شوید

> vendor/bin/pest

array:2 [
  0 => array:2 [
    "name" => "Mr. Elmer Jerde I"
    "email" => "hector.okuneva@example.net"
  ]
  1 => array:2 [
    "name" => "Prof. Rupert Toy"
    "email" => "bosco.ferne@example.com"
  ]
] // tests/Unit/BootloadableTest.php:14
array:2 [
  0 => array:2 [
    "name" => "Nona Howe MD"
    "email" => "zulauf.jarred@example.com"
  ]
  1 => array:2 [
    "name" => "Abbie O'Conner"
    "email" => "lhowell@example.net"
  ]
] // tests/Unit/BootloadableTest.php:19
وارد حالت تمام صفحه شوید

از حالت تمام صفحه خارج شوید

نتیجه قابل پیش بینی است، همانطور که beforeEachهمانطور که از نامش پیداست، قبل از هر تست اجرا می شود. در این مورد، چرا از آن استفاده نکنید beforeAll به جای قلاب؟

tests/Unit/BootloadableTest.php روی خط 6

...

beforeAll( function()
{
    $this->artisan( 'migrate:fresh --seed' );    
} );

...
وارد حالت تمام صفحه شوید

از حالت تمام صفحه خارج شوید

> vendor/bin/pest

FAIL  Tests\Unit\BootloadableTest
─────────────────────────────────────────
FAILED  Tests\Unit\BootloadableTest > 

Using $this when not in object context
وارد حالت تمام صفحه شوید

از حالت تمام صفحه خارج شوید

این $this متغیر در دسترس نیست beforeAll عملکرد، و نه نماها. به عنوان مثال، Artisan::call('migrate:fresh --seed') در این زمینه نیز کار نمی کند. جایگزین: استفاده کنید Bootloadable صفت

خصیصه غلبه می کند setUp و tearDown توابع برای اضافه کردن دو روش جدید: initialize و finalize. این initialize روش یک بار اجرا می شود، مانند beforeAll، در حالی که finalize روش نیز یک بار اجرا می شود، مانند afterAll.

tests\Traits\Bootloadable.php



namespace Tests\Traits;

use Illuminate\Support\Arr;
use Illuminate\Support\Collection;
use Illuminate\Support\Str;
use Pest\TestSuite;

trait Bootloadable
{
    private static int $count = 0;
    private static Collection $tests;

    protected function setUp() : void
    {
        parent::setUp();

        if( ! self::$count )
        {
            $this->init();

            if( method_exists( self::class, 'initialize' ) ) $this->initialize();
        }

        self::$count++;
    }

    protected function tearDown() : void
    {
        if( count( self::$tests ) == self::$count )
        {
            if( method_exists( self::class, 'finalize' ) ) $this->finalize();
        }

        parent::tearDown();
    }

    private function init() : void
    {
        $repository = TestSuite::getInstance()->tests;

        $data = [];

        foreach( $repository->getFilenames() as $file )
        {
            $factory = $repository->get( $file );

            $filename = Str::of( $file )->basename()->explode( '.' )->first();

            if( $factory->class === self::class ) $data = [ ...$data, ...[ $filename => $factory->methods ] ];
        }

        $cases = Collection::make( Arr::dot( $data ) );

        $only = $cases->filter( fn( $case ) => Collection::make( $case->groups )->contains( '__pest_only' ) );

        self::$tests = ( $only->isEmpty() ? $cases : $only )->keys()->map( fn( $key ) => Str::of( $key )->kebab );
    }
}
وارد حالت تمام صفحه شوید

از حالت تمام صفحه خارج شوید

این setUp و tearDown روش ها محدود به تماس هستند initialize و finalize. منطق اصلی در آن قرار دارد init روش

این روش تعداد تست ها را فهرست می کند. این initialize روش زمانی فراخوانی می شود که شمارش اولیه 0 باشد، در حالی که متغیر استاتیک را افزایش می دهد $count. این finalize متد زمانی فراخوانی می شود که این تعداد به طول عدد برسد $tests آرایه، مقداردهی اولیه شده توسط init تابع

برای استفاده از initialize روش، باید صفت را به آن اضافه کنید TestCase و روش مربوطه را اجرا کنید.

tests/TestCase.php



namespace Tests;

use Illuminate\Foundation\Testing\TestCase as BaseTestCase;
use Tests\Traits\Bootloadable;

abstract class TestCase extends BaseTestCase
{
    use Bootloadable;

    protected function initialize() : void
    {
        $this->artisan( 'migrate:fresh --seed' );
    }
}

وارد حالت تمام صفحه شوید

از حالت تمام صفحه خارج شوید

  • این initialize بنابراین روش می تواند جایگزین شود setUp روش در مورد $this->withoutVite، این پیکربندی را می توان مستقیماً به آن اضافه کرد initialize.

فراموش نکنید که قبلی را حذف کنید beforeAll() از BootloadableTest فایل سپس، دویدن vendor/bin/pest دو بار نتیجه زیر را به همراه خواهد داشت: دو مهاجرت و دانه های مختلف برای یک مجموعه آزمایشی.

> vendor/bin/pest

array:2 [
  0 => array:2 [
    "name" => "Prof. Moises Skiles IV"
    "email" => "frederique.nitzsche@example.com"
  ]
  1 => array:2 [
    "name" => "Prof. Cleve Oberbrunner"
    "email" => "madelynn.hane@example.net"
  ]
] // tests/Unit/BootloadableTest.php:8
array:2 [
  0 => array:2 [
    "name" => "Prof. Moises Skiles IV"
    "email" => "frederique.nitzsche@example.com"
  ]
  1 => array:2 [
    "name" => "Prof. Cleve Oberbrunner"
    "email" => "madelynn.hane@example.net"
  ]
] // tests/Unit/BootloadableTest.php:13

> vendor/bin/pest

array:2 [
  0 => array:2 [
    "name" => "Doug Marvin"
    "email" => "nash.schoen@example.com"
  ]
  1 => array:2 [
    "name" => "Joaquin Jacobi"
    "email" => "neoma38@example.net"
  ]
] // tests/Unit/BootloadableTest.php:8
array:2 [
  0 => array:2 [
    "name" => "Doug Marvin"
    "email" => "nash.schoen@example.com"
  ]
  1 => array:2 [
    "name" => "Joaquin Jacobi"
    "email" => "neoma38@example.net"
  ]
] // tests/Unit/BootloadableTest.php:13
وارد حالت تمام صفحه شوید

از حالت تمام صفحه خارج شوید

اکنون زمان دستکاری داده ها در آزمون های مختلف است.

بیایید تصور کنیم که تست اول نام کاربر اول را تغییر می‌دهد و تست دوم نام به‌روزرسانی‌شده از تست اول را تأیید می‌کند.

Tests/Unit/BootloadableTest.php



use App\Models\User;

beforeEach( function()
{
    $this->user = User::first();

    $this->new = "Capsules Codes";
} );

it( "can modify first user name between two tests", function()
{
    $name = $this->user->name;

    echo $this->user->name;

    expect( $this->user->name )->toBe( $name );

    $this->user->name = $this->new;

    $this->user->save();
} );

it( "can verify first user name between two tests", function()
{
    echo " > {$this->user->name} \n";

    expect( $this->user->name )->toBe( $this->new );
} );
وارد حالت تمام صفحه شوید

از حالت تمام صفحه خارج شوید

نتیجه پس از دو اعدام متوالی از vendor/bin/pest فرمان

> vendor/bin/pest 

Esteban Raynor > Capsules Codes

PASS  Tests\Unit\BootloadableTest
✓ it can modify first user name between two tests
✓ it can verify first user name between two tests

Tests:    2 passed (2 assertions)
Duration: 0.40s

> vendor/bin/pest

Demario Corkery > Capsules Codes

PASS  Tests\Unit\BootloadableTest
✓ it can modify first user name between two tests
✓ it can verify first user name between two tests

Tests:    2 passed (2 assertions)
Duration: 0.39s
وارد حالت تمام صفحه شوید

از حالت تمام صفحه خارج شوید

اگر هر تست در فایل خودش قرار گیرد چه اتفاقی می افتد؟

Unit/FirstTest.php



use App\Models\User;

beforeEach( function()
{
    $this->user = User::first();

    $this->new = "Capsules Codes";
} );

it( "can modify first user name between two tests", function()
{
    $name = $this->user->name;

    echo "{$this->user->name} > $this->new \n";

    expect( $this->user->name )->toBe( $name );

    $this->user->name = $this->new;

    $this->user->save();
} );
وارد حالت تمام صفحه شوید

از حالت تمام صفحه خارج شوید

Unit/SecondTest.php



use App\Models\User;

beforeEach( function()
{
    $this->user = User::first();

    $this->new = "Capsules Codes";
} );

it( "can verify first user name between two tests", function()
{
    expect( $this->user->name )->toBe( $this->new );
} );
وارد حالت تمام صفحه شوید

از حالت تمام صفحه خارج شوید

vendor/bin/pest

Mrs. Laurine Ebert V > Capsules Codes

PASS  Tests\Unit\FirstTest
 it can modify first user name between two test files                                                                                                                                                                                           0.08s

PASS  Tests\Unit\SecondTest
 it can verify first user name between two test files                                                                                                                                                                                      0.01s

Tests:    2 passed (2 assertions)
Duration: 0.13s
وارد حالت تمام صفحه شوید

از حالت تمام صفحه خارج شوید

بر خلاف Pest که اجرا می کند beforeAll و afterAll روش‌هایی در ابتدا و انتهای آزمایش‌های یک فایل، در اینجا بر روی یکTestCase اساس به جای فایل. برای اجرای initialize و finalize روش‌هایی که در ابتدا و انتهای آزمون‌ها برای یک فایل داده شده، کمی تغییر در Trait ضروری است.

tests/Traits/Bootloadable.php



namespace Tests\Traits;

use Illuminate\Support\Arr;
use Illuminate\Support\Collection;
use Illuminate\Support\Str;
use Pest\TestSuite;

trait Bootloadable
{
    private static int $count = 0;
    private static array $tests;
    private static string $current;

    protected function setUp() : void
    {
        parent::setUp();

        self::$current = array_reverse( explode( '\\', debug_backtrace( DEBUG_BACKTRACE_IGNORE_ARGS, 2 )[ 1 ][ 'class' ] ) )[ 0 ];

        if( ! isset( self::$tests ) )
        {
            $this->init();
        }

        if( ! self::$count )
        {
            if( method_exists( self::class, 'initialize' ) ) $this->initialize( self::$current );
        }

        self::$count++;
    }

    protected function tearDown() : void
    {
        if(  self::$tests[ self::$current ] == self::$count )
        {
            if( method_exists( self::class, 'finalize' ) ) $this->finalize( self::$current );

            self::$count = 0;
        }

        parent::tearDown();
    }

    private function init() : void
    {
        $repository = TestSuite::getInstance()->tests;

        $data = [];

        foreach( $repository->getFilenames() as $file )
        {
            $factory = $repository->get( $file );

            $filename = Str::of( $file )->basename()->explode( '.' )->first();

            if( $factory->class === self::class ) $data = [ ...$data, ...[ $filename => count( $factory->methods ) ] ];
        }

        self::$tests = $data;
    }
}

وارد حالت تمام صفحه شوید

از حالت تمام صفحه خارج شوید

  • نام آزمون در حال اجرا با استفاده از بازیابی می شود debug_backtrace تابع
  • متغیر self::$tests اکنون یک آرایه انجمنی است، که در آن هر کلید نشان دهنده نام فایل است، و هر مقدار مربوط به تعداد تست هایی است که شامل آن است.
  • متغیر self::$count یک بار به صفر بازنشانی می شود finalize روش اجرا شده است.

مرحله آخر: اصلاح کنید TestCase فایل این امکان دسترسی به نام فایل موجود در آن را فراهم می کند.

tests/TestCase.php



namespace Tests;

use Illuminate\Foundation\Testing\TestCase as BaseTestCase;
use Tests\Traits\Bootloadable;
use Illuminate\Support\Facades\App;
use Illuminate\Support\Facades\File;

abstract class TestCase extends BaseTestCase
{
    use Bootloadable;

    protected function initialize( $filename ) : void
    {
        if( $filename == "FirstTest" )
        {
            $this->artisan( 'migrate:fresh --seed' );
        }

        if( $filename == "SecondTest" )
        {
            $this->artisan( 'migrate:fresh --seed' );
        }
    }

    protected function finalize( $filename ) : void
    {
        if( $filename == "FirstTest" )
        {
            $this->artisan( 'migrate:reset' );
        }

        if( $filename == "SecondTest" )
        {
            $this->artisan( 'migrate:reset' );
        }
    }
}
وارد حالت تمام صفحه شوید

از حالت تمام صفحه خارج شوید

تست ها را می توان دوباره اجرا کرد.

> vendor/bin/test

Brice Fisher > Capsules Codes

   PASS  Tests\Unit\FirstTest
   it can modify first user name between two tests                                                                                                                                                                                           0.36s

   FAIL  Tests\Unit\SecondTest
   it can verify first user name between two tests                                                                                                                                                                                           0.03s
  ─────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────
   FAILED  Tests\Unit\SecondTest > it can verify first user name between two tests
  Failed asserting that two strings are identical.
  -'Capsules Codes'
  +'Tremayne Spinka'

  at tests/Unit/SecondTest.php:18
     14
     15
     16 it( "can verify first user name between two tests", function()
     17 {
    18     expect( $this->user->name )->toBe( $this->new );
     19 } );
     20

  1   tests/Unit/SecondTest.php:18

  Tests:    1 failed, 1 passed (2 assertions)
  Duration: 0.43s
وارد حالت تمام صفحه شوید

از حالت تمام صفحه خارج شوید

به نظر می رسد که Brice Fisher تبدیل شده است Tremayne Spinka!

در اینجا یک نمای کلی از نتیجه به دست آمده با تکرار است BootloadableTest را به دو فایل مجزا تبدیل کنید. [ Make sure to replace “Capsules Codes” with “Pest PHP” in the second file ].

> vendor/bin/pest

Ernestine Dietrich III > Capsules Codes

PASS  Tests\Unit\FirstTest
 it can modify first user name between two test files
 it can verify first user name between two test files

Dr. Nya Gusikowski > Pest PHP

PASS  Tests\Unit\SecondTest
 it can modify first user name between two test files
 it can verify first user name between two test files

Tests:    4 passed (4 assertions)
Duration: 0.42s
وارد حالت تمام صفحه شوید

از حالت تمام صفحه خارج شوید

خوشحالم که این کمک کرد.

نوشته های مشابه

دیدگاهتان را بنویسید

نشانی ایمیل شما منتشر نخواهد شد. بخش‌های موردنیاز علامت‌گذاری شده‌اند *

دکمه بازگشت به بالا