Pest 5 มีอะไรใหม่ ตั้งแต่ TIA, plugin สำหรับ AI agent ไปจนถึง PHPStan และ Rector

Pest 5 ออกเมื่อปลายเดือนกรกฎาคม 2026 ต้องใช้ PHP 8.4 และ PHPUnit 13 อัปเกรดจาก Pest 4 ได้ด้วยการแก้เวอร์ชันใน composer.json ของใหม่ที่เด่นคือ TIA, plugin ให้ AI agent ลองโค้ดใน test, Evals และ plugin ของ PHPStan กับ Rector

เผยแพร่
เวลาอ่าน
5 นาที
ในโพสต์นี้ 9 หัวข้อ
  1. อัปเกรดจาก Pest 4 แก้แค่เวอร์ชันใน composer.json
  2. TIA รันเฉพาะ test ที่โค้ดที่แก้ไปถึง
  3. plugin agent ให้ AI ลองโค้ดใน test จริงได้ทันที
  4. Evals ให้คะแนนคำตอบของ LLM
  5. plugin ของ PHPStan จับ test ที่เขียนผิด
  6. ชุดกฎของ Rector แปลง assertion ให้เป็นแบบ Pest
  7. แบ่ง test ไปรันหลายเครื่องตามเวลาที่ใช้จริง
  8. expectation ใหม่ 8 ตัว
  9. ถ้ายังอยู่บน PHP 8.3

นี่คือตอนที่ 2 ของซีรีส์ Testing ใน Laravel ถ้ายังไม่คุ้นกับ Pest ลองอ่านตอนแรกก่อนได้ครับ

Nuno Maduro เปิดตัว Pest 5 บนเวที Laracon US 2026 ที่บอสตัน และออก v5.0.0 วันที่ 28 กรกฎาคม 2026 ตอนนี้เวอร์ชันล่าสุดคือ 5.3.3 เนื้อหาในโพสต์นี้มาจากเอกสารของ Pest และจากโปรเจกต์ลูกค้าสองตัวที่เราย้ายขึ้น Pest 5.3.3 แล้ว ส่วนเว็บนี้เองยังใช้ Pest 4 อยู่

อัปเกรดจาก Pest 4 แก้แค่เวอร์ชันใน composer.json

ตามคู่มืออัปเกรด Pest 5 ไม่ได้เปลี่ยนชื่อ API ตัวไหน สิ่งที่ต้องเช็กมีสองเรื่อง

  • PHP 8.4 ขึ้นไป เป็นข้อที่น่าจะติดมากที่สุด เช็กทั้งเครื่องเรา, CI และ server ที่รัน test
  • PHPUnit 13 Pest 5 สร้างอยู่บน PHPUnit 13 ถ้า test ไหนเรียกใช้ API ของ PHPUnit ตรง ๆ ให้ดูรายการที่เปลี่ยนใน PHPUnit 13

แล้วขยับ plugin ของ Pest ทุกตัวขึ้น ^5.0 พร้อมกัน

"require-dev": {
    "pestphp/pest": "^5.0",
    "pestphp/pest-plugin-laravel": "^5.0",
    "phpunit/phpunit": "^13.2"
}
composer update "pestphp/*" phpunit/phpunit --with-all-dependencies

ในโปรเจกต์ลูกค้าที่ใช้ Pest 5 phpunit.xml เดิมจาก Pest 4 ใช้ต่อได้โดยไม่ต้องแก้ และ --parallel ยังทำงานเหมือนเดิม arch test กับ mutation testing ยังมาพร้อมตัว Pest เพราะ Pest 5 ดึง pest-plugin-arch และ pest-plugin-mutate เวอร์ชัน 5 มาเอง

TIA รันเฉพาะ test ที่โค้ดที่แก้ไปถึง

TIA ย่อมาจาก Test Impact Analysis ใส่ --tia แล้วรอบแรก Pest จะจดว่า test แต่ละตัววิ่งผ่านไฟล์ไหนบ้าง รอบต่อไปรันเฉพาะ test ที่ไฟล์เหล่านั้นเปลี่ยน ส่วนที่เหลือเอาผลจากรอบก่อนมาแสดงแทน

./vendor/bin/pest --tia

Taylor Otwell เล่าว่า test ของ Laravel Cloud ที่มีมากกว่า 19,000 ตัวลดจากราว 3 นาทีเหลือ 5 วินาที ส่วนในโปรเจกต์ลูกค้าของเรา รอบเต็ม 201 วินาที รอบที่แก้ class เดียวเหลือ 10 วินาที

