0Pricing
PHP Academy · บทเรียน

การจำลองและการแทนที่ด้วย Mockery

แยกหน่วยออกจากกันด้วยตัวแทนการทดสอบที่ยืดหยุ่น

การจำลองและการแทนที่ด้วย Mockery เป็นบทเรียน PHP Academy ฟรีบน CoddyKit นี่คือบทเรียนที่ 2 จากทั้งหมด 4 บทเรียน คุณสามารถอ่านบทเรียนทั้งหมดด้านล่างฟรี — จากนั้นลองปฏิบัติด้วยตัวคุณเองในเบราว์เซอร์พร้อมตัวแก้ไขโค้ดในตัวและติวเตอร์ AI ตลอด 24/7 บทเรียนนี้เป็นส่วนหนึ่งของเส้นทางการเรียน PHP Academy และความก้าวหน้าของคุณจะซิงค์ข้ามเว็บและแอป CoddyKit คอร์ส PHP Academy มีบทเรียนทั้งหมด 4 บทเรียน

เหตุใดจึงควรใช้ไลบรารีจำลองโดยเฉพาะ

PHPUnit มีส่วนติดต่อของวัตถุทดสอบจำลองเป็นของตัวเอง แต่ Mockery มีไวยากรณ์ที่ลื่นไหลและสื่อความหมายมากกว่า พร้อมความสามารถที่วัตถุจำลองของ PHPUnit ไม่มี — วัตถุจำลองบางส่วน สายสอดส่อง การจับคู่อาร์กิวเมนต์ที่ยืดหยุ่น และความคาดหวังตามลำดับ สำหรับโค้ดที่มีการทำงานร่วมกันซับซ้อน Mockery มักอ่านเข้าใจได้ดีกว่ามาก บทเรียนนี้ครอบคลุมวัตถุทดสอบจำลองทุกประเภทและเวลาที่ควรใช้แต่ละแบบ

composer require --dev mockery/mockery

ตัวยึดค่า ตัวจำลอง และตัวสอดส่อง

การใช้คำศัพท์อย่างแม่นยำช่วยป้องกันการทดสอบที่สับสน:

  • ตัวยึดค่า — ส่งคืนค่าที่กำหนดไว้ล่วงหน้า คุณตรวจสอบที่สถานะ (ผลลัพธ์)
  • ตัวจำลอง — มีความคาดหวังเกี่ยวกับการเรียกใช้ คุณตรวจสอบที่พฤติกรรม (มีการเรียกใช้อย่างถูกต้อง) หากไม่เป็นไปตามความคาดหวัง การทดสอบจะไม่ผ่าน
  • ตัวสอดส่อง — บันทึกการเรียกใช้และให้คุณตรวจสอบภายหลัง

หลักง่าย ๆ คือ ให้ใช้ตัวยึดค่าสำหรับการสอบถาม และใช้ตัวจำลอง/ตัวสอดส่องสำหรับคำสั่ง

ตัวยึดค่าด้วยการกำหนดค่าที่ส่งคืน

ใช้ Mockery::mock() และ allows() (หรือ shouldReceive()->andReturn()) เพื่อจัดเตรียมค่าที่ส่งคืนแบบกำหนดไว้ โดยไม่ตรวจสอบว่ามีการเรียกเกิดขึ้นจริงหรือไม่ ในที่นี้ ตัวยึดค่าเกตเวย์จะป้อนอัตราแลกเปลี่ยนที่ทราบแน่ชัด เพื่อให้เราทดสอบการคำนวณแยกจากส่วนอื่นได้

<?php
use Mockery;
use PHPUnit\Framework\TestCase;

final class ConverterTest extends TestCase
{
    public function test_converts_using_rate(): void
    {
        $rates = Mockery::mock(RateGateway::class);
        $rates->allows()->rateFor('USD', 'EUR')->andReturn(0.9);

        $sut = new Converter($rates);
        self::assertSame(90.0, $sut->convert(100.0, 'USD', 'EUR'));

        Mockery::close();
    }
}

