Laravel Testing ด้วย Pest 5 ในปี 2026: TIA, Mocking และคำถามสัมภาษณ์

เชี่ยวชาญแนวทางปฏิบัติที่ดีที่สุดในการทดสอบ Laravel ด้วย Pest 5, Test Impact Analysis, Mockery, facade fake และ architecture test ครอบคลุม unit test, feature test, กลยุทธ์ mocking และคำถามสัมภาษณ์ที่พบบ่อยสำหรับนักพัฒนา Laravel

คู่มือเทคนิคการเขียนเทสต์ Laravel ด้วย Pest ครอบคลุม Mocking, Architecture Testing และการเตรียมตัวสัมภาษณ์งานสายเทคนิค 2026

แนวทางปฏิบัติที่ดีที่สุดสำหรับการทดสอบ Laravel ได้เปลี่ยนแปลงไปอย่างมากพร้อมกับ Pest 5, เครื่องมือ TIA (Test Impact Analysis) และ PHPUnit 13 โดย Laravel 13 มาพร้อมการรองรับ Pest ในตัว ทำให้ประสบการณ์การทดสอบมีความ expressive และกระชับกว่า PHPUnit ทั่วไป คู่มือนี้ครอบคลุม pattern ที่สำคัญสำหรับทั้งแอปพลิเคชัน production และการสัมภาษณ์งานสายเทคนิค

Pest 5 คือมาตรฐานใหม่

Pest 5 (สร้างบน PHPUnit 13) เป็น framework ทดสอบเริ่มต้นสำหรับ Laravel 13 ต้องการ PHP 8.4+ และนำเสนอเครื่องมือ TIA, การตรวจสอบ AI agent ในตัว, การรวมกับ PHPStan และ time-balanced sharding เป็นค่าเริ่มต้น

การตั้งค่า Pest 5 ในโปรเจกต์ Laravel 13

ทุกแอปพลิเคชัน Laravel 13 ใหม่จะ scaffold Pest โดยอัตโนมัติ สำหรับโปรเจกต์ที่มีอยู่ การย้ายจาก Pest 4 ใช้เวลาเพียงไม่กี่นาทีเนื่องจากสิ่งที่ต้องพิจารณาหลักคือความเข้ากันได้กับ PHPUnit 13 การตั้งค่าอยู่ใน tests/Pest.php ซึ่งเป็นที่ลงทะเบียน trait และ helper method แบบ global

tests/Pest.phpphp
use Illuminate\Foundation\Testing\RefreshDatabase;

pest()
    ->extend(Tests\TestCase::class)
    ->use(RefreshDatabase::class)
    ->in('Feature');

ไฟล์เดียวนี้แทนที่ trait CreatesApplication แบบเก่าและการสืบทอด base test class โดย Trait RefreshDatabase จะห่อแต่ละ test ด้วย database transaction และ rollback การเปลี่ยนแปลงโดยอัตโนมัติ

tests/Feature/UserRegistrationTest.phpphp
use App\Models\User;

it('registers a new user with valid data', function () {
    $response = $this->postJson('/api/register', [
        'name' => 'Jane Doe',
        'email' => 'jane@example.com',
        'password' => 'SecurePass123!',
        'password_confirmation' => 'SecurePass123!',
    ]);

    $response->assertStatus(201)
        ->assertJsonStructure(['user' => ['id', 'name', 'email']]);

    expect(User::where('email', 'jane@example.com')->exists())->toBeTrue();
});

API expect() ของ Pest เชื่อมต่อได้อย่างเป็นธรรมชาติกับ assertion ของ PHPUnit การเรียก assertJsonStructure ตรวจสอบรูปแบบของ response ขณะที่ expect()->toBeTrue() ยืนยันสถานะของ database ทั้งสองรูปแบบใช้ร่วมกันได้โดยไม่มีความขัดแย้ง

Test Impact Analysis: ฟีเจอร์เด่นของ Pest 5

