Laravel Testing 2026: Pest, Mocking และคำถามสัมภาษณ์งานสายเทคนิค

คู่มือครบวงจรสำหรับการเขียนเทสต์ Laravel ด้วย Pest ในปี 2026 ครอบคลุม Unit Test, Feature Test, Facade Mocking, Architecture Testing, Mutation Testing และคำถามสัมภาษณ์งานสำหรับนักพัฒนา PHP

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

คุณภาพของซอฟต์แวร์ในระบบนิเวศ PHP ปัจจุบันไม่ได้วัดจากความสามารถของโค้ดที่ใช้งานได้เพียงอย่างเดียว แต่ยังขึ้นอยู่กับความครอบคลุมและคุณภาพของชุดเทสต์ที่รองรับโค้ดเหล่านั้นด้วย ในปี 2026 Pest ได้กลายเป็น Testing Framework มาตรฐานของ Laravel โดยแทนที่การใช้ PHPUnit แบบดั้งเดิมในโปรเจกต์ระดับมืออาชีพเกือบทั้งหมด ความสามารถในการเขียนเทสต์ด้วย Pest, การทำ Mocking อย่างถูกต้อง และการตรวจสอบโครงสร้างสถาปัตยกรรมของโค้ดถือเป็นทักษะที่จำเป็นอย่างยิ่งในกระบวนการสัมภาษณ์งานสายเทคนิค

บทความนี้นำเสนอแนวทางปฏิบัติในการเขียนเทสต์ Laravel อย่างครบถ้วน ตั้งแต่การตั้งค่า Pest ในโปรเจกต์ ไปจนถึง Mutation Testing โดยแต่ละหัวข้อตรงกับทักษะที่มักถูกประเมินในการสัมภาษณ์งาน พร้อมตัวอย่างโค้ดที่สามารถนำไปใช้ได้จริงทั้งในแบบฝึกหัดและโปรเจกต์จริง

ทำไม Pest จึงเป็นมาตรฐานแทน PHPUnit

Pest สร้างขึ้นบน PHPUnit แต่นำเสนอ Syntax ที่อ่านง่ายกว่าโดยได้รับแรงบันดาลใจจาก Jest และ RSpec การใช้ expect()->toBe() แทน assertEquals() แบบดั้งเดิม รวมถึงโครงสร้าง describe/it ที่ช่วยจัดระเบียบเทสต์ให้อ่านเข้าใจง่าย นอกจากนี้ ฟีเจอร์อย่าง Architecture Testing และ Mutation Testing ที่มีมาในตัวช่วยลดการพึ่งพา Package ภายนอก Pest ยังคงรองรับเทสต์ PHPUnit ที่มีอยู่ได้อย่างสมบูรณ์โดยไม่ต้องแก้ไขใด ๆ

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

การตั้งค่า Pest ในโปรเจกต์ Laravel ทั้งหมดจะรวมศูนย์อยู่ที่ไฟล์ tests/Pest.php ซึ่งกำหนด Trait และ Base Class ที่จะถูกนำไปใช้กับทุกไฟล์เทสต์โดยอัตโนมัติ การรวมศูนย์แบบนี้ช่วยลดการเขียนโค้ดซ้ำซ้อนและรับประกันความสอดคล้องของสภาพแวดล้อมเทสต์ตลอดทั้ง Test Suite

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

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

เมธอด extend() ผูก Class TestCase ของ Laravel เข้ากับเทสต์ทุกตัว ทำให้สามารถเข้าถึงเมธอด HTTP อย่าง getJson, postJson รวมถึงการจำลองการยืนยันตัวตนด้วย actingAs และ Assertion ต่าง ๆ ของ Response ได้ ส่วน Trait RefreshDatabase จะรีเซ็ตฐานข้อมูลระหว่างแต่ละเทสต์ผ่าน Transaction ซึ่งรับประกันการแยกข้อมูลอย่างสมบูรณ์โดยไม่ต้องรัน Migration ใหม่ทุกครั้ง

