การจำลองและการแทนที่ด้วย 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 ในทันที — ไม่ต้องติดตั้งในเครื่องของคุณ
บทเรียนทั้งหมดในหลักสูตรนี้
- เวิร์กโฟลว์การพัฒนาโดยขับเคลื่อนด้วยการทดสอบ
- การจำลองและการแทนที่ด้วย Mockery
- การทดสอบแบบผสานรวมและเชิงฟังก์ชัน
- การทดสอบการกลายพันธุ์ด้วย Infection