ตัวจำลองที่กำหนดความคาดหวัง

เมื่อการโต้ตอบเองเป็นสัญญา — เช่น อีเมล ต้อง ถูกส่งเพียงครั้งเดียว — ให้ใช้ expects() หรือ shouldReceive()->once() Mockery จะตรวจสอบความคาดหวังระหว่าง Mockery::close() หากไม่มีการเรียกตามที่กำหนด การทดสอบจะไม่ผ่าน

<?php
use Mockery;

$mailer = Mockery::mock(Mailer::class);
$mailer->expects()
       ->send(Mockery::type(Message::class))
       ->once();

$service = new SignupService($mailer);
$service->register('ada@example.com');

Mockery::close(); // fails here if send() was never called

ตัวจับคู่อาร์กิวเมนต์

ตัวจับคู่อาร์กิวเมนต์ของ Mockery ช่วยให้กำหนดความคาดหวังได้หลวมหรือเข้มงวดตามต้องการ:

  • Mockery::any() — ค่าใดก็ได้
  • Mockery::type('string') / ชื่อคลาส — ตรวจสอบชนิดข้อมูล
  • Mockery::on(fn($a) => ...) — เงื่อนไขกำหนดเอง
  • Mockery::capture($var) — จับอาร์กิวเมนต์ไว้เพื่อตรวจสอบภายหลัง
<?php
use Mockery;

$repo = Mockery::mock(UserRepo::class);
$repo->expects()
     ->save(Mockery::on(fn(User $u) => $u->isActive()))
     ->once()
     ->andReturnTrue();

ลำดับค่าที่ส่งคืนและค่าที่ส่งคืนแบบไดนามิก

คุณสามารถกำหนดค่าหลายค่าที่จะส่งคืนในการเรียกใช้แต่ละครั้ง หรือคำนวณค่าที่ส่งคืนจากอาร์กิวเมนต์ด้วย andReturnUsing() วิธีนี้จำลองตรรกะการลองใหม่ การแบ่งหน้า หรือวัตถุที่ทำงานร่วมกันแบบมีสถานะได้

<?php
use Mockery;

$api = Mockery::mock(HttpClient::class);
// First call throws, second succeeds (retry test):
$api->shouldReceive('get')
    ->twice()
    ->andThrow(new \RuntimeException('timeout'))
    ->andReturn('{"ok":true}');

// Or derive the return from the input:
$api->shouldReceive('echo')
    ->andReturnUsing(fn(string $in) => strtoupper($in));

ตัวสอดส่อง: ตรวจสอบภายหลัง

ตัวสอดส่องสลับลำดับการทำงาน: ลงมือทำก่อน แล้วจึงตรวจสอบ Mockery::spy() จะบันทึกการเรียกใช้ จากนั้นคุณจะค้นหาการเรียกเหล่านั้นด้วย shouldHaveReceived() ตัวสอดส่องช่วยให้โครงสร้างจัดเตรียม-ลงมือทำ-ตรวจสอบสะอาด เมื่อคุณไม่ต้องการให้ความคาดหวังที่ตั้งไว้ล่วงหน้าทำให้การเตรียมยุ่งเหยิง

<?php
use Mockery;

$logger = Mockery::spy(Logger::class);

$service = new PaymentService($logger);
$service->charge(500);

// Assertions happen AFTER the action:
$logger->shouldHaveReceived('info')
       ->with(Mockery::pattern('/charged 500/'))
       ->once();

Mockery::close();

ตัวจำลองบางส่วน

บางครั้งคุณต้องการวัตถุจริง แต่ต้องการแทนที่ฟังก์ชันเพียงหนึ่งรายการ — นี่คือตัวจำลองบางส่วน makePartial() ของ Mockery จะเรียกผ่านไปยังฟังก์ชันจริง ยกเว้นฟังก์ชันที่คุณกำหนดความคาดหวังไว้ ควรใช้อย่างประหยัด: การพึ่งพาตัวจำลองบางส่วนมากเกินไปมักบ่งชี้ว่าคลาสหนึ่งทำงานมากเกินไป