Directive ->in('Feature') จำกัดการใช้งานการตั้งค่านี้เฉพาะกับ Feature Test เท่านั้น Unit Test ที่อยู่ใน tests/Unit/ จะใช้สภาพแวดล้อมที่เบากว่าโดยไม่มีการเชื่อมต่อฐานข้อมูล ทำให้ทำงานได้เร็วกว่ามาก การแบ่งแยกเชิงสถาปัตยกรรมระหว่าง Unit Test และ Feature Test นี้สะท้อน Test Pyramid แบบคลาสสิก และเป็นหัวข้อที่มักถูกถามในการสัมภาษณ์งาน

Unit Test กับ Feature Test

Unit Test มุ่งเน้นทดสอบ Class หรือ Method แต่ละตัวอย่างแยกส่วน โดยไม่มีการโต้ตอบกับฐานข้อมูล ระบบไฟล์ หรือ Service ภายนอก เทสต์เหล่านี้ทำงานได้ภายในไม่กี่มิลลิวินาทีและเป็นฐานที่เชื่อถือได้มากที่สุดของ Test Pyramid โครงสร้าง describe/it ของ Pest ช่วยจัดระเบียบเทสต์เหล่านี้ให้อ่านเข้าใจง่าย

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);
    });
});

บล็อก describe จัดกลุ่มเทสต์ที่เกี่ยวข้องกับ Class หรือฟีเจอร์เดียวกัน ทำให้รายงานผลการรันเทสต์อ่านง่าย Assertion toThrow() ตรวจสอบว่า Exception ถูกโยนออกมาอย่างถูกต้องสำหรับ Input ที่ไม่ถูกต้อง ซึ่งเป็น Defensive Pattern ที่สำคัญใน Business Logic การส่ง Closure ไปยัง expect() ช่วยให้จับ Exception ได้โดยไม่ทำให้เทสต์หยุดทำงาน

ในทางตรงกันข้าม Feature Test จะทดสอบพฤติกรรมของ Endpoint ทั้งหมด ตั้งแต่ HTTP Request ที่เข้ามาจนถึง JSON Response หรือ Redirect โดย Pest ทำให้การเขียนเทสต์เหล่านี้ง่ายขึ้นด้วย Syntax ที่ลื่นไหล

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();
});

เมธอด postJson() ส่ง POST Request พร้อม Header JSON ที่ตั้งค่าอัตโนมัติ ซึ่งจำลอง API Client ของจริง Assertion assertJsonStructure ตรวจสอบว่ามี Key ที่คาดหวังอยู่โดยไม่บังคับค่าเฉพาะ ทำให้เทสต์ทนทานต่อการเปลี่ยนแปลงเล็กน้อยใน Response และ Assertion สุดท้ายผ่าน expect() ยืนยันว่าผู้ใช้ถูกบันทึกลงฐานข้อมูลจริง

ในการสัมภาษณ์งาน ความสามารถในการแยกแยะว่าอะไรควรเป็น Unit Test (การคำนวณ, การแปลงข้อมูล, การตรวจสอบ Business Rule) กับอะไรควรเป็น Feature Test (การโต้ตอบ HTTP, การทำงานร่วมกับฐานข้อมูล) ถือเป็นตัวชี้วัดความเชี่ยวชาญทางเทคนิคที่สำคัญ

Mocking ด้วย Facade และ Mockery

Laravel มีระบบ Facade ที่ทำให้การเข้าถึง Service ต่าง ๆ ของ Framework เป็นเรื่องง่าย สำหรับการเทสต์ Facade เหล่านี้มีเมธอด fake() ที่แทนที่ Implementation จริงด้วย Test Double ที่สามารถบันทึกการเรียกใช้งานได้โดยไม่เกิด Side Effect กลไกนี้มีความสำคัญอย่างยิ่งสำหรับการทดสอบ Process ที่ส่งอีเมล, Dispatch Job หรือ Trigger Notification

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);
    });
});

การเรียก Mail::fake() และ Queue::fake() จะสกัดกั้นการส่งอีเมลและการ Dispatch Job ตลอดระยะเวลาของเทสต์ ไม่มีอีเมลจริงถูกส่งออกไป ไม่มี Job ถูกประมวลผลจริง Assertion อย่าง assertPushed และ assertSent จะตรวจสอบว่า Class ที่ถูกต้องถูก Dispatch พร้อมกับ Parameter ที่คาดหวัง

