Testing Modules
ไอเดียในหนึ่งประโยค
หัวข้อที่มีชื่อว่า “ไอเดียในหนึ่งประโยค”terraform validate, terraform plan และ terraform test แต่ละตัวจับความผิดพลาดคนละประเภทกันโดยสิ้นเชิง การผ่านตัวใดตัวหนึ่งไม่ได้บอกอะไรเลยว่าตัวอื่นจะผ่านด้วย
สามระดับความมั่นใจที่แตกต่างกัน
หัวข้อที่มีชื่อว่า “สามระดับความมั่นใจที่แตกต่างกัน”น่าเผลอมองว่า validate, plan และ test เป็นวิธีตรวจสอบว่า module “ทำงานหรือไม่” ที่ใช้แทนกันได้ แต่จริง ๆ แล้วไม่ใช่ — แต่ละตัวคุยกับ reality คนละชั้น
terraform validate ตรวจแค่ syntax ของ HCL กับความสอดคล้องภายใน configuration เท่านั้น เช่น block เขียนถูกต้องหรือไม่ variable และ resource ที่อ้างอิงมีอยู่จริงหรือไม่ type ตรงกันหรือไม่ ที่สำคัญคือไม่คุยกับ AWS เลยแม้แต่นิดเดียว module หนึ่งอาจผ่าน validate แบบเนียน ๆ แล้วพังทันทีที่ไปแตะ AWS account จริง เพราะ validate ไม่มีทางรู้เลยว่า IAM permission ของคุณ, service quota ของ account หรือ AWS API เห็นด้วยกับสิ่งที่คุณเขียนหรือไม่
terraform plan ปิดช่องว่างนี้ด้วยการคุยกับ AWS API จริง จึงจับ error ฝั่ง provider ได้ — AMI ID พิมพ์ผิด, region ที่ resource type นั้นไม่รองรับ, permission ที่ credential ของคุณไม่มี — และแสดง diff ของสิ่งที่จะเปลี่ยน แต่ plan ไม่ assert อะไรเลยว่าผลลัพธ์นั้นถูกต้อง แต่แสดง diff ให้ดูอย่างสบายใจแม้ bucket จะตั้งชื่อผิด, subnet อยู่ผิด CIDR range หรือ security group เปิด port ที่ไม่ตั้งใจ ตราบใดที่ diff นั้น consistent ในตัวเอง
terraform test เป็นตัวเดียวในสามตัวที่ assert behavior ที่คาดหวังไว้จริง ๆ เป็น native testing framework ของ Terraform และเป็นตัวที่บอกได้จริงว่า module ทำสิ่งที่คุณตั้งใจไว้หรือไม่ ไม่ใช่แค่ Terraform parse หรือ apply ผ่าน
native test framework ของ Terraform
หัวข้อที่มีชื่อว่า “native test framework ของ Terraform”test อยู่ในไฟล์ .tftest.hcl และรันด้วย terraform test ไฟล์ test หนึ่งมี variables block สำหรับค่า input เฉพาะของ test และมี run block หนึ่งตัวหรือมากกว่า แต่ละตัวมี assert block ที่มี condition expression กับ error_message string ที่แสดงเมื่อ condition นั้นเป็นเท็จ
variables { bucket_name = "acme-app-logs-test"}
run "bucket_name_matches_input" { command = plan
assert { condition = aws_s3_bucket.this.bucket == var.bucket_name error_message = "Bucket name did not match the bucket_name input variable" }}test นี้เล็งไปที่ pattern ของ s3-bucket module จากบทก่อนหน้าในโมดูลนี้ โดยตั้ง bucket_name เป็น input เฉพาะของ test แล้ว assert ว่า attribute aws_s3_bucket.this.bucket ที่ได้ตรงกับค่าที่ส่งเข้ามาเป๊ะ ถ้ามีใครแก้ module แล้วเผลอ hardcode ชื่อ bucket หรือส่ง variable ผิดตัวเข้ามา assertion นี้จะ fail พร้อม message ที่ชี้ตรงไปที่ความผิดพลาด — สิ่งที่ validate กับ plan เพียงอย่างเดียวไม่มีทางจับได้เลย
command = plan เทียบกับ command = apply
หัวข้อที่มีชื่อว่า “command = plan เทียบกับ command = apply”ทุก run block มี command argument และการเลือกระหว่าง plan กับ apply คือ trade-off จริง ไม่ใช่แค่ความชอบส่วนตัว
command = plan เร็วและไม่สร้าง resource จริงเลย Terraform สร้าง plan แล้ว assertion ของคุณตรวจค่าที่วางแผนไว้ — เหมือนตัวอย่างข้างบนเป๊ะ นี่คือ default ที่ถูกต้องสำหรับ test case ส่วนใหญ่ เพราะ validate behavior ที่คาดหวังไว้ได้โดยไม่ต้องแตะ AWS account จริงเลย
command = apply สร้าง resource จริง assert บน attribute ที่เกิดขึ้นจริง แล้วรื้อทุกอย่างทิ้งอีกครั้งตอนจบไฟล์ test นี่คือ integration testing ตัวจริง เพราะจับปัญหาที่ plan จับไม่ได้ เช่น AWS API ปฏิเสธ combination ของ argument ที่ดูดีบนกระดาษ ต้นทุนคือช้ากว่าและสร้าง cloud resource จริงชั่วคราว ดังนั้นควรเก็บไว้ใช้กับ assertion ที่ต้องดู behavior จริงของ AWS โดยเฉพาะ
run "bucket_is_created_with_correct_name" { command = apply
assert { condition = aws_s3_bucket.this.bucket == var.bucket_name error_message = "Real bucket name in AWS did not match the bucket_name input variable" }}flowchart TB a["terraform validate: HCL syntax and internal consistency only, never talks to AWS"] --> b["terraform plan: talks to the real AWS API, catches provider-side errors, shows the diff"] b --> c["terraform test: asserts specific expected behavior via .tftest.hcl run blocks"]