ข้าม​ไป​ยัง​เนื้อหา

Continuous Integration

รวม​และ​ทดสอบ​ทั้ง​ระบบ​อัตโนมัติ​ทุก​ครั้ง​ที่ code เปลี่ยน

Continuous Integration (CI) เป็น​แนว​ปฏิบัติ​จาก Extreme Programming (XP) ที่​รับประกัน​ว่า​ปัญหา​ของ​ทั้ง​ระบบ​จะ​ถูก​ตรวจ​พบ​ให้​เร็ว​ที่สุด​เท่า​ที่​จะ​เป็น​ไป​ได้​หลัง​จาก​มัน​ถูก​ใส่​เข้า​มา

มัน​หมาย​ถึง​การ build และ​ทดสอบ​ทั้ง​ระบบ​โดย​อัตโนมัติ (คือ​การ​รวม​ระบบ​เข้า​กับ​ทุก​สิ่ง​ที่​มัน​พึ่งพา) หลัง​การ​เปลี่ยนแปลง​ทุก​ครั้ง โดย​ทั่วไป​คือ​ทุก​ครั้ง​ที่​มี​การ commit เข้า​สู่ mainline ของ​ระบบ​ควบคุม version Continuous Integration คือ​การ​ผลัก​แนว​ปฏิบัติ “daily build” — ซึ่ง​ครั้ง​หนึ่ง​เคย​ถูก​ยก​ให้​เป็น best practice ที่​ควร​มุ่ง​ไป​ให้​ถึง — ให้​สุด​ขั้ว จาก​ที่​เคย build วัน​ละ​ครั้ง กลาย​เป็น build ทุก​ครั้ง​ที่​มี​การ​เปลี่ยนแปลง​เข้า​มา

ผลลัพธ์​คือ feedback loop ที่​สั้น​ที่สุด​เท่า​ที่​จะ​เป็น​ไป​ได้ ระหว่าง​เวลา​ที่​ปัญหา​ถูก​ใส่​เข้า​มา​ใน​ระบบ กับ​เวลา​ที่​มัน​ถูก​ตรวจ​พบ​โดย​อัตโนมัติ Martin Fowler อธิบาย​ว่า​เหตุผล​ที่ CI ทรง​พลัง​มา​จาก​ธรรมชาติ​ของ​งาน integration เอง — ความ​ยาก​ใน​การ merge code สอง​ก้อน​เข้า​ด้วย​กัน​ไม่​ได้​เพิ่ม​แบบ​เชิง​เส้น​ตาม​ขนาด​ของ​การ​เปลี่ยนแปลง แต่​เพิ่ม​แบบ​ไม่​เป็น​สัดส่วน (non-linear) ยิ่ง​ปล่อย​ให้ code แยก​กัน​นาน​เท่าไร การ​รวม​กลับ​เข้า​มา​ก็​ยิ่ง​เจ็บ​ปวด​มาก​ขึ้น​เท่านั้น การ integrate บ่อย ๆ ใน​ปริมาณ​เล็ก ๆ จึง​ถูก​กว่า​การ integrate นาน ๆ ครั้ง​ใน​ปริมาณ​ใหญ่​เสมอ

ทำงาน​ร่วม​กับ Fail Fast: CI คือ​การนำ​หลัก Fail Fast มา​ใช้​ใน​ระดับ​ทั้ง​ระบบ ยิ่ง​พบ​ข้อ​ผิดพลาด​ใกล้​เวลา​ที่​มัน​เกิด​มาก​เท่าไร ก็​ยิ่ง​หา​สาเหตุ​และ​แก้​ได้​ถูก​และ​ง่าย​ขึ้น​เท่านั้น เพราะ​บริบท (context) ของ​การ​เปลี่ยนแปลง​ยัง​สด​ใหม่​อยู่​ใน​หัว​ของ​ผู้​เขียน code

สิ่ง​สำคัญ​ที่​ต้อง​แยก​ให้​ออก​คือ CI เป็น​เรื่อง​ของ กระบวนการ​รวม code และ​ตรวจสอบ​คุณภาพ ไม่ใช่ Continuous Delivery (CD) ที่​พูด​ถึง​การ​ทำให้​ระบบ​พร้อม release ได้​ตลอด​เวลา หรือ Continuous Deployment ที่​ปล่อย​ขึ้น production อัตโนมัติ — CI คือ​รากฐาน​ที่​ทำให้ CD และ Continuous Deployment เป็น​ไป​ได้ แต่​ทั้ง​สาม​เป็น​คนละ​ขั้นตอน​กัน