Closure ที่ส่งไปยัง Queue::assertPushed แสดงถึง Pattern ขั้นสูง: ไม่เพียงตรวจสอบว่า Job ถูก Dispatch เท่านั้น แต่ยังตรวจสอบว่า Property ของ Job มีค่าที่ถูกต้องด้วย ความละเอียดของ Assertion ระดับนี้สามารถตรวจจับ Bug ที่ละเอียดอ่อน เช่น กรณีที่ Object ผิดตัวถูกส่งไปยัง Job ซึ่งเป็นข้อผิดพลาดที่พบบ่อยระหว่างการ Refactor

สำหรับ Service ที่ไม่ใช่ Facade ของ Laravel นั้น Mockery ให้การควบคุมที่ละเอียดกว่าสำหรับพฤติกรรมของ Test Double กรณีนี้มักเกิดขึ้นกับ Client ของ Service ภายนอก เช่น Stripe, Twilio หรือ API ภายใน

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');
});

เมธอด Mockery::mock() สร้าง Test Double ที่สกัดกั้นการเรียก Method การเชื่อมต่อ shouldReceive()->once()->with()->andReturn() กำหนดเงื่อนไขที่เข้มงวด: Method charge ต้องถูกเรียกเพียงครั้งเดียว พร้อม Parameter ที่ระบุ และจะคืนค่าที่กำหนดไว้ หากมีความเบี่ยงเบนใด ๆ เทสต์จะล้มเหลวทันที

คำสั่ง $this->app->instance() ลงทะเบียน Mock ใน Service Container ของ Laravel เมื่อ PaymentGateway ร้องขอ Instance ของ StripeClient ผ่าน Dependency Injection ตัว Container จะให้ Mock แทน Implementation จริง กลไกนี้อาศัยหลักการ Dependency Inversion และเป็นหัวใจสำคัญของการทดสอบในแอปพลิเคชัน Laravel ที่มีสถาปัตยกรรมดี

ความแตกต่างระหว่างจำนวนเงินที่ส่งไปยัง processCharge (50.00 ดอลลาร์) กับที่ส่งไปยัง charge (5000 เซ็นต์) เผยให้เห็น Logic การแปลงค่าภายใน PaymentGateway เทสต์จึงตรวจสอบการแปลงค่านี้โดยปริยาย และสามารถตรวจจับ Regression ในการคำนวณได้โดยไม่ต้องตรวจสอบโค้ดของ Class โดยตรง

Mock เฉพาะที่ขอบเขตของ Service เท่านั้น

การ Mock ควรทำเฉพาะที่ขอบเขต (Boundary) ของ Service เท่านั้น ไม่ควร Mock Class ภายในที่อยู่ภายใต้การควบคุมของโปรเจกต์ การ Mock มากเกินไปจะทำให้เทสต์ผูกติดกับ Implementation Detail แทนที่จะเป็นพฤติกรรม ส่งผลให้เทสต์เปราะบางและต้องแก้ไขทุกครั้งที่ Refactor โค้ดภายใน ให้ใช้ Facade Fake สำหรับ Service ของ Laravel และ Mockery สำหรับ Service ภายนอกเท่านั้น

Architecture Testing ด้วย Pest Preset

Pest นำเสนอฟีเจอร์ที่ไม่เหมือนใครในระบบนิเวศ PHP: Architecture Test เทสต์เหล่านี้ตรวจสอบว่า Source Code ปฏิบัติตามข้อตกลงเชิงโครงสร้างที่ทีมกำหนดไว้ โดยไม่ต้องรัน Application Logic การละเมิดจะถูกตรวจจับแบบ Static Analysis คล้ายกับ Linter ขั้นสูง

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 ต้องไม่จัดการ Eloquent โดยตรง ซึ่งบังคับให้ใช้ Service Layer หรือ Repository ข้อจำกัดนี้ป้องกันการสะสมของ Business Logic ใน Controller ซึ่งเป็น Anti-pattern คลาสสิกของแอปพลิเคชัน Laravel ที่มีโครงสร้างไม่ดี

