เขียน Feature Test ใน Laravel สำหรับเชื่อมต่อ Oracle Database (บทที่ 1: การ Set Up ไฟล์และฐานข้อมูล)
เขียน Feature Test ใน Laravel สำหรับเชื่อมต่อ Oracle Database
การเริ่มต้นเขียน Test สำหรับโปรเจกต์ที่มีความซับซ้อนอย่างการเชื่อมต่อ Oracle Database และมีระบบ Authentication (เช่น Keycloak) กั้นอยู่อาจดูเป็นเรื่องยาก บทความนี้จะพาไปตั้งไข่โครงสร้าง Feature Test แบบ Step-by-Step โดยเริ่มจากการจัดเตรียมไฟล์และ Set up สภาพแวดล้อมให้พร้อมก่อนลงมือเขียน Logic จริง
เป้าหมายของบทนี้: สามารถรัน Test เพื่อเชื่อมต่อกับ Oracle Database และวิ่งทะลุเข้า Controller ได้สำเร็จ โดยยังไม่ต้องคำนึงถึงการเช็คผลลัพธ์ (Assert) ในขั้นตอนนี้
เป้าหมายของบทนี้: สามารถรัน Test เพื่อเชื่อมต่อกับ Oracle Database และวิ่งทะลุเข้า Controller ได้สำเร็จ โดยยังไม่ต้องคำนึงถึงการเช็คผลลัพธ์ (Assert) ในขั้นตอนนี้
1. สร้างไฟล์ Test ไว้ที่ไหนดี?
ใน Laravel เราจะสร้างไฟล์ทดสอบไว้ที่: tests/Feature/Stock/SampleControllerStoreTest.php
เหตุผลที่เราใช้โครงสร้างนี้:
ใน Laravel เราจะสร้างไฟล์ทดสอบไว้ที่: tests/Feature/Stock/SampleControllerStoreTest.php
เหตุผลที่เราใช้โครงสร้างนี้:
- ทำไมต้อง Feature? เพราะเราต้องการจำลองการยิง HTTP Request และเชื่อมต่อกับระบบฐานข้อมูลจริง ซึ่งต่างจาก Unit Test ที่มักจะแยกส่วนการทำงาน
- ทำไมต้องมีโฟลเดอร์ Stock? การจัดกลุ่มไฟล์ Test ตาม Domain หรือ Module (เช่น Stock) จะช่วยให้โปรเจกต์ที่เป็นสเกลใหญ่เป็นระเบียบและค้นหาไฟล์ได้ง่ายขึ้น
2. โครงสร้างไฟล์ขั้นต่ำ (Minimal Pattern)
นี่คือโครงสร้างพื้นฐานที่คุณสามารถจำไปใช้เป็น Pattern เริ่มต้นได้เลย:
<?php
namespace Tests\Feature\Stock;
use Tests\TestCase;
use App\Models\User;
use App\Models\Scheme;
use Illuminate\Support\Facades\DB;
use Illuminate\Foundation\Testing\DatabaseTransactions;
class SampleControllerStoreTest extends TestCase
{
use DatabaseTransactions;
protected function setUp(): void
{
parent::setUp();
// ขั้นตอนสำคัญ — เราจะมาตั้งค่าการเชื่อมต่อ DB และ Middleware กันที่นี่
}
public function test_store_creates_inventory_and_redirects_to_edit(): void
{
// บทถัดไป: เราจะมาเขียน Arrange → Act → Assert กัน
$this->markTestIncomplete('ยังไม่เขียน logic — รอบทที่ 2');
}
}
จุดสำคัญที่ต้องทำความเข้าใจ
โค้ด | ความหมายและหน้าที่
extends TestCase | ต้องใช้ TestCase ของ Laravel (มีฟีเจอร์ HTTP, DB, Auth) ไม่ใช่ TestCase เพียวๆ ของ PHPUnit
DatabaseTransactions | เปิด Transaction ตอนเริ่มเทส และ Rollback กลับตอนจบ ป้องกันไม่ให้ข้อมูลขยะค้างใน Oracle
setUp() | ฟังก์ชันที่จะถูกรัน ก่อน ทุกๆ Test Method เหมาะสำหรับใช้ตั้งค่า Environment
markTestIncomplete | บอก PHPUnit ว่าเทสนี้ "ยังไม่เสร็จ" (Incomplete) จะไม่ถูกมองว่าเป็น Fail
3. เจาะลึกการตั้งค่าใน setUp()
ภายในฟังก์ชัน setUp() สำหรับเคสนี้ เราจะต้องจัดการ 2 เรื่องหลักๆ เพื่อให้ระบบพร้อมทดสอบ:
นี่คือโครงสร้างพื้นฐานที่คุณสามารถจำไปใช้เป็น Pattern เริ่มต้นได้เลย:
<?php
namespace Tests\Feature\Stock;
use Tests\TestCase;
use App\Models\User;
use App\Models\Scheme;
use Illuminate\Support\Facades\DB;
use Illuminate\Foundation\Testing\DatabaseTransactions;
class SampleControllerStoreTest extends TestCase
{
use DatabaseTransactions;
protected function setUp(): void
{
parent::setUp();
// ขั้นตอนสำคัญ — เราจะมาตั้งค่าการเชื่อมต่อ DB และ Middleware กันที่นี่
}
public function test_store_creates_inventory_and_redirects_to_edit(): void
{
// บทถัดไป: เราจะมาเขียน Arrange → Act → Assert กัน
$this->markTestIncomplete('ยังไม่เขียน logic — รอบทที่ 2');
}
}
จุดสำคัญที่ต้องทำความเข้าใจ
โค้ด | ความหมายและหน้าที่
extends TestCase | ต้องใช้ TestCase ของ Laravel (มีฟีเจอร์ HTTP, DB, Auth) ไม่ใช่ TestCase เพียวๆ ของ PHPUnit
DatabaseTransactions | เปิด Transaction ตอนเริ่มเทส และ Rollback กลับตอนจบ ป้องกันไม่ให้ข้อมูลขยะค้างใน Oracle
setUp() | ฟังก์ชันที่จะถูกรัน ก่อน ทุกๆ Test Method เหมาะสำหรับใช้ตั้งค่า Environment
markTestIncomplete | บอก PHPUnit ว่าเทสนี้ "ยังไม่เสร็จ" (Incomplete) จะไม่ถูกมองว่าเป็น Fail
3. เจาะลึกการตั้งค่าใน setUp()
ภายในฟังก์ชัน setUp() สำหรับเคสนี้ เราจะต้องจัดการ 2 เรื่องหลักๆ เพื่อให้ระบบพร้อมทดสอบ:
A) บังคับใช้ Oracle Database (ทับค่าใน phpunit.xml)
โดยปกติไฟล์ phpunit.xml มักจะถูกตั้งค่าให้ใช้ SQLite ในหน่วยความจำเพื่อความรวดเร็ว:
<env name="DB_CONNECTION" value="sqlite"/>
<env name="DB_DATABASE" value=":memory:"/>
หากเราไม่ทับค่านี้ เทสจะวิ่งไปหา SQLite ว่างๆ และทำให้เกิด Error เราจึงต้องเพิ่มคำสั่งเหล่านี้เข้าไปใน setUp():
config(['database.default' => 'oracle']);
DB::purge(); // ทิ้ง connection เก่าที่จำอยู่ใน memory
DB::reconnect('oracle'); // เชื่อมต่อใหม่ด้วย config ของ oracle จากไฟล์ .env
ข้อควรระวัง: ลำดับการเรียกใช้สำคัญมาก ต้องทำตามลำดับ config → purge → reconnect เสมอ
โดยปกติไฟล์ phpunit.xml มักจะถูกตั้งค่าให้ใช้ SQLite ในหน่วยความจำเพื่อความรวดเร็ว:
<env name="DB_CONNECTION" value="sqlite"/>
<env name="DB_DATABASE" value=":memory:"/>
หากเราไม่ทับค่านี้ เทสจะวิ่งไปหา SQLite ว่างๆ และทำให้เกิด Error เราจึงต้องเพิ่มคำสั่งเหล่านี้เข้าไปใน setUp():
config(['database.default' => 'oracle']);
DB::purge(); // ทิ้ง connection เก่าที่จำอยู่ใน memory
DB::reconnect('oracle'); // เชื่อมต่อใหม่ด้วย config ของ oracle จากไฟล์ .env
ข้อควรระวัง: ลำดับการเรียกใช้สำคัญมาก ต้องทำตามลำดับ config → purge → reconnect เสมอ
B) ปิดการทำงานของ Middleware (Bypass Authentication)
เนื่องจาก Route ของระบบ Stock มักจะถูกครอบด้วย auth:web หรือ Middleware อย่าง Keycloak ในการเขียนเทสเบื้องต้นที่เรายังไม่ต้องการจำลองการ Login ผ่าน Browser เราสามารถข้ามด่านนี้ไปก่อนได้ด้วยคำสั่ง:
$this->withoutMiddleware();
4. รวมโค้ด setUp() ฉบับสมบูรณ์
เมื่อนำทั้งสองส่วนมารวมกัน ฟังก์ชัน setUp() ของเราจะหน้าตาเป็นแบบนี้:
protected function setUp(): void
{
parent::setUp(); // บังคับ: ต้องเรียก parent ก่อนเสมอ
// 1. บังคับเชื่อมต่อ Oracle
config(['database.default' => 'oracle']);
DB::purge();
DB::reconnect('oracle');
// 2. ปิดกั้นด่าน Authentication ชั่วคราว
$this->withoutMiddleware();
}
5. ทดสอบรันและเช็คความถูกต้อง (Concept Check)
มาทดสอบดูกันว่าโครงสร้างที่เราเตรียมไว้ทำงานได้หรือไม่ โดยรันคำสั่ง:
php artisan test --filter=SampleControllerStoreTest
ผลลัพธ์ที่คาดหวัง:
เนื่องจาก Route ของระบบ Stock มักจะถูกครอบด้วย auth:web หรือ Middleware อย่าง Keycloak ในการเขียนเทสเบื้องต้นที่เรายังไม่ต้องการจำลองการ Login ผ่าน Browser เราสามารถข้ามด่านนี้ไปก่อนได้ด้วยคำสั่ง:
$this->withoutMiddleware();
4. รวมโค้ด setUp() ฉบับสมบูรณ์
เมื่อนำทั้งสองส่วนมารวมกัน ฟังก์ชัน setUp() ของเราจะหน้าตาเป็นแบบนี้:
protected function setUp(): void
{
parent::setUp(); // บังคับ: ต้องเรียก parent ก่อนเสมอ
// 1. บังคับเชื่อมต่อ Oracle
config(['database.default' => 'oracle']);
DB::purge();
DB::reconnect('oracle');
// 2. ปิดกั้นด่าน Authentication ชั่วคราว
$this->withoutMiddleware();
}
5. ทดสอบรันและเช็คความถูกต้อง (Concept Check)
มาทดสอบดูกันว่าโครงสร้างที่เราเตรียมไว้ทำงานได้หรือไม่ โดยรันคำสั่ง:
php artisan test --filter=SampleControllerStoreTest
ผลลัพธ์ที่คาดหวัง:
- ระบบควรแสดงสถานะเป็น I (Incomplete) ไม่ใช่ตัวอักษรสีแดงที่ฟ้อง Error เรื่อง Class หรือ Namespace
- สิ่งนี้แปลว่า: โหลดไฟล์สำเร็จ และคำสั่งใน setUp() ทำงานได้ราบรื่น
หากเกิด Error สีแดง: ถ้า Error ระบุปัญหาเกี่ยวกับการเชื่อมต่อ Oracle แสดงว่าเครื่องอาจจะเชื่อมต่อกับ Database Server ไม่ได้ หรือ Credential ในไฟล์ .env ไม่ถูกต้อง (ซึ่งเป็นเรื่องของ Environment ไม่ใช่ Logic ของเทสเราพัง)
สิ่งที่เรายัง "ไม่ได้ทำ" ในบทนี้
เพื่อโฟกัสทีละเรื่อง ในบทนี้เรายังละเว้นสิ่งเหล่านี้ไว้ก่อน:
เพื่อโฟกัสทีละเรื่อง ในบทนี้เรายังละเว้นสิ่งเหล่านี้ไว้ก่อน:
- ยังไม่มีการยิง Request จริง (เช่น post('/stock/store'))
- ยังไม่มีการดึงข้อมูล Model ต่างๆ เช่น User หรือ Scheme มาใช้
- ยังไม่มีการเขียน Assert เพื่อตรวจสอบการ Redirect หรือเช็คข้อมูลใน Database
ในบทถัดไป เราจะมาเริ่มลงมือเขียน Action และเช็คผลลัพธ์ (Arrange → Act → Assert) กันครับ!
laravel