เครื่องมือ TIA คือสิ่งที่ทำให้ Pest 5 เปลี่ยนแปลงวิธีการทดสอบสำหรับ codebase ขนาดใหญ่ ในการรันครั้งแรก Pest จะบันทึกว่า test ใดเข้าถึงไฟล์ใด การรันครั้งต่อไปจะทำงานเฉพาะ test ที่ได้รับผลกระทบจากการเปลี่ยนแปลงและเล่นผลลัพธ์ที่ cache ไว้สำหรับส่วนที่เหลือ

bash
# First run: records baseline (requires PCOV or Xdebug)
php artisan test

# Subsequent runs: only affected tests execute
php artisan test

Test suite ของ Laravel Cloud ที่มีมากกว่า 19,000 test ลดเวลาจากประมาณสามนาทีเหลือห้าวินาทีเมื่อเปิดใช้งาน TIA เครื่องมือนี้เข้าใจมากกว่าไฟล์ PHP: มันตรวจจับการเปลี่ยนแปลงใน migration, Blade template และ JavaScript component ที่ใช้ร่วมกัน

TIA ต้องการ coverage driver (PCOV หรือ Xdebug) เพื่อบันทึก baseline สำหรับ CI pipeline การ mapping ที่ cache ไว้จะคงอยู่ระหว่างการรัน ทำให้การปรับปรุงประสิทธิภาพสะสมขึ้นเรื่อยๆ

Unit Test กับ Feature Test ใน Laravel

ความแตกต่างระหว่าง unit test และ feature test ใน Laravel กำหนดว่าส่วนใดของ framework จะถูกเรียกใช้ Unit test ทำงานโดยไม่มี application container ทำให้เร็วกว่าแต่จำกัดเฉพาะ logic ล้วนๆ Feature test จะ boot แอปพลิเคชันทั้งหมด ทำให้สามารถเรียก HTTP, query database และ resolve service ได้

tests/Unit/PriceCalculatorTest.phpphp
use App\Services\PriceCalculator;

describe('PriceCalculator', function () {
    it('applies a percentage discount correctly', function () {
        $calculator = new PriceCalculator();

        // 20% off a 150.00 base price
        $result = $calculator->applyDiscount(150.00, 20);

        expect($result)->toBe(120.00);
    });

    it('rejects negative discount values', function () {
        $calculator = new PriceCalculator();

        expect(fn () => $calculator->applyDiscount(100.00, -5))
            ->toThrow(InvalidArgumentException::class);
    });
});

Unit test มุ่งเป้าไปที่ class ที่แยกตัวออกมาโดยไม่มี dependency ภายนอก Block describe จัดกลุ่ม assertion ที่เกี่ยวข้อง และ Pest 5 รองรับ block describe ซ้อนกันสำหรับลำดับชั้น test ที่ซับซ้อน

Feature test ควรเป็นส่วนใหญ่ของ test suite ใน Laravel เพราะจับ bug ด้านการทำงานร่วมกันที่ unit test พลาด เช่น middleware ของ route ที่ไม่ถูกต้อง, กฎ validation ที่หายไป หรือ ความสัมพันธ์ Eloquent ที่เสียหาย กฎทั่วไป: ถ้าโค้ดเข้าถึง database, HTTP layer หรือ facade ให้เขียน feature test

กลยุทธ์ Mocking ด้วย Facade และ Mockery

Mocking ในการทดสอบ Laravel แยกโค้ดที่กำลังทดสอบออกจาก dependency ภายนอก Facade ของ Laravel มี implementation fake ในตัวสำหรับ queue, event, notification, mail และ storage ส่วน Mockery จัดการส่วนที่เหลือ

tests/Feature/OrderProcessingTest.phpphp
use App\Models\Order;
use App\Models\User;
use Illuminate\Support\Facades\Mail;
use Illuminate\Support\Facades\Queue;
use App\Mail\OrderConfirmation;
use App\Jobs\ProcessPayment;