เทสต์ที่สองรับประกันว่า Service Class ถูกประกาศเป็น final ซึ่งป้องกัน Inheritance ที่ไม่เหมาะสมอันทำให้การบำรุงรักษาซับซ้อนขึ้น เทสต์ที่สามตรวจจับ Debug Function ที่หลงเหลืออยู่ในโค้ดที่ใช้งานจริง ซึ่งอาจทำให้เกิดการรั่วไหลของข้อมูลใน Production

Pest ยังมี Architecture Preset ที่กำหนดค่ามาแล้วซึ่งครอบคลุมข้อตกลงมาตรฐานของ Laravel

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

Preset laravel() ตรวจสอบว่า Controller Extend จาก Base Class ที่ถูกต้อง, Model อยู่ใน Namespace ที่ถูกต้อง และปฏิบัติตาม Naming Convention Preset security() ตรวจจับการใช้ Function ที่อันตราย เช่น eval(), exec() หรือ shell_exec() และ Preset php() บังคับ Best Practice ทั่วไปของภาษา

ในการสัมภาษณ์งาน ความรู้เรื่อง Architecture Test แยกผู้สมัครที่มีวิสัยทัศน์กว้างเกี่ยวกับคุณภาพซอฟต์แวร์ออกจากผู้ที่จำกัดตัวเองอยู่แค่ Feature Test เทสต์เหล่านี้ทำให้การตัดสินใจเชิงสถาปัตยกรรมของทีมสามารถรันได้อัตโนมัติใน CI Pipeline

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

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

Mutation Testing

Code Coverage วัดว่าบรรทัดใดถูกรันระหว่างการเทสต์ แต่ไม่ได้บอกอะไรเกี่ยวกับคุณภาพของ Assertion Mutation Testing อุดช่องโหว่นี้โดยการแก้ไข Source Code อย่างเป็นระบบ (เปลี่ยน Operator, ลบเงื่อนไข, แก้ไขค่า Return) แล้วตรวจสอบว่าเทสต์ตรวจจับการเปลี่ยนแปลงแต่ละครั้งได้หรือไม่ เทสต์ที่ผ่านทั้ง ๆ ที่มี Mutation แสดงว่าเทสต์นั้นไม่แม่นยำเพียงพอ

Pest ผนวก Mutation Testing เข้ามาเป็นฟีเจอร์ Built-in ตั้งแต่เวอร์ชัน 3 ทำให้สามารถใช้งานได้โดยไม่ต้องตั้งค่าเพิ่มเติม

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

คำสั่งนี้วิเคราะห์ Class เป้าหมาย สร้าง Mutant และรัน Test Suite สำหรับแต่ละ Mutant รายงานจะแสดง Mutation Score ซึ่งคือเปอร์เซ็นต์ของ Mutant ที่ถูกตรวจจับโดยเทสต์ที่มีอยู่

ตัวอย่างต่อไปนี้แสดงให้เห็นความแตกต่างระหว่างเทสต์ที่มี Coverage สูงแต่ Mutation Score ต่ำ กับเทสต์ที่แข็งแกร่งจริง ๆ

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);
});

เทสต์แรกผ่านแม้ว่าสูตรคำนวณจะถูกเปลี่ยนแปลงทั้งหมด เพราะตัวเลขทศนิยมใด ๆ ก็ผ่าน Assertion toBeFloat() ได้ เทสต์ที่สองบังคับค่าที่แน่นอนที่คาดหวัง ทำให้ตรวจจับ Mutation ใด ๆ ใน Logic การคำนวณได้ ความแตกต่างระหว่าง Assertion แบบกว้างกับ Assertion แบบแม่นยำนี้เป็นแนวคิดพื้นฐานที่ผู้สัมภาษณ์มักประเมิน

Mutation Score ที่ 90% ขึ้นไปบ่งบอกถึง Test Suite ที่เชื่อถือได้จริง สามารถตรวจจับ Regression ที่ละเอียดอ่อนซึ่ง Line Coverage 100% อาจมองข้ามไป

