การทดสอบแบบผสานรวมและเชิงฟังก์ชัน
ทดสอบการทำงานร่วมกันของส่วนประกอบในสถานการณ์จริง
การทดสอบแบบผสานรวมและเชิงฟังก์ชัน เป็นบทเรียน PHP Academy ฟรีบน CoddyKit นี่คือบทเรียนที่ 3 จากทั้งหมด 4 บทเรียน คุณสามารถอ่านบทเรียนทั้งหมดด้านล่างฟรี — จากนั้นลองปฏิบัติด้วยตัวคุณเองในเบราว์เซอร์พร้อมตัวแก้ไขโค้ดในตัวและติวเตอร์ AI ตลอด 24/7 บทเรียนนี้เป็นส่วนหนึ่งของเส้นทางการเรียน PHP Academy และความก้าวหน้าของคุณจะซิงค์ข้ามเว็บและแอป CoddyKit คอร์ส PHP Academy มีบทเรียนทั้งหมด 4 บทเรียน
เหนือกว่าการทดสอบหน่วย
การทดสอบหน่วยพิสูจน์ว่าคลาสทำงานได้อย่างโดดเดี่ยวโดยจำลองทุกอย่างไว้ แต่ตัวจำลองอาจทำให้เข้าใจผิดได้ เพราะฐานข้อมูลจริง ตัวจัดการเส้นทางของเฟรมเวิร์ก หรือชั้น HTTP อาจทำงานแตกต่างกัน การทดสอบการผสานรวม ตรวจสอบองค์ประกอบจริงตั้งแต่สองส่วนขึ้นไปที่เชื่อมต่อกัน ส่วน การทดสอบเชิงฟังก์ชัน ทดสอบแอปพลิเคชันตั้งแต่ต้นจนจบผ่านจุดเข้าถึงสาธารณะ เช่น คำขอ HTTP หรือคำสั่งคอนโซล บทเรียนนี้จะอธิบายตำแหน่งและการเขียนการทดสอบทั้งสองแบบ
พีระมิดการทดสอบ
ชุดการทดสอบที่มีสุขภาพดีควรมีรูปร่างเหมือนพีระมิด:
- การทดสอบหน่วยจำนวนมาก — ทำงานเร็ว แยกอิสระ และเรียกใช้เป็นประจำ
- การทดสอบการผสานรวมจำนวนน้อยกว่า — ใช้ฐานข้อมูล/คิว/แคชจริง จึงทำงานช้าลง
- การทดสอบเชิงฟังก์ชัน/E2E จำนวนน้อย — ใช้ระบบทั้งหมด ช้าที่สุด และเปราะบางที่สุด
การกลับลำดับนี้จนกลายเป็นรูปกรวยไอศกรีมที่มีการทดสอบ E2E เป็นส่วนใหญ่ จะทำให้ชุดการทดสอบช้าและให้ผลไม่แน่นอน ควรเพิ่มความครอบคลุมไปยังการทดสอบระดับล่างเมื่อทำได้
การผสานรวม: รีโพซิทอรีกับฐานข้อมูลจริง
การทดสอบการผสานรวมสำหรับรีโพซิทอรีจะใช้การเชื่อมต่อฐานข้อมูลจริง ไม่ใช่ตัวจำลอง เพื่อค้นหาข้อผิดพลาดของ SQL สคีมา และการแมป ควรใช้ฐานข้อมูลสำหรับการทดสอบโดยเฉพาะ หรือใช้ SQLite ในหน่วยความจำที่มีความแตกต่างของภาษาฐานข้อมูลใกล้เคียงกับระบบของคุณมากพอ รวมทั้งใช้ PDO จริง
<?php
use PHPUnit\Framework\TestCase;
final class UserRepositoryTest extends TestCase
{
private \PDO $pdo;
protected function setUp(): void
{
$this->pdo = new \PDO('sqlite::memory:');
$this->pdo->setAttribute(\PDO::ATTR_ERRMODE, \PDO::ERRMODE_EXCEPTION);
$this->pdo->exec('CREATE TABLE users (id INTEGER PRIMARY KEY, email TEXT)');
}
public function test_persists_and_finds_a_user(): void
{
$repo = new UserRepository($this->pdo);
$id = $repo->create('ada@example.com');
self::assertSame('ada@example.com', $repo->find($id)->email);
}
}
การแยกอิสระ: การย้อนกลับธุรกรรม
การทดสอบการผสานรวมต้องไม่ทิ้งสถานะไว้ให้การทดสอบอื่น การทำแบบที่เร็วที่สุดคือครอบการทดสอบแต่ละรายการด้วยธุรกรรม แล้วสั่งย้อนกลับใน tearDown() ฐานข้อมูลจะกลับสู่สถานะว่างโดยไม่ต้องใส่ข้อมูลตั้งต้นใหม่ (การล้างข้อมูลทั้งตารางเป็นทางเลือกสำรองเมื่อโค้ดที่กำลังทดสอบสั่งยืนยันธุรกรรมหรือใช้ DDL)
<?php
protected function setUp(): void
{
$this->pdo = TestDb::connection();
$this->pdo->beginTransaction();
}
protected function tearDown(): void
{
$this->pdo->rollBack(); // undo everything this test did
}
ข้อมูลตั้งต้นและตัวสร้าง
การทดสอบต้องใช้ข้อมูล ควรหลีกเลี่ยงไฟล์ SQL ขนาดใหญ่ที่เปราะบาง และเลือกใช้ ตัวสร้าง ขนาดเล็กที่สร้างเฉพาะแถวที่การทดสอบต้องการ พร้อมค่าเริ่มต้นที่เหมาะสมซึ่งสามารถปรับเปลี่ยนได้ในแต่ละกรณี วิธีนี้ทำให้เจตนาของการทดสอบแต่ละรายการชัดเจน และยังคงทำงานได้ดีเมื่อสคีมาขยายตัว
<?php
final class UserFactory
{
public static function create(\PDO $pdo, array $overrides = []): int
{
$data = array_merge(['email' => 'user'.uniqid().'@test.dev'], $overrides);
$stmt = $pdo->prepare('INSERT INTO users (email) VALUES (:email)');
$stmt->execute($data);
return (int) $pdo->lastInsertId();
}
}
เชิงฟังก์ชัน: เรียกใช้แอปเหมือนเป็นไคลเอนต์
การทดสอบเชิงฟังก์ชันจะขับเคลื่อนเคอร์เนลหรือเราเตอร์ของแอปพลิเคชันจริงด้วยคำขอจำลอง แล้วตรวจสอบการตอบกลับ เช่น รหัสสถานะ ส่วนหัว และเนื้อหา WebTestCase ของ Symfony และตัวช่วยทดสอบ HTTP ของ Laravel ทำสิ่งนี้ได้โดยไม่ต้องเปิดเซิร์ฟเวอร์เว็บจริง ด้านล่างคือรูปแบบของ Symfony
<?php
use Symfony\Bundle\FrameworkBundle\Test\WebTestCase;
final class HealthControllerTest extends WebTestCase
{
public function test_health_endpoint_returns_ok(): void
{
$client = static::createClient();
$client->request('GET', '/health');
self::assertResponseIsSuccessful(); // 2xx
self::assertJson($client->getResponse()->getContent());
}
}
การตรวจสอบการตอบกลับ
การทดสอบเชิงฟังก์ชันจะตรวจสอบพฤติกรรมที่สังเกตได้ตรงขอบเขตของระบบ ได้แก่ สถานะ รูปแบบ JSON และผลข้างเคียงในฐานข้อมูล ด้านล่าง คำขอ POST จะสร้างทรัพยากรขึ้นมา เราจะตรวจสอบทั้งการตอบกลับ HTTP และดูว่าระเบียนถูกบันทึกลงในพื้นที่จัดเก็บจริงหรือไม่ เพื่อยืนยันว่าระบบทั้งหมดทำงานร่วมกันได้
<?php
public function test_creating_a_user_persists_it(): void
{
$client = static::createClient();
$client->request('POST', '/users', server: [
'CONTENT_TYPE' => 'application/json',
], content: json_encode(['email' => 'ada@example.com']));
self::assertResponseStatusCodeSame(201);
$repo = static::getContainer()->get(UserRepository::class);
self::assertNotNull($repo->findByEmail('ada@example.com'));
}
จำลองเฉพาะขอบเขตภายนอกที่จำเป็น
แม้แต่การทดสอบเชิงฟังก์ชันก็ไม่ควรเรียกผู้ให้บริการชำระเงินจริงหรือส่งอีเมลจริง ควรแทนที่เฉพาะบริการภายนอกที่อยู่ ชั้นนอกสุด โดยสลับบริการเหล่านั้นในคอนเทนเนอร์ทดสอบเป็นตัวจำลองหรือการใช้งานในหน่วยความจำ ขณะเดียวกันยังคงใช้โค้ด การกำหนดเส้นทาง และฐานข้อมูลของคุณจริง วิธีนี้รักษาความใกล้เคียงกับการทำงานตั้งแต่ต้นจนจบไว้ได้ โดยไม่ต้องพึ่งพาการเรียกเครือข่ายที่ให้ผลไม่แน่นอน
<?php
public function test_checkout_charges_via_gateway(): void
{
$client = static::createClient();
// Replace the real gateway binding with an in-memory fake:
static::getContainer()->set(PaymentGateway::class, new FakePaymentGateway());
$client->request('POST', '/checkout', content: json_encode(['cart' => 1]));
self::assertResponseIsSuccessful();
}
แยกชุดการทดสอบ แยกความเร็ว
แบ่ง phpunit.xml ของคุณออกเป็นชุดการทดสอบ เพื่อให้ชุดการทดสอบหน่วยที่ทำงานเร็วทำงานทุกครั้งที่บันทึกไฟล์ และให้ชุดการทดสอบการผสานรวม/เชิงฟังก์ชันที่ทำงานช้าทำงานตามต้องการหรือใน CI จัดกลุ่มตามไดเรกทอรี แล้วเลือกชุดที่ต้องการด้วย --testsuite
<phpunit>
<testsuites>
<testsuite name="unit">
<directory>tests/Unit</directory>
</testsuite>
<testsuite name="integration">
<directory>tests/Integration</directory>
</testsuite>
<testsuite name="functional">
<directory>tests/Functional</directory>
</testsuite>
</testsuites>
</phpunit>ความแน่นอนสำคัญกว่าความครอบคลุม
วิธีที่เร็วที่สุดในการทำลายความไว้วางใจที่มีต่อชุดการทดสอบคือการให้ผลไม่แน่นอน ควรทำให้การทดสอบการผสานรวม/เชิงฟังก์ชันมีผลแน่นอน:
- หยุดเวลาไว้กับที่โดยฉีดนาฬิกาเข้าไป แทนการเรียกใช้
time() - กำหนดค่าเริ่มต้นให้การสุ่ม และหลีกเลี่ยงการตรวจสอบลำดับของผลลัพธ์จากคำค้นที่ไม่ได้เรียงลำดับ
- รีเซ็ตสถานะฐานข้อมูลในการทดสอบทุกครั้ง (ย้อนกลับธุรกรรม / ล้างข้อมูลทั้งตาราง)
- อย่าพึ่งพาลำดับการทำงานของการทดสอบ
ทำให้ฐานข้อมูลทดสอบตรงกับระบบจริง
SQLite ในหน่วยความจำทำงานเร็ว แต่ความแตกต่างของภาษาฐานข้อมูล เช่น ความไวต่อพิมพ์เล็กพิมพ์ใหญ่ ฟังก์ชัน JSON การบังคับใช้คีย์นอก และชนิดข้อมูล อาจซ่อนข้อผิดพลาดที่จะแสดงออกมาเฉพาะบนเครื่องมือฐานข้อมูลจริงของคุณ สำหรับโค้ดที่ใช้ SQL เฉพาะของเครื่องมือ ควรเรียกใช้การทดสอบการผสานรวมกับเครื่องมือเดียวกับระบบจริง โดยมักใช้คอนเทนเนอร์ Docker ชั่วคราวใน CI แทนการใช้สิ่งทดแทนที่สะดวกกว่า
<?php
// Read connection from environment so CI points at a real Postgres/MySQL container
$dsn = getenv('TEST_DATABASE_URL') ?: 'sqlite::memory:';
$pdo = new \PDO($dsn);
$pdo->setAttribute(\PDO::ATTR_ERRMODE, \PDO::ERRMODE_EXCEPTION);
// In CI, TEST_DATABASE_URL targets the same engine as production.
ตรวจสอบอย่างรวดเร็ว
การทดสอบเชิงฟังก์ชันควรจัดการกับผู้ให้บริการชำระเงินจากบุคคลภายนอกอย่างไร
สรุปทบทวน
คุณได้เรียนรู้การทดสอบองค์ประกอบหลายส่วนร่วมกัน:
- การทดสอบการผสานรวมใช้ฐานข้อมูลและบริการจริงเพื่อค้นหาสิ่งที่ตัวจำลองซ่อนไว้ และแยกการทดสอบด้วยการย้อนกลับธุรกรรม
- การทดสอบเชิงฟังก์ชันขับเคลื่อนเคอร์เนลจริงด้วยคำขอจำลอง แล้วตรวจสอบการตอบกลับและผลข้างเคียง
- ตัวสร้างสร้างข้อมูลเพียงเท่าที่จำเป็น และควรจำลองเฉพาะขอบเขตภายนอกที่อยู่ชั้นนอกสุด
- แบ่งชุดการทดสอบตามความเร็ว และทำให้การทดสอบที่ช้ากว่ามีผลแน่นอน
ถัดไป: การวัด คุณภาพ ของการทดสอบด้วยการทดสอบการกลายพันธุ์
คำถามที่พบบ่อย
บทเรียน “การทดสอบแบบผสานรวมและเชิงฟังก์ชัน” ฟรีหรือไม่
ใช่ — ข้อความเต็มของ “การทดสอบแบบผสานรวมและเชิงฟังก์ชัน” ฟรีให้อ่านที่นี่บนเว็บ เพื่อปฏิบัติแบบโต้ตอบ (ตัวแก้ไขโค้ดในตัวและติวเตอร์ AI ตลอด 24/7) และปลดล็อคส่วนที่เหลือของคอร์ส PHP Academy ให้อัปเกรดเป็น CoddyKit PRO คอร์ส PHP Academy มีบทเรียนทั้งหมด 4 บทเรียน
คุณจะเรียนรู้อะไรในบทเรียน “การทดสอบแบบผสานรวมและเชิงฟังก์ชัน”
ทดสอบการทำงานร่วมกันของส่วนประกอบในสถานการณ์จริง คุณปฏิบัติ PHP Academy ด้วยโค้ดที่ใช้งานได้จริงที่คุณเรียกใช้โดยตรงในเบราว์เซอร์ และติวเตอร์ AI ตลอด 24/7 ตอบคำถามของคุณขณะที่คุณไปผ่านบทเรียน
คุณต้องมีประสบการณ์ก่อนที่จะเริ่มเรียน PHP Academy หรือไม่
ไม่จำเป็นต้องมีประสบการณ์มาก่อน PHP Academy บน CoddyKit ออกแบบมาสำหรับผู้เริ่มต้นไปจนถึงผู้เรียนขั้นสูง คุณสามารถเริ่มต้นที่นี่หรือเริ่มจากตัวแรกและเรียนด้วยความเร็วของคุณเอง นี่คือบทเรียนที่ 3 จากทั้งหมด 4 บทเรียน
บทเรียน “การทดสอบแบบผสานรวมและเชิงฟังก์ชัน” ใช้เวลานานแค่ไหน
บทเรียน CoddyKit ส่วนใหญ่ใช้เวลาประมาณ 5–10 นาที แต่ละบทเรียนจึงสั้นและเป็นแบบโต้ตอบ คุณสามารถก้าวหน้าอย่างต่อเนื่องและกลับมาเรียนต่อจากตรงที่เพิ่งหยุดบนเว็บและแอปได้เลย
ฉันเขียนและรันโค้ดในบทเรียน PHP Academy นี้ได้ไหม
ได้ บทเรียน PHP Academy ทุกบทมีตัวแก้ไขโค้ดในตัว คุณจึงเขียนและรันโค้ดจริงได้เลยในเบราว์เซอร์ และได้รับข้อเสนอแนะจาก AI ในทันที — ไม่ต้องติดตั้งในเครื่องของคุณ
บทเรียนทั้งหมดในหลักสูตรนี้
- เวิร์กโฟลว์การพัฒนาโดยขับเคลื่อนด้วยการทดสอบ
- การจำลองและการแทนที่ด้วย Mockery
- การทดสอบแบบผสานรวมและเชิงฟังก์ชัน
- การทดสอบการกลายพันธุ์ด้วย Infection