it('dispatches payment job and sends confirmation email', function () {
    // Fake both Mail and Queue facades
    Mail::fake();
    Queue::fake();

    $user = User::factory()->create();
    $order = Order::factory()->for($user)->create([
        'total' => 99.99,
        'status' => 'pending',
    ]);

    // Act: confirm the order via HTTP
    $this->actingAs($user)
        ->postJson("/api/orders/{$order->id}/confirm")
        ->assertOk();

    // Assert: payment job was dispatched with correct amount
    Queue::assertPushed(ProcessPayment::class, function ($job) use ($order) {
        return $job->order->id === $order->id
            && $job->order->total === 99.99;
    });

    // Assert: confirmation email was sent to the user
    Mail::assertSent(OrderConfirmation::class, function ($mail) use ($user) {
        return $mail->hasTo($user->email);
    });
});

Facade fake สกัดกั้นการเรียกในระดับ framework ป้องกันไม่ให้ส่งอีเมลจริงหรือ dispatch job Assertion แบบ closure ตรวจสอบข้อมูลที่แน่นอนที่ส่งไปยังแต่ละ component

Mock ที่ขอบเขตระบบ

หลีกเลี่ยงการ mock class domain ภายใน ให้ mock ที่ขอบเขต: API ภายนอก, mail, queue, filesystem การ mock มากเกินไปทำให้ test เปราะบางและผูกติดกับรายละเอียดของ implementation

สำหรับ dependency ที่ไม่ใช่ facade ให้ inject ผ่าน constructor และใช้ Mockery:

tests/Feature/PaymentGatewayTest.phpphp
use App\Services\PaymentGateway;
use App\Services\StripeClient;

it('charges the customer through the payment gateway', function () {
    // Create a mock of the Stripe client
    $stripeClient = Mockery::mock(StripeClient::class);
    $stripeClient->shouldReceive('charge')
        ->once()
        ->with('cus_abc123', 5000, 'usd')
        ->andReturn(['status' => 'succeeded', 'id' => 'ch_xyz']);

    // Bind the mock in the container
    $this->app->instance(StripeClient::class, $stripeClient);

    $gateway = app(PaymentGateway::class);
    $result = $gateway->processCharge('cus_abc123', 50.00);

    expect($result['status'])->toBe('succeeded');
});

การ bind mock ด้วย $this->app->instance() แทนที่ implementation จริงตลอดระยะเวลาของ test โดย Mockery ตรวจสอบว่า charge ถูกเรียกเพียงครั้งเดียวพร้อม argument ที่คาดหวัง

Architecture Testing ด้วย Preset ของ Pest

Pest 5 รวม architecture testing ที่บังคับใช้กฎโครงสร้างทั่วทั้ง codebase Test เหล่านี้ทำงานกับ AST ไม่ใช่ runtime ทำให้เร็วมาก

tests/Architecture/ArchitectureTest.phpphp
arch('controllers do not use Eloquent directly')
    ->expect('App\Http\Controllers')
    ->not->toUse('Illuminate\Database\Eloquent');

arch('services are final classes')
    ->expect('App\Services')
    ->toBeFinal();

arch('no debugging functions in production code')
    ->expect(['dd', 'dump', 'var_dump', 'ray'])
    ->not->toBeUsed();

กฎแรกป้องกันไม่ให้ controller query database โดยตรง บังคับใช้ pattern service layer กฎที่สองรับรองว่า service ไม่สามารถถูก extend ลดความซับซ้อนของการสืบทอด กฎที่สามจับ statement debug ที่เหลืออยู่ก่อนถึง production

Preset เฉพาะสำหรับ Laravel ก็มีให้ใช้งาน:

tests/Architecture/LaravelPresetTest.phpphp
arch()->preset()->laravel();
arch()->preset()->security();
arch()->preset()->php();

สามบรรทัดนี้บังคับใช้กฎหลายสิบข้อ: model extend จาก base class ที่ถูกต้อง, controller ไม่มี business logic, ไม่มีฟังก์ชันที่ไม่ปลอดภัยเช่น eval() หรือ md5() สำหรับการ hash และปฏิบัติตามมาตรฐาน PHP

พร้อมที่จะพิชิตการสัมภาษณ์ Laravel แล้วหรือยังครับ?

ฝึกฝนด้วยตัวจำลองแบบโต้ตอบ, flashcards และแบบทดสอบเทคนิคครับ

Mutation Testing เพื่อตรวจสอบคุณภาพ Test