<?php
use Mockery;

$report = Mockery::mock(Report::class)->makePartial();
// Only stub the slow/external method; the rest runs for real
$report->shouldReceive('fetchRawData')->andReturn(['a', 'b', 'c']);

// real summarize() runs, using the stubbed data
$summary = $report->summarize();

อย่าจำลองสิ่งที่คุณไม่ได้เป็นเจ้าของ

หลักการสำคัญของการทดสอบคือ หลีกเลี่ยงการจำลองคลาสจากผู้ให้บริการภายนอกโดยตรง ส่วนติดต่อของคลาสเหล่านั้นอาจเปลี่ยนแปลง และตัวจำลองของคุณอาจค่อย ๆ แตกต่างจากความเป็นจริงโดยไม่ส่งสัญญาณเตือน ให้ห่อคลาสเหล่านั้นไว้เบื้องหลังส่วนติดต่อของคุณเองแทน แล้วจำลองส่วนติดต่อนั้น ตัวจำลองจะตรวจสอบสัญญาของคุณ ส่วนการทดสอบการผสานรวมของตัวแปลงจะตรวจสอบการเชื่อมโยงจริง

<?php
interface PaymentGateway {            // you own this
    public function charge(int $cents, string $token): string;
}

final class StripeGateway implements PaymentGateway {
    public function __construct(private \Stripe\StripeClient $client) {}
    public function charge(int $cents, string $token): string {
        return $this->client->paymentIntents->create([/* ... */])->id;
    }
}
// Tests mock PaymentGateway, never \Stripe\StripeClient directly.

ปิดให้เรียบร้อยและตรวจสอบจำนวนการเรียก

สิ่งที่ต้องทำในการดำเนินงานมีสองประการ:

  • เรียก Mockery::close() ใน tearDown() (หรือใช้ลักษณะการทำงาน MockeryPHPUnitIntegration) เพื่อให้ตรวจสอบความคาดหวังจริงและล้างข้อมูลส่วนกลาง
  • ใช้จำนวนการเรียกที่ชัดเจน (once(), times(n), never()) — ตัวจำลองที่กำหนดคลุมเครือเปิดทางให้ข้อบกพร่องเล็ดลอดได้
<?php
use PHPUnit\Framework\TestCase;
use Mockery\Adapter\Phpunit\MockeryPHPUnitIntegration;

final class OrderServiceTest extends TestCase
{
    use MockeryPHPUnitIntegration; // auto-calls Mockery::close()

    public function test_does_not_refund_paid_orders(): void
    {
        $gw = \Mockery::mock(PaymentGateway::class);
        $gw->shouldReceive('refund')->never();
        // ... exercise SUT ...
    }
}

ความคาดหวังตามลำดับ

บางครั้งลำดับการเรียกเป็นส่วนหนึ่งของสัญญา — คุณต้องเรียก beginTransaction() ก่อน commit() ordered() ของ Mockery จะบังคับใช้ลำดับ และทำให้การทดสอบไม่ผ่านหากการเรียกมาไม่ตรงลำดับ ควรใช้เฉพาะเมื่อจำเป็นต้องรักษาลำดับจริง ๆ เพราะการระบุลำดับมากเกินไปทำให้การทดสอบเปราะบาง

<?php
use Mockery;

$tx = Mockery::mock(Transaction::class);
$tx->shouldReceive('begin')->once()->ordered();
$tx->shouldReceive('commit')->once()->ordered();

$service = new TransferService($tx);
$service->run();

Mockery::close(); // fails if commit() happened before begin()

ตรวจสอบอย่างรวดเร็ว

ตัวยึดค่าหรือตัวจำลอง — ควรใช้แบบใดกับอะไร

สรุป

