การเขียน Unit Test สำหรับ API
ในบทที่แล้ว เราได้พูดถึงการจัดการ Exception เพื่อให้ API ของเราสามารถจัดการกับข้อผิดพลาดได้อย่างมีประสิทธิภาพ ในบทนี้ เราจะมาเรียนรู้วิธีการเขียน Unit Test เพื่อตรวจสอบว่า API ของเราทำงานได้อย่างถูกต้องตามที่เราคาดหวัง การเขียน Unit Test เป็นส่วนสำคัญของการพัฒนาซอฟต์แวร์ที่มีคุณภาพ ช่วยให้เรามั่นใจได้ว่าโค้ดของเราทำงานได้อย่างถูกต้อง และยังช่วยลดโอกาสในการเกิด Bug ใน Production อีกด้วย
Unit Test คืออะไร?
Unit Test คือการทดสอบส่วนประกอบที่เล็กที่สุดของโค้ดของเรา ซึ่งโดยทั่วไปแล้วก็คือ Function หรือ Method ใน Class จุดประสงค์ของการทำ Unit Test คือการตรวจสอบว่า Function/Method นั้นๆ ทำงานได้อย่างถูกต้องตาม Specification ที่เรากำหนดไว้ Unit Test ควรจะ:
- รวดเร็ว: Unit Test ควรจะทำงานได้อย่างรวดเร็ว เพื่อให้เราสามารถรัน Test ได้บ่อยๆ
- เป็นอิสระ: Unit Test แต่ละตัวควรจะทำงานเป็นอิสระจากกัน ไม่ควรขึ้นอยู่กับสถานะของ Test อื่นๆ
- ทำซ้ำได้: Unit Test ควรจะให้ผลลัพธ์เหมือนเดิมเสมอเมื่อรันซ้ำๆ กัน
- ตรวจสอบได้ง่าย: Unit Test ควรจะตรวจสอบผลลัพธ์ได้อย่างง่ายดาย
เหตุผลเชิงออกแบบ: ทำไมต้องทำ Unit Test? การทำ Unit Test ช่วยให้เราค้นพบ Bug ได้ตั้งแต่เนิ่นๆ ซึ่งจะช่วยลดค่าใช้จ่ายในการแก้ไข Bug ในภายหลัง นอกจากนี้ Unit Test ยังช่วยให้เรามั่นใจได้ว่าเมื่อเราแก้ไขโค้ดในอนาคต เราจะไม่ทำให้ Functionality เดิมเสียไป (Regression Testing) และยังช่วยให้โค้ดของเรามีความยืดหยุ่นมากขึ้น เพราะเมื่อเรามี Unit Test ที่ดี เราจะกล้าที่จะ Refactor โค้ดของเรามากขึ้น
การเขียน Unit Test
ในการเขียน Unit Test เราจะต้อง:
- ระบุ Unit ที่ต้องการทดสอบ: เลือก Function หรือ Method ที่เราต้องการทดสอบ
- กำหนด Input: เตรียม Input ที่จะใช้ในการทดสอบ Unit นั้นๆ
- คาดการณ์ Output: กำหนด Output ที่เราคาดหวังว่าจะได้รับจาก Unit นั้นๆ เมื่อได้รับ Input ที่เรากำหนดไว้
- เขียน Assertion: เขียน Assertion เพื่อตรวจสอบว่า Output ที่เราได้รับตรงกับ Output ที่เราคาดหวังไว้หรือไม่
ตัวอย่าง: สมมติว่าเรามี Function ที่ใช้สำหรับคำนวณราคารวมของสินค้า โดยมี Input คือ ราคาสินค้า (price) และจำนวนสินค้า (quantity) และ Output คือ ราคารวม (total price)
<?php
function calculateTotalPrice(float $price, int $quantity): float
{
return $price * $quantity;
}
?>
ในการเขียน Unit Test สำหรับ Function นี้ เราสามารถทำได้ดังนี้:
- ระบุ Unit ที่ต้องการทดสอบ: `calculateTotalPrice` Function
- กำหนด Input:
- `price = 10.00`
- `quantity = 2`
- คาดการณ์ Output: `total price = 20.00`
- เขียน Assertion: ตรวจสอบว่า `calculateTotalPrice(10.00, 2)` คืนค่า `20.00`
การใช้ PHPUnit
PHPUnit เป็น Testing Framework ที่ได้รับความนิยมมากที่สุดสำหรับ PHP ช่วยให้เราเขียนและรัน Unit Test ได้อย่างง่ายดาย ในการเริ่มต้นใช้งาน PHPUnit เราจะต้องติดตั้ง PHPUnit ก่อน โดยสามารถทำได้โดยใช้ Composer
composer require --dev phpunit/phpunit
หลังจากติดตั้ง PHPUnit แล้ว เราจะต้องสร้างไฟล์ Test Case สำหรับ Unit ที่เราต้องการทดสอบ โดยไฟล์ Test Case จะต้อง:
- อยู่ใน Directory ที่กำหนดไว้สำหรับการทำ Test (เช่น `tests/`)
- มีชื่อลงท้ายด้วย `Test.php` (เช่น `CalculateTotalPriceTest.php`)
- Class Test Case จะต้องสืบทอดมาจาก `PHPUnit\Framework\TestCase`
- Method Test จะต้องมีชื่อขึ้นต้นด้วย `test` (เช่น `testCalculateTotalPrice`)
ตัวอย่าง: สร้างไฟล์ `tests/CalculateTotalPriceTest.php`:
<?php
use PHPUnit\Framework\TestCase;
class CalculateTotalPriceTest extends TestCase
{
public function testCalculateTotalPrice(): void
{
$price = 10.00;
$quantity = 2;
$expectedTotalPrice = 20.00;
$totalPrice = calculateTotalPrice($price, $quantity);
$this->assertEquals($expectedTotalPrice, $totalPrice, "ราคารวมไม่ถูกต้อง");
}
public function testCalculateTotalPriceWithZeroQuantity(): void
{
$price = 10.00;
$quantity = 0;
$expectedTotalPrice = 0.00;
$totalPrice = calculateTotalPrice($price, $quantity);
$this->assertEquals($expectedTotalPrice, $totalPrice, "ราคารวมไม่ถูกต้องเมื่อจำนวนสินค้าเป็น 0");
}
}
?>
ในตัวอย่างนี้ เราได้สร้าง Test Case ชื่อ `CalculateTotalPriceTest` ซึ่งมี 2 Method Test คือ `testCalculateTotalPrice` และ `testCalculateTotalPriceWithZeroQuantity` Method `testCalculateTotalPrice` จะทดสอบกรณีที่จำนวนสินค้ามากกว่า 0 และ Method `testCalculateTotalPriceWithZeroQuantity` จะทดสอบกรณีที่จำนวนสินค้าเป็น 0
คำอธิบาย Code:
- `use PHPUnit\Framework\TestCase;`: Import Class `TestCase` จาก PHPUnit
- `class CalculateTotalPriceTest extends TestCase`: กำหนด Class Test Case ให้สืบทอดมาจาก `TestCase`
- `public function testCalculateTotalPrice(): void`: กำหนด Method Test (ต้องมีชื่อขึ้นต้นด้วย `test`)
- `$this->assertEquals($expectedTotalPrice, $totalPrice, "ราคารวมไม่ถูกต้อง");`: ใช้ Assertion `assertEquals` เพื่อตรวจสอบว่าค่าที่คาดหวัง (`$expectedTotalPrice`) เท่ากับค่าที่ได้รับ (`$totalPrice`) หรือไม่ ถ้าไม่เท่ากัน Test จะ Fail และแสดง Message "ราคารวมไม่ถูกต้อง"
ในการรัน Test ให้ใช้ Command:
./vendor/bin/phpunit tests/CalculateTotalPriceTest.php
ถ้า Test ผ่าน เราจะเห็น Output คล้ายๆ กับ:
PHPUnit 9.5.28 by Sebastian Bergmann and contributors.
.. 2 / 2 (100%)
Time: 00:00.011, Memory: 4.00 MB
OK (2 tests, 2 assertions)
ถ้า Test ไม่ผ่าน เราจะเห็น Output ที่แสดงรายละเอียดของ Failure พร้อมทั้ง Message ที่เรากำหนดไว้ใน Assertion
แนวปฏิบัติที่ดี:
- เขียน Test ก่อนเขียน Code: Test-Driven Development (TDD) คือแนวคิดที่ให้เราเขียน Test ก่อนที่จะเขียน Code จริง ซึ่งจะช่วยให้เราออกแบบ Code ได้ดีขึ้น และยังช่วยให้เรามั่นใจได้ว่า Code ของเราทำงานได้อย่างถูกต้อง
- ครอบคลุมทุกกรณี: พยายามเขียน Test ให้ครอบคลุมทุกกรณีที่เป็นไปได้ ทั้งกรณีปกติและกรณีที่เกิด Error
- เขียน Test ที่อ่านง่าย: เขียน Test ให้เข้าใจง่าย เพื่อให้เราสามารถเข้าใจได้ว่า Test นั้นๆ กำลังทดสอบอะไร
- รัน Test บ่อยๆ: รัน Test ทุกครั้งที่เราแก้ไข Code เพื่อให้เรามั่นใจได้ว่า Code ของเรายังทำงานได้อย่างถูกต้อง
สรุป: การเขียน Unit Test เป็นทักษะที่สำคัญสำหรับนักพัฒนา API ทุกคน ช่วยให้เราพัฒนา API ที่มีคุณภาพสูง และลดโอกาสในการเกิด Bug ใน Production ในบทนี้ เราได้เรียนรู้พื้นฐานของการเขียน Unit Test โดยใช้ PHPUnit ในบทถัดไป เราจะมาเรียนรู้เกี่ยวกับการใช้ Mock Objects ใน Unit Test เพื่อให้เราสามารถทดสอบ Code ที่ขึ้นอยู่กับ Dependencies ภายนอกได้อย่างมีประสิทธิภาพ