Code coverage วัดว่าบรรทัดใดถูกทำงานระหว่าง test Mutation testing ไปไกลกว่านั้น: มันแก้ไข source code และตรวจสอบว่า test ตรวจจับการเปลี่ยนแปลงหรือไม่ ถ้า mutation รอดชีวิต test suite มีช่องโหว่

bash
# Run mutation testing on a specific class
php artisan test --mutate --class=App\\Services\\PriceCalculator

Pest 5 แนะนำ mutation เช่นการเปลี่ยน > เป็น >=, ลบ statement return และกลับเงื่อนไข boolean Mutation score ต่ำกว่า 80% มักบ่งชี้ว่า test ตรวจสอบ output โดยไม่ตรวจสอบ edge case

php
// Example: this test has high coverage but low mutation score
it('calculates shipping cost', function () {
    $cost = calculateShipping(weight: 5.0, zone: 'domestic');

    // Only checks that the result is numeric
    expect($cost)->toBeFloat();
});

// Improved: catches mutations by asserting the exact value
it('calculates domestic shipping for 5kg package', function () {
    $cost = calculateShipping(weight: 5.0, zone: 'domestic');

    // Exact assertion catches operator and value mutations
    expect($cost)->toBe(12.50);
});

Test แรกยังคง pass แม้ว่า logic การคำนวณจะผิดทั้งหมด ตราบใดที่คืนค่า float Test ที่สอง fail ทันทีเมื่อ mutation เปลี่ยนสูตร Assertion ที่เฉพาะเจาะจงให้ mutation score ที่สูงขึ้น

Pattern การทดสอบ Database และ Factory

Model factory ของ Laravel สร้างข้อมูล test ที่สมจริงโดยไม่ต้องสร้าง array ด้วยตนเอง Pest 5 รวมกับ factory ให้การ setup ข้อมูลที่อ่านง่ายและบำรุงรักษาง่าย

tests/Feature/ArticlePublishingTest.phpphp
use App\Models\Article;
use App\Models\User;

it('publishes a draft article and updates the timestamp', function () {
    $author = User::factory()->create(['role' => 'editor']);

    $article = Article::factory()
        ->for($author, 'author')
        ->draft()
        ->create(['title' => 'Testing Best Practices']);

    $this->actingAs($author)
        ->patchJson("/api/articles/{$article->id}/publish")
        ->assertOk()
        ->assertJsonPath('data.status', 'published');

    $article->refresh();

    expect($article->status)->toBe('published')
        ->and($article->published_at)->not->toBeNull()
        ->and($article->published_at->isToday())->toBeTrue();
});

Factory state draft() ตั้งค่าเริ่มต้นสำหรับบทความที่ยังไม่ได้เผยแพร่ Assertion expect() ที่ chain ด้วย ->and() อ่านเหมือนภาษาธรรมชาติและ fail พร้อมข้อความที่อธิบายชัดเจน

Parallel Testing และ Sharding

รัน php artisan test --parallel เพื่อทำงาน test หลาย process พร้อมกัน Laravel สร้าง database test แยกสำหรับแต่ละ process โดยอัตโนมัติ ป้องกันการขัดแย้งของข้อมูล Time-balanced sharding ของ Pest 5 กระจาย test ตามเวลาการทำงานจริงแทนที่จะเป็นจำนวน ทำให้ทุก CI shard เสร็จพร้อมกัน

คำถามสัมภาษณ์งานสายเทคนิคเกี่ยวกับ Laravel Testing

การสัมภาษณ์งานสายเทคนิคสำหรับ ตำแหน่ง Laravel มักทดสอบความรู้ด้านการทดสอบ ต่อไปนี้คือคำถามที่พบบ่อยที่สุดพร้อมประเด็นสำคัญที่คำตอบที่ดีควรครอบคลุม

ความแตกต่างระหว่าง fake(), mock() และ spy() ในการทดสอบ Laravel คืออะไร?