TIA ต้องมี coverage driver อย่าง pcov และมีจุดที่ทำให้ผลเขียวทั้งที่ไม่ได้รัน test จริงอยู่หลายจุด เราจึงแยกไปเล่าเต็ม ๆ ในตอนที่ 3

plugin agent ให้ AI ลองโค้ดใน test จริงได้ทันที

plugin นี้ทำมาให้ AI agent โดยตรง agent ส่งโค้ดสั้น ๆ ผ่าน --agent แล้ว Pest จะรันโค้ดนั้นเป็น test หนึ่งตัวชื่อ verify ข้างในใช้ factory, $this->actingAs() และ fake ของ Laravel ได้เหมือน test ที่เราเขียนเอง

composer require pestphp/pest-plugin-agent --dev

./vendor/bin/pest --agent='$user = \App\Models\User::factory()->create(); $this->actingAs($user)->get("/dashboard")->assertOk();'

ปกติพอ agent แก้โค้ดเสร็จแล้วอยากรู้ว่าหน้านั้นยังเปิดได้ไหม agent จะสร้างไฟล์ test ชั่วคราว หรือเปิด tinker ไล่ลองเอง plugin นี้ทำให้ลองได้ในคำสั่งเดียว แล้วไม่มีไฟล์ค้างใน repo ให้ต้องตามลบ เอกสารของ Pest ก็บอกไว้ว่าใช้ลองเร็ว ๆ เท่านั้น test ที่ต้องกันพลาดในอนาคตยังต้องเขียนเป็นไฟล์จริง

Evals ให้คะแนนคำตอบของ LLM

ถ้าแอปของเราเรียก LLM เช่น ให้ช่วยสรุปข้อความหรือตอบคำถามลูกค้า Evals ใช้ expect() ตัวเดิมตรวจคำตอบนั้นได้ ตรวจแบบตรงตัวอย่าง toContain() หรือให้คะแนน 0 ถึง 1 ด้วย toBeRelevant(), toBeFactual() และ toBeSafe() ซึ่งใช้ LLM อีกตัวเป็นคนให้คะแนน

Evals ไม่รันในรอบปกติ ต้องสั่ง ./vendor/bin/pest --evals เอง เพราะแต่ละรอบเรียก LLM จริงและเสียค่า token

plugin ของ PHPStan จับ test ที่เขียนผิด

ในโพสต์ของ PHPStan เราใช้ PHPStan อ่านโค้ดในแอป Pest 5 มี plugin ให้ PHPStan อ่าน test ของ Pest เข้าใจด้วย ติดตั้งแล้วเพิ่ม include บรรทัดเดียวใน phpstan.neon ไฟล์เดิม

includes:
    - vendor/larastan/larastan/extension.neon
    - vendor/pestphp/pest-plugin-phpstan/extension.neon

plugin รู้ว่า $this ใน test คือ TestCase ของเรา และเพิ่มกฎ 14 ข้อที่ทำงานทุกระดับความเข้มงวด กฎที่ช่วยได้บ่อยคือ

  • ชื่อ test ซ้ำกันในไฟล์เดียว ซึ่งทำให้ตัวหลังทับตัวแรกโดยไม่มีใครรู้
  • test ที่ closure ว่างเปล่า ผ่านทุกครั้งเพราะไม่ได้ตรวจอะไรเลย
  • expectation ที่เป็นไปไม่ได้ เช่น expect(10)->toStartWith('1')

ชื่อซ้ำกับ closure ว่างเกิดได้ง่ายตอนให้ AI agent เขียน test ทีละหลายตัว ถ้าตั้งให้ agent รัน composer analyse ก่อนจบงานทุกครั้ง กฎพวกนี้จะจับให้เอง

ถ้าโปรเจกต์ยังไม่มี Larastan อย่าเพิ่งให้ PHPStan อ่านโฟลเดอร์ tests เกินระดับ 0 ตอนเราลองกับ test ของโปรเจกต์ Laravel ตัวหนึ่งที่ระดับ 5 ได้ error 612 ข้อ ทุกข้อเป็นเรื่องเดียวกันคือ PHPStan ไม่รู้จัก Eloquent

ชุดกฎของ Rector แปลง assertion ให้เป็นแบบ Pest