การนำ CI มา​ใช้​จริง​ต้อง​อาศัย​หลาย​องค์​ประกอบ​ทำงาน​ร่วม​กัน (สรุป​จาก​แนวทาง​ของ Fowler และ​แนว​ปฏิบัติ​ที่​ยอมรับ​กัน​ทั่วไป):

  1. เก็บ​ทุก​อย่าง​ไว้​ใน version control เดียว — source code script build file config schema ของ​ฐาน​ข้อมูล ต้อง​อยู่​ใน repository เดียวกัน เพื่อ​ให้​ใคร​ก็ตาม​สามารถ build ระบบ​ทั้งหมด​จาก​เครื่อง​เปล่า ๆ ได้
  2. ทำ build ให้​เป็น​อัตโนมัติ​ด้วย​คำ​สั่ง​เดียว — ไม่​ว่า​จะ​เป็น dotnet build, MSBuild, หรือ build tool อื่น ต้อง​ไม่มี​ขั้นตอน​ที่​ต้อง​ทำ​มือ​แทรก​อยู่
  3. ทำให้ build ทดสอบ​ตัวเอง​ได้ (self-testing) — build ต้อง​รวม​การ​รัน automated test เข้าไป​ด้วย ไม่ใช่​แค่ compile ผ่าน​แล้ว​จบ
  4. ทุก​คน commit เข้า mainline อย่าง​น้อย​วัน​ละ​ครั้ง — Kent Beck เคย​กล่าว​ไว้​ว่า​ไม่​ควร​มี code ที่​ยัง​ไม่​ถูก integrate ค้าง​อยู่​นาน​เกิน​สอง​สาม​ชั่วโมง
  5. ทุก commit ต้อง build บน​เครื่อง integration ที่​แยก​จาก​เครื่อง dev — เพื่อ​ตัด​ปัญหา “มัน​รัน​ได้​บน​เครื่อง​ผม” ออก​ไป โดย​ทั่วไป​ใช้ CI server เช่น GitHub Actions, Azure Pipelines, หรือ Jenkins คอย​เฝ้า​ดู mainline แล้ว trigger build อัตโนมัติ
  6. แก้ build ที่​พัง​ให้​เร็ว​ที่สุด — เมื่อ build แดง ไม่มี​งาน​ไหน​สำคัญ​กว่า​การ​ทำให้​มัน​เขียว​อีก​ครั้ง บ่อย​ครั้ง​วิธี​ที่​เร็ว​ที่สุด​คือ revert commit ที่​ทำให้​พัง แล้ว​ค่อย​แก้​ทีหลัง
  7. รักษา​ความเร็ว​ของ build ให้​สั้น — เป้าหมาย​ทั่วไป​คือ build ที่ commit ต้อง​เสร็จ​ใน​เวลา​ประมาณ​สิบ​นาที หาก​มี test ชุด​ใหญ่​ที่​ช้า ให้​แยก​ไป​รัน​ใน pipeline ขั้น​ถัด​ไป (deployment pipeline แบบ​มี​หลาย​ขั้น)
  8. ทดสอบ​ใน​สภาพ​แวดล้อม​ที่​ใกล้​เคียง production — เพื่อ​ลด​โอกาส​ที่​ปัญหา​จะ​โผล่​มา​เฉพาะ​ตอน deploy จริง
  9. ทำให้​ทุก​คน​เห็น​สถานะ​ของ build ได้​ง่าย — dashboard การ​แจ้ง​เตือน​ใน chat หรือ badge ใน README ช่วย​ให้​ทีม​รู้ทันที​เมื่อ mainline พัง

CI ไม่​ได้​จำกัด​อยู่​แค่​การ compile — มัน​ควร​ครอบคลุม​การ​รัน static analysis, linter, security scan และ automated test ใน​ระดับ​ต่าง ๆ (unit, integration) เพื่อ​ให้ “เขียว” หมาย​ถึง​คุณภาพ​ใน​หลาย​มิติ ไม่ใช่​แค่ compile ผ่าน

flowchart LR
    Dev[Developer commits code] --> Repo[Push to mainline]
    Repo --> Trigger[CI server detects change]
    Trigger --> Build[Automated build]
    Build --> Test[Automated tests run]
    Test -->|pass| Green[Build green feedback to team]
    Test -->|fail| Red[Build red fix immediately]
    Red --> Dev

ตัวอย่าง GitHub Actions workflow ง่าย ๆ ที่​ทำ CI สำหรับ project .NET — ทุก​ครั้ง​ที่​มี push หรือ​เปิด pull request เข้า main จะ build และ​รัน test อัตโนมัติ:

name: CI
on:
push:
branches: [main]
pull_request:
branches: [main]
jobs:
build-and-test:
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v4
- name: ตั้งค่า .NET SDK
uses: actions/setup-dotnet@v4
with:
dotnet-version: "8.0.x"
- name: คืนค่า dependency
run: dotnet restore
- name: Build project
run: dotnet build --no-restore --configuration Release
- name: รัน automated test ทั้งหมด
run: dotnet test --no-build --configuration Release --logger trx

ฝั่ง code C# เอง ก็​ควร​เขียน​ให้ “self-testing” ได้​ตั้งแต่​ต้น เช่น unit test ที่ CI จะ​รัน​ทุก​ครั้ง:

public class OrderTotalCalculatorTests
{
[Fact]
public void CalculateTotal_WithTwoLineItems_ReturnsSumOfLinePrices()
{
// Arrange: จำลองคำสั่งซื้อสองรายการ
var calculator = new OrderTotalCalculator();
var order = new Order(new List<OrderLine>
{
new OrderLine(unitPrice: 100m, quantity: 2), // 200
new OrderLine(unitPrice: 50m, quantity: 1) // 50
});
// Act
decimal total = calculator.CalculateTotal(order);
// Assert: ผลรวมต้องถูกต้อง — ถ้าใครแก้ logic แล้วพลาด CI จะจับได้ทันที
Assert.Equal(250m, total);
}
}

เมื่อ CI server รันทดสอบ​ทุก commit ทีม​จะ​รู้ทันที​ถ้า​ใคร​ทำ regression เข้า​มา แทนที่​จะ​ไป​เจอ​ตอน integrate ใหญ่​ก่อน release ซึ่ง​สาย​เกิน​ไป​และ​แก้​ยาก​กว่า​มาก

ประโยชน์

  • ลด​ความ​เสี่ยง​ตอน release — เพราะ mainline ถูก​ทดสอบ​และ integrate อยู่​ตลอด​เวลา ไม่มี “ช่วง integration” ก้อน​ใหญ่​ที่​น่า​กลัว​ก่อน​ส่ง​มอบ
  • ลด​ต้นทุน​ของ​การ integrate — เนื่องจาก​ความ​ยาก​ของ​การ merge เพิ่ม​แบบ​ไม่​เป็น​เชิง​เส้น​ตาม​ขนาด​การ​เปลี่ยนแปลง การ​รวม code ที​ละ​น้อย​บ่อย ๆ จึง​ถูก​กว่า​การ​รวม​ก้อน​ใหญ่​นาน ๆ ครั้ง​เสมอ
  • พบ defect เร็ว​ขึ้น​และ​แก้​ถูก​กว่า — ตรง​กับ​หลัก Fail Fast ยิ่ง bug ถูก​จับ​ใกล้​จุด​ที่​มัน​เกิด ยิ่ง​แก้​ง่าย​และ​ถูก
  • สนับสนุน​การ refactor อย่าง​ต่อ​เนื่อง — เมื่อ​ไม่มี branch ที่​แยก​ออก​ไป​นาน ๆ ทีม​กล้า​ปรับ​โครงสร้าง code โดย​ไม่​กลัว merge conflict มหาศาล
  • ทำให้การ​ตัดสิน​ใจ release เป็น​เรื่อง​ธุรกิจ ไม่ใช่​ข้อ​จำกัด​ทาง​เทคนิค — เมื่อ mainline พร้อม deploy อยู่​เสมอ ทีม​เลือก​ได้​เอง​ว่า​จะ​ปล่อย​เมื่อไร

ข้อ​ควร​ระวัง

  • CI ไม่​ทำงาน​ถ้า​ไม่มี​ชุด​ทดสอบ​อัตโนมัติ​ที่​น่า​เชื่อถือ — build ที่ “เขียว” ทั้ง​ที่​ไม่มี test ครอบคลุม​เพียงพอ ให้​ความ​มั่นใจ​ปลอม (false confidence)
  • ต้อง​มี​ทีม​ที่​ทำงาน​ร่วม​กัน​สม่ำเสมอ — Fowler ชี้​ว่า CI เหมาะ​กับ​ทีม​ที่ commit เข้า mainline เป็น​ประจำ ไม่​เหมาะ​กับ​โครงการ​ที่ contributor เข้า​มา​แบบ​ห่าง ๆ
  • build ที่​ช้า​ทำลาย​วินัย​ของ​ทีม — ถ้า build ใช้​เวลา​นาน​เกิน​ไป คน​จะ​เริ่ม​เลี่ยง​การ commit บ่อย ๆ ซึ่ง​ย้อน​กลับ​ไป​สู่​ปัญหา​เดิม​ที่ CI พยายาม​แก้
  • build พัง​แล้ว​ปล่อย​ทิ้ง​ไว้ = CI ตาย​ทั้ง​ระบบ — ถ้า​ไม่มี​วินัย​ว่า “ไม่มี​งาน​ไหน​สำคัญ​กว่า​การ​แก้ build แดง” สถานะ build จะ​กลาย​เป็น​สิ่ง​ที่​ทีม​เพิกเฉย และ​สูญเสีย​คุณค่า​ของ CI ไป​ทั้งหมด
  • ไม่ใช่ Continuous Delivery หรือ Continuous Deployment — CI ทำให้ code build และ​ทดสอบ​ผ่าน​อัตโนมัติ​เท่านั้น การ​ส่ง​ขึ้น production อัตโนมัติ​เป็น​อีก​ขั้น​ที่​ต้อง​ออกแบบ pipeline เพิ่มเติม (feature flag, deployment pipeline แบบ​หลาย​ขั้น) ต่อยอด​จาก​รากฐาน CI