Facade fake() แทนที่ทั้ง facade ด้วย implementation ใน memory (Mail::fake, Queue::fake) mock() ผ่าน Mockery ตั้งความคาดหวังก่อนการทำงานและ fail ถ้าไม่ตรงตามนั้น spy() บันทึกการโต้ตอบและอนุญาตให้ทำ assertion หลังการทำงานโดยไม่ต้องตั้งความคาดหวังล่วงหน้า การเลือกขึ้นอยู่กับว่า test ต้องการตรวจสอบพฤติกรรม (mock), บันทึกการโต้ตอบ (spy) หรือป้องกัน side effect (fake)

RefreshDatabase แตกต่างจาก DatabaseTransactions อย่างไร?

RefreshDatabase รัน migration ครั้งเดียวและห่อแต่ละ test ด้วย transaction DatabaseTransactions สันนิษฐานว่า database มี schema ที่ถูกต้องแล้วและห่อ test ด้วย transaction เท่านั้น RefreshDatabase ปลอดภัยกว่าสำหรับ CI pipeline ที่ database อาจยังไม่มีอยู่ DatabaseTransactions เร็วกว่าเมื่อรับประกันว่า schema เป็นปัจจุบัน

เมื่อใดควรใช้ feature test มากกว่า unit test?

Feature test ควรครอบคลุมโค้ดใดก็ตามที่โต้ตอบกับ HTTP layer, database หรือ service ของ Laravel Unit test สงวนไว้สำหรับ pure function และ value object ที่ไม่มี dependency กับ framework ในแอปพลิเคชัน Laravel ทั่วไป feature test มากกว่า unit test ในอัตราส่วนประมาณ 3:1 หรือมากกว่า สะท้อนความเป็นจริงที่โค้ด Laravel ส่วนใหญ่รวมเข้ากับ framework โดยธรรมชาติ

Test Impact Analysis ปรับปรุงประสิทธิภาพ CI อย่างไร?

TIA บันทึกว่า test ใดเข้าถึงไฟล์ source ใดในการรันครั้งแรก ในการรันครั้งต่อไป Pest ทำงานเฉพาะ test ที่ได้รับผลกระทบจากไฟล์ที่เปลี่ยนแปลงและเล่นผลลัพธ์ที่ cache ไว้สำหรับ test ที่ไม่เปลี่ยนแปลง Suite ขนาดใหญ่ที่ใช้เวลาหลายนาทีสามารถเสร็จในไม่กี่วินาที ข้อแลกเปลี่ยน: TIA ต้องการ coverage driver (PCOV หรือ Xdebug) เพื่อสร้างแผนที่ dependency และการรันครั้งแรกใช้เวลานานกว่าปกติ

Mutation testing ปรับปรุงคุณภาพ test เกินกว่า code coverage อย่างไร?

Code coverage วัดเส้นทางการทำงาน Test สามารถได้ coverage 100% โดยเรียกทุก method โดยไม่ assert อะไรที่มีความหมาย Mutation testing แก้ไข source code (เปลี่ยน operator, ลบ return, กลับ boolean) และตรวจสอบว่าอย่างน้อยหนึ่ง test fail Mutation ที่รอดชีวิตเผยให้เห็น assertion ที่หลวมเกินไปหรือขาดหายไปทั้งหมด การรัน php artisan test --mutate ให้ mutation score เป็นเปอร์เซ็นต์ควบคู่กับ coverage มาตรฐาน

สำหรับ คำถามสัมภาษณ์ Laravel เพิ่มเติม คลังคำถามของ SharpSkill ครอบคลุม authentication, pattern service container, ความสัมพันธ์ Eloquent และ สถาปัตยกรรม queue

การทดสอบ HTTP Response และ JSON Assertion

Helper การทดสอบ HTTP ของ Laravel ตรวจสอบ status code, header, โครงสร้าง JSON และเป้าหมาย redirect เมื่อรวมกับ Pest จะสร้าง integration test ที่กระชับ

tests/Feature/ApiAuthenticationTest.phpphp
use App\Models\User;