Parallel Testing เพื่อเพิ่มความเร็ว

Pest รองรับ Parallel Testing ผ่านคำสั่ง php artisan test --parallel ซึ่งกระจายเทสต์ไปรันพร้อมกันในหลาย Process โดยแต่ละ Process จะใช้ฐานข้อมูลแยกกัน ฟีเจอร์นี้สามารถลดเวลาการรัน Test Suite ลงได้อย่างมากในโปรเจกต์ขนาดใหญ่ แต่ต้องระวังว่าเทสต์ทุกตัวต้องเป็นอิสระจากกันอย่างแท้จริง ไม่ควรมีเทสต์ที่พึ่งพาสถานะจากเทสต์อื่น

รูปแบบการทดสอบฐานข้อมูลและ Factory

Model Factory ของ Laravel เป็นเครื่องมือสำคัญสำหรับสร้างข้อมูลทดสอบที่สอดคล้องและบำรุงรักษาง่าย Factory ช่วยให้กำหนดค่า Default ที่สมเหตุสมผลและระบุเฉพาะ Attribute ที่เกี่ยวข้องกับเทสต์ปัจจุบัน ทำให้จุดประสงค์ของเทสต์ชัดเจน

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();
});

เทสต์นี้ตรวจสอบ Workflow การเผยแพร่บทความทั้งหมด Factory draft() สร้างบทความในสถานะ Draft เพื่อจำลองสถานะเริ่มต้น Assertion assertJsonPath ตรวจสอบค่าที่แน่นอนใน JSON Response โดยไม่ต้องบังคับโครงสร้างทั้งหมด การเรียก refresh() โหลด Model ใหม่จากฐานข้อมูล เพื่อรับประกันว่า Assertion ตรวจสอบสถานะที่บันทึกจริง ไม่ใช่ Cache ในหน่วยความจำ

Chain ของ Assertion ใน Pest ด้วย ->and() ช่วยให้ตรวจสอบหลาย Property ในนิพจน์เดียวที่ลื่นไหล เพิ่มความอ่านง่ายของเทสต์ การตรวจสอบ isToday() ใช้ความสามารถของ Carbon ที่ผนวกใน Laravel เพื่อยืนยันว่า Timestamp การเผยแพร่ตรงกับวันปัจจุบัน

Factory จัดการความสัมพันธ์ระหว่าง Model โดยอัตโนมัติผ่านเมธอด for() และ has() ซึ่งลดโค้ดซ้ำซ้อนในการสร้างข้อมูลทดสอบ State อย่าง draft() ทำให้สามารถกำหนด Preset ของ Attribute ที่ใช้บ่อยได้ ซึ่งช่วยให้เทสต์สื่อสารจุดประสงค์ได้ชัดเจนยิ่งขึ้น

คำถามสัมภาษณ์งานที่พบบ่อย

คำถามเกี่ยวกับ Testing ในการสัมภาษณ์งาน Laravel มุ่งประเมินความเข้าใจในกลไกเบื้องลึก ไม่ใช่แค่การท่องจำ Syntax หัวข้อต่อไปนี้ครอบคลุมเรื่องที่ถูกถามบ่อยที่สุด สำหรับคำถามสัมภาษณ์ Laravel เพิ่มเติม สามารถศึกษาได้ที่ คำถามสัมภาษณ์ Laravel PHP และ Service Container กับ Dependency Injection

RefreshDatabase กับ DatabaseMigrations แตกต่างกันอย่างไร?

Trait RefreshDatabase ใช้ Database Transaction เพื่อย้อนกลับการเปลี่ยนแปลงหลังจากแต่ละเทสต์ ซึ่งเร็วกว่ามาก ส่วน DatabaseMigrations รัน Migration ทั้งหมดก่อนแต่ละเทสต์และ Truncate ตารางหลังจากนั้น ซึ่งช้ากว่าแต่จำเป็นในบางกรณีที่ Transaction ไม่เพียงพอ เช่น เทสต์ที่เกี่ยวข้องกับ Database Connection หลายตัว

ทำไมจึงควรใช้ Factory แทนการ Insert ข้อมูลด้วยตนเอง?

