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

การทดสอบแบบผสานรวมและเชิงฟังก์ชัน

ทดสอบการทำงานร่วมกันของส่วนประกอบในสถานการณ์จริง

การทดสอบแบบผสานรวมและเชิงฟังก์ชัน เป็นบทเรียน 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 ในทันที — ไม่ต้องติดตั้งในเครื่องของคุณ

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

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