describe('API Authentication', function () {
    it('rejects unauthenticated requests with 401', function () {
        $this->getJson('/api/profile')
            ->assertUnauthorized();
    });

    it('returns the authenticated user profile', function () {
        $user = User::factory()->create([
            'name' => 'John Doe',
            'email' => 'john@example.com',
        ]);

        $this->actingAs($user)
            ->getJson('/api/profile')
            ->assertOk()
            ->assertJson([
                'data' => [
                    'name' => 'John Doe',
                    'email' => 'john@example.com',
                ],
            ]);
    });

    it('validates required fields on registration', function () {
        $this->postJson('/api/register', [])
            ->assertUnprocessable()
            ->assertJsonValidationErrors(['name', 'email', 'password']);
    });
});

Block describe จัดกลุ่ม test ที่เกี่ยวข้องกับ authentication แต่ละชื่อ test อธิบายพฤติกรรมที่คาดหวังไม่ใช่ implementation Method assertJsonValidationErrors ตรวจสอบว่า field ที่ระบุมีข้อความ error validation

แหล่งข้อมูล

  • Pest 5 Released โดย Laravel News ประกาศเครื่องมือ TIA, Agent plugin และ Evals
  • Pest 5 Now Available เอกสารอย่างเป็นทางการพร้อมข้อกำหนดและฟีเจอร์ใหม่
  • What We Know About Laravel 13 ครอบคลุม PHP Attributes, AI SDK และการเปิดตัวมีนาคม 2026
  • PHPUnit 13 Release Announcement รายละเอียด assertion ใหม่และ deprecation

สิ่งที่ควรจำเกี่ยวกับ Laravel Testing ด้วย Pest 5

  • Pest 5 กับ Laravel 13 ขจัด boilerplate ผ่าน API การตั้งค่าแบบ fluent, chain expect() และการค้นหา test อัตโนมัติ
  • เครื่องมือ TIA รันซ้ำเฉพาะ test ที่ได้รับผลกระทบ ลดเวลาการทำงาน suite ขนาดใหญ่จากหลายนาทีเหลือไม่กี่วินาทีโดยไม่เสียความแม่นยำของ coverage
  • Feature test ควรเป็นหัวใจของ test suite Laravel โดย unit test สงวนไว้สำหรับ business logic ที่แยกตัว
  • Facade fake (Mail::fake(), Queue::fake()) จัดการ mocking ระดับ framework ขณะที่ Mockery จัดการ dependency ภายนอกที่ inject ผ่าน container
  • Architecture test บังคับใช้กฎโครงสร้าง (ไม่มี Eloquent ใน controller, ไม่มีฟังก์ชัน debug) โดยไม่มี runtime overhead
  • Mutation testing ด้วย --mutate จับ assertion ที่อ่อนแอที่ code coverage อย่างเดียวพลาด
  • Factory state และ assertion expect()->and() ที่ chain รักษาการ setup ข้อมูล test ให้อ่านง่ายและ assertion เฉพาะเจาะจง
  • สำหรับ การเตรียมสัมภาษณ์ Laravel การเข้าใจ mock/fake/spy, พฤติกรรม RefreshDatabase, TIA และ mutation testing แสดงให้เห็นความเป็นผู้ใหญ่ด้านการทดสอบที่เหนือกว่า coverage พื้นฐาน

เริ่มฝึกซ้อมเลย!

ทดสอบความรู้ของคุณด้วยตัวจำลองสัมภาษณ์และแบบทดสอบเทคนิคครับ

ชาเลนจ์ประจำวัน

คุณหาบั๊กใน Laravel เจอไหม

โค้ดจริงหนึ่งชิ้น บั๊กที่ซ่อนอยู่หนึ่งจุด วันละหนึ่งครั้ง ลองได้โดยไม่ต้องมีบัญชี

Anthony Fillion-Maillet

เขียนโดย

Anthony Fillion-Maillet

ผู้ก่อตั้ง SharpSkill

เป็นนักพัฒนาฟูลสแตกมากว่า 10 ปี ดูแล SharpSkill และรับผิดชอบทุกสิ่งที่เผยแพร่ที่นี่

อัปเดตเมื่อ 2 กันยายน 2569

แท็ก

#laravel
#testing
#pest
#php
#mocking
#best-practices
#interview

แชร์

บทความที่เกี่ยวข้อง