Rector plugin มีกฎ 60 ข้อ แบ่งเป็นชุดจัดรูปแบบ test กับชุดอัปเกรดเวอร์ชัน ชุดที่ใช้บ่อยคือ PestSetList::CODING_STYLE ซึ่งแปลง assertion แบบ PHPUnit เป็น expect() และเปลี่ยน expect($count > 10)->toBeTrue() เป็น toBeGreaterThan(10) ที่บอกค่าจริงตอน test ไม่ผ่าน

ใส่ชุดนี้ลงใน rector.php ไฟล์เดิมของโปรเจกต์ ไม่ต้องสร้างไฟล์แยก ในโปรเจกต์ลูกค้าเราเปิดชุดนี้แล้วปิดกฎสองข้อ เพราะ test ไม่ผ่านทันทีหลังรัน

use Pest\Rector\Rules\ConvertExpectExceptionToThrowRector;
use Pest\Rector\Rules\SimplifyToLiteralBooleanRector;
use Pest\Rector\Set\PestSetList;

return RectorConfig::configure()
    ->withPaths([__DIR__.'/app', __DIR__.'/tests'])
    ->withSets([PestSetList::CODING_STYLE])
    ->withSkip([
        // เดาผิดว่าบรรทัดไหนเป็นตัวโยน exception
        ConvertExpectExceptionToThrowRector::class,
        // toEqual(false) → toBeFalse() ทำให้ค่า null ไม่ผ่านอีกต่อไป
        SimplifyToLiteralBooleanRector::class,
    ]);
  • ConvertExpectExceptionToThrowRector เปลี่ยน $this->expectException() เป็น expect(fn () => …)->toThrow() แต่ต้องเดาว่าบรรทัดไหนคือตัวที่โยน exception ในไฟล์หนึ่งกฎนี้ห่อบรรทัดที่สร้าง object ไว้ใน closure แล้วปล่อยบรรทัดที่โยน exception จริงไว้ข้างนอก assertion สองข้อจึงไม่ผ่าน
  • SimplifyToLiteralBooleanRector เปลี่ยน toEqual(false) เป็น toBeFalse() ตัวแรกเทียบแบบ == ซึ่ง null ก็ผ่าน ส่วนตัวหลังเทียบแบบ === test 6 ตัวที่ column เป็น null จึงแดงหมด

อีกจุดที่ควรอ่าน diff ก่อนรับคือ toBe([]) ที่กลายเป็น toBeEmpty() ตัวหลังผ่านทั้ง null, '' และ 0 ด้วย จึงหลวมกว่าเดิม ถ้าค่าที่ตรวจไม่ได้ประกาศว่าเป็น array เสมอ ควรคงไว้แบบเดิม วิธีตั้ง Rector ทั้งไฟล์และกฎที่เราข้ามในโค้ดแอป อยู่ในโพสต์ของ Rector

แบ่ง test ไปรันหลายเครื่องตามเวลาที่ใช้จริง

ถ้า CI แบ่ง test ไปรันหลายเครื่องพร้อมกัน Pest แบ่งตามเวลาที่ test แต่ละไฟล์ใช้ได้แล้ว ทุกเครื่องจะได้งานหนักใกล้กัน ฟีเจอร์นี้เริ่มมาตั้งแต่ Pest 4.6 รันครั้งแรกให้ Pest จดเวลาไว้ แล้ว commit ไฟล์ tests/.pest/shards.json เข้า repo

./vendor/bin/pest --update-shards
./vendor/bin/pest --shard=1/4

expectation ใหม่ 8 ตัว

toBeEmail(), toBeUlid(), toBeIpAddress(), toBeMacAddress(), toBeHostname(), toBeDomain(), toBeBase64() และ toBeHexadecimal() ทุกตัวใช้กับ ->not ได้ เช่น expect($user->email)->toBeEmail() แทนการเขียน regex เอง

ถ้ายังอยู่บน PHP 8.3

Pest 4 ยังใช้งานได้ดี arch test, browser testing และ --parallel มีครบแล้วตั้งแต่ Pest 4 ส่วน TIA กับ plugin ใหม่ในโพสต์นี้ต้องรอย้ายขึ้น PHP 8.4 ก่อน ถ้าอยากอ่านย้อนว่าแต่ละเวอร์ชันเพิ่มอะไรมา สรุป Pest 2 มีหมายเหตุสั้น ๆ ของ Pest 3, 4 และ 5 ไว้ด้วย

ตอนถัดไปเราจะลงรายละเอียดของ TIA ทั้งตัวเลขจากโปรเจกต์จริงและจุดที่ทำให้ผลเขียวหลอกตา

อ่านต่อ

ถูกอ้างถึงใน

ล่าสุด

ดูทั้งหมด →