ยกเลิกการ Manual Test บน Shared Staging DB ที่ข้อมูลตีกันจนพัง! ยกระดับความมั่นใจใน Stored Units (UDFs, Procedures, Triggers) สู่สถาปัตยกรรม Shift-Left Automated Quality Gate เขียน Unit Test ในภาษา SQL ด้วย pgTAP ควบคู่กับการสปินอัป Ephemeral Database บน CI/CD ด้วย Testcontainers การันตีความสะอาด 100% ด้วย Transactional Isolation (ROLLBACK) ไร้ Regression Bugs ก่อนถึง Production!
โค้ดฐานข้อมูลเข้า Git แล้ว คำถามถัดไปคือจะรู้ได้อย่างไรว่า Pull Request วันนี้ไม่ทำลาย logic เดิม — วันนี้คือการทดสอบอัตโนมัติบนฐานข้อมูลชั่วคราวที่สร้างขึ้นมาแล้วทิ้ง
การเขียนโปรแกรมทดสอบให้อัตโนมัติ เพื่อเช็คความถูกต้องของ business logic โดยไม่ต้องให้คนมานั่งกดทดสอบด้วยมือ
เครื่องมือเขียน unit test ให้ stored code ใน PostgreSQL ด้วยภาษา SQL / PL/pgSQL
เครื่องมือที่สปินอัปตู้คอนเทนเนอร์ฐานข้อมูลชั่วคราวขึ้นมารันเทสบน CI/CD แล้วลบทิ้งทันทีเมื่อรันเสร็จ เพื่อความสะอาดร้อยเปอร์เซ็นต์
โดยรวมวันนี้คือระบบทดสอบที่สร้างฐานข้อมูลชั่วคราวขึ้นมา รัน Flyway migration แล้วยิง pgTAP ตรวจ stored units ทุกตัว ก่อนสั่ง rollback และลบฐานข้อมูลทิ้ง
ช่วยดักบั๊กตั้งแต่ตอนเปิด Pull Request (shift-left testing) การันตีว่าการแก้โค้ดวันนี้จะไม่ไปทำลายการทำงานเดิมที่มีอยู่
หลังจากที่เราได้นำ Stored Units เข้าสู่ Version Control ด้วย Flyway (Day 11) คำถามสำคัญระดับ Enterprise ที่สถาปนิกและหัวหน้าทีมพัฒนามักตั้งคำถามเสมอคือ "เมื่อมีนักพัฒนา 30 คนส่ง Pull Request เข้ามาแก้ไข Stored Procedures และ Triggers เราจะการันตีได้อย่างไรว่าโค้ดใหม่จะไม่ไปทำลาย Business Logic เดิมที่มีอยู่?"
องค์กรด้านการชำระเงินดิจิทัลแห่งหนึ่ง มี Stored Procedures และ Triggers คำนวณค่าธรรมเนียมและสิทธิประโยชน์ลูกค้ารวมกันกว่า 300 โปรแกรมยูนิต ทุกครั้งที่มีการเปลี่ยน Business Logic ทีม QA ต้องใช้เวลา Manual Test นานกว่า 3 วัน โดยการยิง API ผ่าน Postman เข้าไปยัง Shared Staging Database:
ต้นตอของวิกฤตนี้คือ "การขาด Automated Unit Test ในระดับ Database Logic" ทางออกเดียวในระดับ Enterprise คือการย้ายการทดสอบมาอยู่ใน CI/CD แบบ Shift-Left Testing ด้วย pgTAP และ Testcontainers ครับ!
| มิติเปรียบเทียบสถาปัตยกรรม | แบบเดิม (Shared Staging DB Manual Test) | แบบใหม่ (Automated pgTAP + Testcontainers) |
|---|---|---|
| สภาพแวดล้อมฐานข้อมูล (Environment) | Shared Staging DB (ฐานข้อมูลตัวเดียว ใช้ร่วมกันทั้งทีม) | Ephemeral Container (สปินอัปใหม่และทำลายทิ้งอัตโนมัติ) |
| ระดับการทดสอบ (Testing Scope) | Black-box ยิง API ผ่าน Postman (ไม่เห็น Logic ข้างใน) | White-box Unit Testing ทดสอบ Stored Units ถึงระดับฟังก์ชัน |
| ความบริสุทธิ์ของข้อมูล (Data Isolation) | เกิด Data Pollution ขยะข้อมูลสะสมจนเกิด Flaky Tests | 100% Clean State ด้วย BEGIN; ... ROLLBACK; |
| ระยะเวลาในการรัน Test (Feedback Loop) | 3 วัน (QA ต้องไล่ทดสอบด้วยมือทีละหน้าจอ) | 5-10 วินาที (รันอัตโนมัติบน Pull Request CI/CD Pipeline) |
| การป้องกัน Regression Bugs | ต่ำมาก (บั๊ก Edge Cases หลุดรอดไปถึง Production เสมอ) | เกราะป้องกัน 100% (Quality Gate) ห้าม Merge ถ้าเทสต์ไม่ผ่าน |
"ทุกท่านครับ การทดสอบ Stored Procedure ด้วยการต่อ API ยิงเข้า Staging DB คือการทำงานแบบสะเปะสะปะที่สร้างปัญหา Data Pollution และล้มเหลวในการดักจับ Edge Cases!
ด้วย Day 12 Architecture เราจะเปลี่ยนมาใช้ pgTAP เขียน Unit Test ระดับ Database Logic ควบคู่กับ Testcontainers บน CI/CD Pipeline ทุกครั้งที่มี Pull Request ระบบจะสร้าง Ephemeral PostgreSQL ขึ้นมารัน Flyway แล้วยิง pgTAP ตรวจสอบความถูกต้องกว่า 100 เคสใน 5 วินาที พร้อม Rollback ข้อมูลทิ้ง 100%!
ผลลัพธ์คือการขจัด Regression Bugs ก่อนถึง Production ได้ 100% ลดเวลาการทดสอบจาก 3 วันเหลือเพียง 10 วินาที และการันตีว่า Stored Code ของเรามีความแม่นยำระดับ Mission-Critical ครับ"
ลองเลือกสถานการณ์การทดสอบโค้ดและสภาพแวดล้อม เพื่อดูว่าระบบ Automated Quality Gate จะตรวจจับข้อผิดพลาดและปกป้อง Production ได้อย่างไร
การเขียน Database Unit Test ด้วย pgTAP ไม่ต่างจากการเขียน Unit Test ในภาษาอย่าง JUnit, Jest หรือ pytest แต่ทำในภาษา SQL ทั้งหมด โดยมีโครงสร้างหลัก 4 ส่วน: เริ่มด้วย BEGIN;, วางแผนด้วย plan(N);, ตรวจสอบด้วย Assertion Functions (เช่น is(), results_eq()), และจบด้วย ROLLBACK;:
-- 🎯 [ส่วนที่ 1: Transaction Isolation & Extension Setup]
BEGIN;
CREATE EXTENSION IF NOT EXISTS pgtap;
-- 🎯 [ส่วนที่ 2: วางแผนจำนวนการทดสอบที่ต้องรัน]
SELECT plan(3); -- ระบุชัดเจนว่าต้องมี Assertion 3 ข้อ หากรันไม่ครบจะถือว่า Fail ทันที!
-- 🎯 [ส่วนที่ 3: Assertion Functions ตรวจสอบผลลัพธ์]
-- ข้อ 1: ตรวจสอบความถูกต้องของ Schema
SELECT has_table('accounts', 'ตาราง accounts ต้องมีอยู่ในระบบ');
-- ข้อ 2: ตรวจสอบค่าผลลัพธ์ของฟังก์ชัน (Actual vs Expected)
SELECT is(
fn_calculate_discount(1000.00, 'GOLD'), -- Actual Value ที่ฟังก์ชันคำนวณได้
150.00, -- Expected Value ที่เราคาดหวัง
'ส่วนลดระดับ GOLD สำหรับยอด 1,000 บาท ต้องเท่ากับ 150 บาท' -- คำอธิบาย Test Case
);
-- ข้อ 3: ตรวจสอบผลลัพธ์จาก Query ชุดข้อมูล
SELECT results_eq(
'SELECT status FROM accounts WHERE account_id = 1', -- Actual Query
'VALUES (''ACTIVE''::varchar)', -- Expected Query / Values
'สถานะของบัญชีหมายเลข 1 ต้องเป็น ACTIVE'
);
-- 🎯 [ส่วนที่ 4: สรุปผลการทดสอบ และล้างข้อมูล 100%]
SELECT * FROM finish();
ROLLBACK; -- 💡 กฎเหล็ก: สั่ง ROLLBACK เสมอเพื่อไม่ให้มีข้อมูลทดสอบตกค้างในฐานข้อมูล!
SELECT * FROM finish(); และตามด้วย ROLLBACK; คือหัวใจของ Transactional Test Isolation ที่รับประกันว่าการทดสอบจะไม่มีผลกระทบต่อ State ของฐานข้อมูลเลยแม้แต่น้อย
-- test/01_schema_test.sql
-- 🎯 ทดสอบความสมบูรณ์ของโครงสร้างตาราง คอลัมน์ และ Stored Units หลังรัน Flyway Migration
BEGIN;
CREATE EXTENSION IF NOT EXISTS pgtap;
SELECT plan(3);
-- [1] ตรวจสอบว่าตาราง orders ถูกสร้างขึ้นเรียบร้อยแล้วหรือไม่
SELECT has_table('orders', 'ตาราง orders ต้องถูกสร้างขึ้น');
-- [2] ตรวจสอบว่าคอลัมน์ net_amount มีอยู่ในตาราง orders หรือไม่
SELECT has_column('orders', 'net_amount', 'ตาราง orders ต้องมีคอลัมน์ net_amount');
-- [3] ตรวจสอบว่า Stored Procedure pr_transfer_funds_enterprise มีอยู่จริงหรือไม่
SELECT has_function('pr_transfer_funds_enterprise', 'ต้องมี Stored Procedure pr_transfer_funds_enterprise');
SELECT * FROM finish();
ROLLBACK;
-- test/02_business_logic_test.sql
-- 🎯 ทดสอบความถูกต้องของ Business Logic และ Transactional Changes
BEGIN;
CREATE EXTENSION IF NOT EXISTS pgtap;
SELECT plan(2);
-- [1] เตรียมข้อมูลจำลองสำหรับทดสอบ (Dummy Test Data) ใน Transaction
INSERT INTO accounts (account_id, account_no, balance, status)
VALUES (999, 'ACC-TEST-01', 5000.00, 'ACTIVE');
-- [2] Test 1: ทดสอบ Stored Function ดึงยอดเงินด้วย is()
SELECT is(
fn_get_account_balance(999),
5000.00,
'ฟังก์ชัน fn_get_account_balance ต้องคืนค่ายอดเงิน 5000.00 ได้ถูกต้อง'
);
-- [3] Test 2: ทดสอบการทำงานของ Stored Procedure ฝากเงิน แล้วตรวจสอบผลลัพธ์ด้วย results_eq()
CALL pr_deposit_funds(999, 1500.00);
SELECT results_eq(
'SELECT balance FROM accounts WHERE account_id = 999',
'VALUES (6500.00::numeric)',
'ยอดเงินหลังฝากเพิ่ม 1500 ต้องกลายเป็น 6500.00'
);
SELECT * FROM finish();
-- 🎯 [4] สั่ง ROLLBACK: ข้อมูลจำลอง ID 999 และประวัติเงินฝากจะถูกทำลายทิ้งทันที ไร้ Data Pollution!
ROLLBACK;
ROLLBACK; ตอนท้ายจะยกเลิกการ INSERT บัญชี 999 ทิ้งทั้งหมด ทำให้เราสามารถรันเทสต์นี้ซ้ำอีกพันครั้งโดยไม่เกิดปัญหาคีย์ซ้ำ (Duplicate Key Error)
เปรียบเทียบกระบวนการเดิมที่รัน Manual Test บน Shared Staging DB กับกระบวนการใหม่ที่ใช้ Testcontainers สปินอัป Ephemeral DB อัตโนมัติบน CI/CD Pipeline:
# ❌ Flawed Process: ใช้ Staging DB ร่วมกันทั้งทีม
# 1. นักพัฒนาต่อ DBeaver เข้า staging-db.internal
# 2. แก้ข้อมูลในตารางทดสอบด้วยมือ ➔ คนอื่นรันเทสต์พังทันที (Data Pollution)
# 3. QA ต้องไล่คลิก Postman ทดสอบทีละ API นาน 3 วัน
# 4. ไม่มีการเช็ค Edge Cases เมื่อนำขึ้น Prod คำนวณเงินติดลบ เสียหาย 5 ล้านบาท!
# คำสั่งเดิมที่ใช้รัน:
psql -h staging-db.internal -U appuser -d staging_db -f test_script.sql
# ผลลัพธ์: ขยะข้อมูลตกค้างสะสมจนกลายเป็น Flaky Database!
# test_runner.py (สคริปต์รันอัตโนมัติบน GitHub Actions / GitLab CI)
# 🎯 สปินอัป Ephemeral PostgreSQL Container ชั่วคราว รัน Flyway แล้วยิง pgTAP
import os
from testcontainers.postgres import PostgresContainer
import psycopg2
# [Statement 1] สปินอัป Ephemeral Database Container สะอาดเอี่ยม 100%
with PostgresContainer("postgres:16-alpine") as postgres:
db_url = postgres.get_connection_url()
# [Statement 2] รัน Flyway Migration ปูโครงสร้าง Schema และ Stored Units
print("🚀 Running Flyway Migrations on Ephemeral Container...")
os.system(f"flyway -url={db_url} -user={postgres.username} -password={postgres.password} migrate")
# [Statement 3] เชื่อมต่อและรัน pgTAP Automated Test Suite
conn = psycopg2.connect(
dbname=postgres.dbname,
user=postgres.username,
password=postgres.password,
host=postgres.get_container_host_ip(),
port=postgres.get_exposed_port(5432)
)
cur = conn.cursor()
print("🧪 Executing pgTAP Automated Test Suite...")
cur.execute(open("test/02_business_logic_test.sql", "r").read())
results = cur.fetchall()
for row in results:
print(row)
conn.close()
# 🎯 เมื่อพ้นบล็อก with คอนเทนเนอร์ Docker จะถูกทำลายทิ้งทันที ไร้ขยะตกค้าง 100%!
แบบทดสอบวัดระดับความเข้าใจ Day 12: Automated Testing with Testcontainers & pgTAP (pgTAP Assertions, Ephemeral Database Containers, Transactional Test Isolation, Shift-Left Testing) และข้อสอบสะสมทบทวนครบทั้ง Days 1–11 คลิกเลือกคำตอบ ก, ข, ค, หรือ ง เพื่อดูผลลัพธ์และคำอธิบายอย่างละเอียดทันที
บทเรียน Day 12: Automated Testing with Testcontainers & pgTAP ได้เปลี่ยนผ่านกระบวนการตรวจสอบคุณภาพของ Stored Code จากการ Manual Test บน Shared Staging DB สู่กระบวนการ Shift-Left Automated Quality Gate บน CI/CD Pipeline อย่างสมบูรณ์แบบ การเขียน Unit Test ในภาษา SQL ด้วย pgTAP ร่วมกับการครอบด้วย BEGIN...ROLLBACK และการใช้ Testcontainers ช่วยให้คุณการันตีความถูกต้องของโค้ดฐานข้อมูลระดับล้านธุรกรรมได้อย่างมั่นใจ 100%
ในวันถัดไป เราจะไปเจาะลึกการกำหนดขอบเขตสถาปัตยกรรม (Architectural Boundaries) ระหว่าง Application Layer และ Database Layer ผ่าน Decision Matrix เพื่อตอบคำถามเชิงกลยุทธ์ว่า ตรรกะใดควรนำมาประมวลผล In-Database และตรรกะใดควรผลักออกไปรันบน App Layer เพื่อป้องกันปัญหา Database CPU Bottleneck ครับ!