การทำ Unit Testing ด้วย PHPUnit
ในบทที่แล้ว เราได้เรียนรู้เกี่ยวกับการใช้ Debugging Tools เพื่อตรวจสอบและแก้ไขข้อผิดพลาดในโค้ด PHP ของเรา ในบทนี้ เราจะมาเรียนรู้วิธีการเขียน Unit Test โดยใช้ PHPUnit ซึ่งเป็น Framework ที่ได้รับความนิยมอย่างมากสำหรับการทำ Unit Testing ใน PHP การทำ Unit Testing จะช่วยให้เรามั่นใจได้ว่า Code ที่เราเขียนทำงานได้อย่างถูกต้องตามที่คาดหวัง และยังช่วยลดข้อผิดพลาดที่อาจเกิดขึ้นใน Production ได้อีกด้วย
Unit Testing คืออะไร?
Unit Testing คือการทดสอบส่วนย่อยที่สุดของโปรแกรม (Unit) โดยทั่วไปคือฟังก์ชันหรือเมธอด เพื่อให้แน่ใจว่า Unit นั้นทำงานได้อย่างถูกต้องตาม Specification ที่กำหนดไว้ การทำ Unit Testing จะช่วยให้เราสามารถตรวจจับข้อผิดพลาดได้ตั้งแต่เนิ่นๆ ทำให้ง่ายต่อการแก้ไข และยังช่วยให้เรามั่นใจได้ว่าเมื่อมีการเปลี่ยนแปลง Code จะไม่ส่งผลกระทบต่อส่วนอื่นๆ ของโปรแกรม
PHPUnit คืออะไร?
PHPUnit เป็น Framework สำหรับการทำ Unit Testing ใน PHP ที่ได้รับความนิยมอย่างมาก มีเครื่องมือและฟังก์ชันต่างๆ ที่ช่วยให้เราเขียน Test Cases ได้ง่ายและมีประสิทธิภาพ เช่น Assertions ต่างๆ ที่ใช้ตรวจสอบผลลัพธ์ของการทดสอบ
PHPUnit Installation
ก่อนที่เราจะเริ่มเขียน Unit Test ได้ เราต้องติดตั้ง PHPUnit ก่อน ซึ่งสามารถทำได้ง่ายๆ ผ่าน Composer:
composer require --dev phpunit/phpunit
คำสั่งนี้จะติดตั้ง PHPUnit เป็น Dependency สำหรับการพัฒนา (--dev) ซึ่งหมายความว่า PHPUnit จะไม่ถูกติดตั้งเมื่อเรา Deploy โปรแกรมไปยัง Production
หลังจากติดตั้งเสร็จเรียบร้อยแล้ว เราสามารถตรวจสอบได้ว่า PHPUnit ถูกติดตั้งสำเร็จหรือไม่โดยการรันคำสั่ง:
./vendor/bin/phpunit --version
ถ้าทุกอย่างเรียบร้อยดี จะแสดง Version ของ PHPUnit ที่ติดตั้งไว้
Test Cases
Test Case คือ Class ที่ประกอบไปด้วย Test Methods ซึ่งแต่ละ Test Method จะทดสอบการทำงานของ Unit หนึ่งๆ ในโปรแกรมของเรา โดยทั่วไปแล้ว Test Case จะอยู่ใน Directory ที่แยกจาก Code หลัก เพื่อความเป็นระเบียบ
สมมติว่าเรามี Class ชื่อ `Calculator` ที่มี Method ชื่อ `add` ซึ่งทำหน้าที่บวกเลขสองจำนวน:
// src/Calculator.php
class Calculator
{
public function add(int $a, int $b): int
{
return $a + $b;
}
}
เราสามารถสร้าง Test Case สำหรับ Class `Calculator` ได้ดังนี้:
// tests/CalculatorTest.php
use PHPUnit\Framework\TestCase;
class CalculatorTest extends TestCase
{
public function testAdd()
{
$calculator = new Calculator();
$result = $calculator->add(2, 3);
$this->assertEquals(5, $result);
}
}
อธิบาย Code:
use PHPUnit\Framework\TestCase;: Import Class `TestCase` จาก PHPUnit ซึ่งเป็น Class แม่ของ Test Case ทั้งหมดclass CalculatorTest extends TestCase: สร้าง Class `CalculatorTest` ซึ่ง extends จาก `TestCase`public function testAdd(): สร้าง Test Method ชื่อ `testAdd` โดยชื่อ Method ต้องขึ้นต้นด้วย `test` เสมอ$calculator = new Calculator();: สร้าง Instance ของ Class `Calculator`$result = $calculator->add(2, 3);: เรียกใช้ Method `add` ของ Class `Calculator`$this->assertEquals(5, $result);: ใช้ Assertion `assertEquals` เพื่อตรวจสอบว่าผลลัพธ์ที่ได้ ($result) เท่ากับ 5 หรือไม่
เพื่อให้ PHPUnit รู้ว่าจะรัน Test Case นี้ได้อย่างไร เราต้อง Configure Autoloading ในไฟล์ `composer.json` โดยเพิ่มส่วน `autoload-dev` ดังนี้:
{
"autoload": {
"psr-4": {
"App\\": "src/"
}
},
"autoload-dev": {
"psr-4": {
"Tests\\": "tests/"
}
},
"require": {
"php": "^7.4|^8.0"
},
"require-dev": {
"phpunit/phpunit": "^9.0"
}
}
จากนั้นรันคำสั่ง:
composer dump-autoload
เพื่อ Update Autoloader
ตอนนี้เราสามารถรัน Test Case ได้โดยใช้คำสั่ง:
./vendor/bin/phpunit tests/CalculatorTest.php
ถ้า Test Case ผ่าน จะแสดงข้อความ:
OK (1 test, 1 assertion)
ถ้า Test Case ไม่ผ่าน จะแสดงข้อความ Error พร้อมรายละเอียด
Assertions
Assertions คือ Method ที่ใช้ตรวจสอบว่าผลลัพธ์ของการทดสอบเป็นไปตามที่คาดหวังหรือไม่ PHPUnit มี Assertions ให้เลือกใช้มากมาย เช่น:
assertEquals($expected, $actual): ตรวจสอบว่า$actualเท่ากับ$expectedassertSame($expected, $actual): ตรวจสอบว่า$actualเหมือนกับ$expected(Type และ Value ต้องเหมือนกัน)assertTrue($condition): ตรวจสอบว่า$conditionเป็นtrueassertFalse($condition): ตรวจสอบว่า$conditionเป็นfalseassertNull($variable): ตรวจสอบว่า$variableเป็นnullassertNotNull($variable): ตรวจสอบว่า$variableไม่เป็นnullassertGreaterThan($expected, $actual): ตรวจสอบว่า$actualมากกว่า$expectedassertLessThan($expected, $actual): ตรวจสอบว่า$actualน้อยกว่า$expectedassertEmpty($variable): ตรวจสอบว่า$variableว่างเปล่า (Empty)assertNotEmpty($variable): ตรวจสอบว่า$variableไม่ว่างเปล่าassertArrayHasKey($key, $array): ตรวจสอบว่า$arrayมี Key$keyassertObjectHasAttribute($attribute, $object): ตรวจสอบว่า$objectมี Attribute$attributeassertStringContainsString($needle, $haystack): ตรวจสอบว่า$haystackมี String$needle
ตัวอย่างการใช้งาน Assertions อื่นๆ:
public function testAddNegativeNumbers()
{
$calculator = new Calculator();
$result = $calculator->add(-2, -3);
$this->assertEquals(-5, $result);
}
public function testAddZero()
{
$calculator = new Calculator();
$result = $calculator->add(0, 5);
$this->assertSame(5, $result); // ใช้ assertSame เพราะต้องการตรวจสอบ Type ด้วย
}
กรณีใช้งานจริงและข้อผิดพลาดที่พบบ่อย
- การตั้งชื่อ Test Methods: ควรตั้งชื่อ Test Methods ให้สื่อความหมายถึงสิ่งที่กำลังทดสอบ เช่น `testAddTwoPositiveNumbers`, `testAddNegativeAndPositiveNumbers`, `testAddWithZero`
- การทดสอบ Edge Cases: ควรทดสอบ Edge Cases หรือกรณีพิเศษต่างๆ เช่น การบวกเลขที่มีค่าเป็นศูนย์, การบวกเลขที่มีค่าเป็นลบ, การบวกเลขที่มีค่ามากเกินไป
- การ Mocking Dependencies: ในบางครั้ง Unit ที่เราต้องการทดสอบอาจมี Dependencies กับ Units อื่นๆ ซึ่งอาจทำให้การทดสอบซับซ้อนขึ้น ในกรณีนี้ เราสามารถใช้ Mocking เพื่อจำลองการทำงานของ Dependencies เหล่านั้นได้ (จะกล่าวถึงในบทต่อๆไป)
- ข้อผิดพลาดที่พบบ่อย:
- ลืม Include ไฟล์ Test Case
- ตั้งชื่อ Test Method ไม่ถูกต้อง (ไม่ได้ขึ้นต้นด้วย `test`)
- ใช้ Assertion ไม่ถูกต้อง (เช่น ใช้ `assertEquals` แทน `assertSame` ในกรณีที่ต้องการตรวจสอบ Type ด้วย)
- ไม่ได้ Update Autoloader หลังจากเพิ่ม Test Case ใหม่
การทำ Unit Testing เป็นส่วนสำคัญของการพัฒนา Software ที่มีคุณภาพ การเริ่มต้นเขียน Unit Test ตั้งแต่เนิ่นๆ จะช่วยให้เราสามารถตรวจจับข้อผิดพลาดได้ง่ายขึ้น และยังช่วยให้เรามั่นใจได้ว่า Code ที่เราเขียนทำงานได้อย่างถูกต้องตามที่คาดหวัง ในบทต่อไป เราจะมาเรียนรู้เกี่ยวกับการทำ Code Coverage ด้วย PHPUnit ซึ่งจะช่วยให้เราวัดได้ว่า Code ของเราได้รับการทดสอบครอบคลุมมากน้อยแค่ไหน