Factory ห่อหุ้มการสร้าง Object ทดสอบพร้อมค่า Default ที่สอดคล้องกัน ช่วยให้ระบุเฉพาะ Attribute ที่เกี่ยวข้องกับเทสต์ปัจจุบัน ทำให้จุดประสงค์ของเทสต์ชัดเจน Factory จัดการ Relationship ระหว่าง Model โดยอัตโนมัติผ่านเมธอด for() และ has() ซึ่งลดโค้ดซ้ำซ้อนในการสร้างข้อมูล

จะทดสอบ Middleware โดยไม่ต้องทดสอบ Controller ทั้งหมดได้อย่างไร?

Laravel อนุญาตให้สร้าง Instance ของ Middleware โดยตรงและเรียกเมธอด handle() ด้วย Request ที่สร้างขึ้น วิธีนี้แยก Logic ของ Middleware ออกจาก Controller อีกทางเลือกหนึ่งคือการใช้ Feature Test ที่กำหนดเป้าหมายเฉพาะ พร้อม Assertion บน HTTP Status เพื่อตรวจสอบพฤติกรรมของ Middleware ในบริบทจริง

เมื่อไหร่ควรใช้ Mockery แทน Facade Fake?

Facade Fake อย่าง Mail::fake() และ Queue::fake() เพียงพอสำหรับ Service ที่ผนวกใน Laravel ส่วน Mockery จำเป็นสำหรับ Service ภายนอก (API Client, SDK ภายนอก) ที่ไม่ใช่ Facade หลักทั่วไปคือ: ใช้ Laravel Fake เมื่อมีให้ใช้ และใช้ Mockery สำหรับส่วนที่เหลือ

Architecture Test ตรวจสอบอะไรที่เทสต์ปกติไม่ครอบคลุม?

Architecture Test ตรวจสอบโครงสร้างและ Dependency ของโค้ดโดยไม่ต้องรันโค้ด เทสต์เหล่านี้ตรวจจับการละเมิดข้อตกลง เช่น Controller ที่ใช้ Eloquent โดยตรง, Debug Function ที่หลงเหลือ หรือ Service ที่ไม่ได้ประกาศเป็น Final ซึ่ง Feature Test ไม่สามารถตรวจจับได้เพราะโค้ดยังทำงานได้ถูกต้องแม้จะละเมิดหลักสถาปัตยกรรม

จะตีความ Mutation Score ที่ต่ำแม้ว่า Code Coverage สูงได้อย่างไร?

Mutation Score ที่ต่ำพร้อมกับ Coverage สูงหมายความว่าเทสต์รันโค้ดแต่ไม่ตรวจสอบผลลัพธ์อย่างแม่นยำ วิธีแก้ไขคือเปลี่ยน Assertion แบบกว้าง เช่น toBeFloat, toBeArray ให้เป็น Assertion แบบแม่นยำ เช่น toBe(12.50), toHaveCount(3) ที่ตรวจจับการเปลี่ยนแปลงใน Business Logic ได้

การทดสอบ HTTP Response

การทดสอบ HTTP เป็นส่วนสำคัญของ Feature Test ใน Laravel ครอบคลุมตั้งแต่ Authentication, Validation ไปจนถึงการตรวจสอบโครงสร้าง JSON Response ตัวอย่างต่อไปนี้แสดง Pattern การทดสอบที่พบบ่อยในโปรเจกต์จริงและในการสัมภาษณ์งาน

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']);
    });
});

กลุ่มเทสต์นี้ครอบคลุมสามสถานการณ์พื้นฐานของ API ที่มีการยืนยันตัวตน: การปฏิเสธ Request ที่ไม่ได้ยืนยันตัวตน, Response ที่ถูกต้องสำหรับผู้ใช้ที่เข้าสู่ระบบแล้ว และการตรวจสอบฟิลด์ที่จำเป็น Assertion assertUnauthorized() ตรวจสอบ Status 401 ขณะที่ assertUnprocessable() ตรงกับ Status 422 ซึ่งเป็นมาตรฐานสำหรับ Validation Error