คุณเข้าใจวัตถุทดสอบจำลองของ Mockery อย่างเชี่ยวชาญแล้ว:

  • ตัวยึดค่า (allows) สำหรับการสอบถาม ตัวจำลอง (expects) สำหรับคำสั่ง และตัวสอดส่องสำหรับการตรวจสอบภายหลัง
  • ตัวจับคู่อาร์กิวเมนต์ (type, on, capture) ช่วยปรับระดับความเข้มงวด
  • ลำดับค่าที่ส่งคืนและ andReturnUsing ช่วยจำลองวัตถุที่ทำงานร่วมกันแบบมีสถานะหรือแบบไดนามิก
  • ตัวจำลองบางส่วนแทนที่ฟังก์ชันเดียว ควรใช้อย่างประหยัด
  • อย่าจำลองสิ่งที่คุณไม่ได้เป็นเจ้าของ — ห่อสิ่งจากผู้ให้บริการภายนอกไว้เบื้องหลังส่วนติดต่อของคุณ
  • เรียก Mockery::close() เสมอ และตรวจสอบจำนวนการเรียกที่ชัดเจน

ถัดไป: การทดสอบแบบผสานรวมและการทดสอบการทำงาน

คำถามที่พบบ่อย

บทเรียน “การจำลองและการแทนที่ด้วย Mockery” ฟรีหรือไม่

ใช่ — ข้อความเต็มของ “การจำลองและการแทนที่ด้วย Mockery” ฟรีให้อ่านที่นี่บนเว็บ เพื่อปฏิบัติแบบโต้ตอบ (ตัวแก้ไขโค้ดในตัวและติวเตอร์ AI ตลอด 24/7) และปลดล็อคส่วนที่เหลือของคอร์ส PHP Academy ให้อัปเกรดเป็น CoddyKit PRO คอร์ส PHP Academy มีบทเรียนทั้งหมด 4 บทเรียน

คุณจะเรียนรู้อะไรในบทเรียน “การจำลองและการแทนที่ด้วย Mockery”

แยกหน่วยออกจากกันด้วยตัวแทนการทดสอบที่ยืดหยุ่น คุณปฏิบัติ PHP Academy ด้วยโค้ดที่ใช้งานได้จริงที่คุณเรียกใช้โดยตรงในเบราว์เซอร์ และติวเตอร์ AI ตลอด 24/7 ตอบคำถามของคุณขณะที่คุณไปผ่านบทเรียน

คุณต้องมีประสบการณ์ก่อนที่จะเริ่มเรียน PHP Academy หรือไม่

ไม่จำเป็นต้องมีประสบการณ์มาก่อน PHP Academy บน CoddyKit ออกแบบมาสำหรับผู้เริ่มต้นไปจนถึงผู้เรียนขั้นสูง คุณสามารถเริ่มต้นที่นี่หรือเริ่มจากตัวแรกและเรียนด้วยความเร็วของคุณเอง นี่คือบทเรียนที่ 2 จากทั้งหมด 4 บทเรียน

บทเรียน “การจำลองและการแทนที่ด้วย Mockery” ใช้เวลานานแค่ไหน

บทเรียน CoddyKit ส่วนใหญ่ใช้เวลาประมาณ 5–10 นาที แต่ละบทเรียนจึงสั้นและเป็นแบบโต้ตอบ คุณสามารถก้าวหน้าอย่างต่อเนื่องและกลับมาเรียนต่อจากตรงที่เพิ่งหยุดบนเว็บและแอปได้เลย

ฉันเขียนและรันโค้ดในบทเรียน PHP Academy นี้ได้ไหม

ได้ บทเรียน PHP Academy ทุกบทมีตัวแก้ไขโค้ดในตัว คุณจึงเขียนและรันโค้ดจริงได้เลยในเบราว์เซอร์ และได้รับข้อเสนอแนะจาก AI ในทันที — ไม่ต้องติดตั้งในเครื่องของคุณ

บทเรียนทั้งหมดในหลักสูตรนี้

  1. เวิร์กโฟลว์การพัฒนาโดยขับเคลื่อนด้วยการทดสอบ
  2. การจำลองและการแทนที่ด้วย Mockery
  3. การทดสอบแบบผสานรวมและเชิงฟังก์ชัน
  4. การทดสอบการกลายพันธุ์ด้วย Infection
← กลับไปที่ PHP Academy