การใช้ assertJsonValidationErrors ตรวจสอบว่า Validation Error ถูกจัดรูปแบบอย่างถูกต้องและเชื่อมโยงกับฟิลด์ที่เกี่ยวข้อง Pattern นี้จำเป็นอย่างยิ่งสำหรับ API ที่ถูกใช้งานโดย Frontend Client ซึ่งแสดงข้อความ Error ใต้แต่ละฟิลด์ของฟอร์ม

สรุป

การเขียนเทสต์ Laravel ด้วย Pest ในปี 2026 ไม่ใช่แค่ทักษะเสริม แต่เป็นรากฐานสำคัญของความน่าเชื่อถือในสายอาชีพนักพัฒนา PHP ความสามารถในการเขียนเทสต์ที่อ่านง่าย, การ Mock Dependency ภายนอกอย่างถูกต้อง และการรักษาข้อตกลงสถาปัตยกรรมด้วยระบบอัตโนมัติ คือสิ่งที่แยกนักพัฒนาระดับ Senior ออกจากระดับ Intermediate

ประเด็นสำคัญสำหรับการสัมภาษณ์งานสายเทคนิค:

  • การตั้งค่า Pest -- ไฟล์ tests/Pest.php รวมศูนย์ Trait และ Base Class โดยแยก Unit Test กับ Feature Test อย่างชัดเจน
  • Feature Test -- การผสมผสาน postJson / assertStatus / expect ครอบคลุมการทำงานทั้งหมดของ Endpoint
  • Facade Mocking -- Mail::fake(), Queue::fake() และ Assertion ที่เกี่ยวข้องเป็น Pattern มาตรฐานสำหรับทดสอบ Asynchronous Process
  • Mockery -- สำหรับ Service ภายนอก การ Inject Mock ผ่าน Service Container เป็นไปตามหลักการ Dependency Inversion
  • Architecture Test -- ทำให้การตัดสินใจเชิงโครงสร้างของทีมสามารถรันได้อัตโนมัติใน CI/CD Pipeline
  • Mutation Testing -- Mutation Score เผยคุณภาพที่แท้จริงของ Assertion ซึ่งอยู่เหนือ Line Coverage
  • Factory และ Assertion -- การใช้ Factory ของ Laravel อย่างชำนาญและ JSON Assertion ที่ละเอียดช่วยเร่งการเขียนเทสต์ที่บำรุงรักษาง่าย

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

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

แท็ก

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

แชร์

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

ฟีเจอร์ใหม่ของ Laravel 12 starter kit และคำถามสัมภาษณ์ในปี 2026

Laravel 12 ในปี 2026: ฟีเจอร์ใหม่ Starter Kit และคำถามสัมภาษณ์

Laravel 12 นำ starter kit ที่ออกแบบใหม่มาพร้อม React 19, Vue 3, Livewire 4 และ WorkOS AuthKit คู่มือฉบับสมบูรณ์ครอบคลุมฟีเจอร์ใหม่ เส้นทางการอัปเกรด และคำถามสัมภาษณ์สำคัญสำหรับปี 2026

คำถามสัมภาษณ์งาน Laravel และ PHP - คู่มือฉบับสมบูรณ์

25 คำถามสัมภาษณ์งาน Laravel และ PHP ยอดนิยมในปี 2026

รวม 25 คำถามสัมภาษณ์งาน Laravel และ PHP ที่พบบ่อยที่สุด ครอบคลุม Service Container, Eloquent ORM, middleware, queues และการ deploy ระบบ production พร้อมคำตอบและตัวอย่างโค้ดครบถ้วน

คู่มือเจาะลึก Laravel Middleware ครอบคลุม Authentication, Rate Limiting และ Custom Middleware

Laravel Middleware เจาะลึก: Authentication, Rate Limiting และ Custom Middleware ฉบับสมบูรณ์

คู่มือเจาะลึก Laravel Middleware ครอบคลุมการทำงานของ Authentication Middleware, Rate Limiting ด้วย Throttle, การสร้าง Custom Middleware และการลงทะเบียน Middleware ใน Laravel 12 พร้อมตัวอย่างโค้ดที่ใช้งานได้จริงในโปรดักชัน