Testing Modules
ไอเดียในหนึ่งประโยค
หัวข้อที่มีชื่อว่า “ไอเดียในหนึ่งประโยค”terraform validate, terraform plan และ terraform test แต่ละตัวตรวจสอบคนละเรื่องกันจริง ๆ — syntax, diff ของโลกจริง และ behavior ที่ถูก assert จริง — การผ่านตัวหนึ่งไม่ได้แปลว่าอีกตัวจะผ่านด้วย
สามระดับความมั่นใจที่ต่างกัน
หัวข้อที่มีชื่อว่า “สามระดับความมั่นใจที่ต่างกัน”น่าเผลอคิดว่า validate, plan และ test เป็นสามวิธีที่ถามคำถามเดียวกัน แต่จริง ๆ แล้วตรวจคนละเรื่องกัน และแต่ละตัวจับ mistake คนละแบบที่อีกสองตัวจับไม่ได้เลย
terraform validate ตรวจว่า HCL ของคุณ syntax ถูกต้องและ consistent กันภายใน — ว่า variable ทุกตัวที่ถูกอ้างอิงมีอยู่จริง type ตรงกัน argument ที่ required ครบ ที่สำคัญคือ validate ไม่เคยคุยกับ GCP เลย ไม่มี credential ไม่เรียก API ไหนเลย และบอกไม่ได้ว่า project_id เป็นของจริงหรือ account ของคุณมีสิทธิ์สร้าง google_storage_bucket หรือเปล่า
terraform validate# Success! The configuration is valid.terraform plan ไปไกลกว่านั้น โดย authenticate เข้า GCP API จริง อ่าน state ปัจจุบันของ resource ที่มีอยู่ และคำนวณ diff ว่าอะไรจะเปลี่ยน นี่จับ error กลุ่มที่ validate จับไม่ได้เลย — ค่า location ที่ API ปฏิเสธ, project_id ที่ credential ของคุณเข้าไม่ถึง, quota ที่เกิน แต่ plan ก็ยังไม่ได้ assert อะไรเลยว่าผลลัพธ์ถูกต้องตามที่คุณตั้งใจไว้ แค่โชว์ให้ดูว่าจะเกิดอะไรขึ้น
terraform plan# google_storage_bucket.this will be created# + name = "acme-app-logs"# + location = "US"terraform test เป็นตัวเดียวในสามตัวที่ assert behavior ที่คาดหวังไว้จริง ๆ — ว่าชื่อ bucket ตรงกับที่คุณส่งเข้าไปจริงไหม output ตัวหนึ่งมีค่าตรงกับที่คาดไว้จริงไหม การผ่าน validate หรือแม้แต่ plan ที่ clean บอกแค่ว่า configuration well-formed และ GCP ยอมรับ ไม่มีตัวไหนบอกว่า module ตัวนี้ทำงานตามที่คุณออกแบบไว้จริงหรือเปล่า
native test framework ของ Terraform
หัวข้อที่มีชื่อว่า “native test framework ของ Terraform”native test framework ของ Terraform ที่เปิดตัวใน Terraform 1.6 รันไฟล์ .tftest.hcl ผ่านคำสั่ง terraform test ไฟล์ test หนึ่งไฟล์มี variables block ระดับบนสุดสำหรับค่า input เฉพาะของ test และมี run block หนึ่งตัวขึ้นไป แต่ละตัวมี assert block หนึ่งตัวขึ้นไปที่มี condition expression กับ error_message ที่จะโชว์เมื่อ condition นั้นเป็นเท็จ
variables { bucket_name = "acme-app-logs-test" project_id = "acme-app-test"}
run "bucket_name_matches_input" { command = plan
assert { condition = google_storage_bucket.this.name == var.bucket_name error_message = "Bucket name did not match the bucket_name input variable" }}ตัวอย่างนี้ test module gcs-bucket จากก่อนหน้านี้ในโมดูลนี้ โดย assert ว่า resource ของ bucket ข้างใน module ได้ name ตรงกับที่ caller ส่งเข้ามาเป็น bucket_name เป๊ะ ไม่ใช่แค่ว่า configuration syntax ถูกต้อง หรือ GCP ยอมรับเท่านั้น
command = plan เทียบกับ command = apply
หัวข้อที่มีชื่อว่า “command = plan เทียบกับ command = apply”ทุก run block มี command argument และการเลือกระหว่างสองค่านั้นคือ tradeoff จริง ไม่ใช่แค่พิธีการ command = plan คำนวณ plan แล้ว assert กับค่าที่ plan ไว้ โดยไม่สร้าง resource จริงเลยแม้แต่ตัวเดียว วิธีนี้เร็ว ฟรี และรันได้ทุก commit อย่างปลอดภัย นี่คือเหตุผลที่ควรครอบคลุม test case ส่วนใหญ่ของคุณ
run "bucket_name_matches_input" { command = plan
assert { condition = google_storage_bucket.this.name == var.bucket_name error_message = "Bucket name did not match the bucket_name input variable" }}command = apply สร้าง resource จริงใน GCP จริง ๆ assert กับ attribute ที่เกิดขึ้นจริง แล้ว teardown ทุกอย่างทิ้งตอนท้ายไฟล์ test นี่คือ integration testing ของจริง — เพราะจับสิ่งที่ plan-only test จับไม่ได้ เช่น naming collision ที่ GCP API ปฏิเสธเฉพาะตอน apply เท่านั้น — แต่ช้ากว่าและเสียค่าใช้จ่าย cloud resource จริงชั่วครู่ ดังนั้นควรเลือกใช้อย่างตั้งใจ ไม่ใช่ใช้เป็น default
run "bucket_actually_created" { command = apply
assert { condition = google_storage_bucket.this.url != "" error_message = "Bucket was not actually created in GCP" }}flowchart LR v["terraform validate: HCL syntax and internal consistency only"] --> p["terraform plan: talks to the real GCP API, shows the diff"] --> t["terraform test: asserts specific expected behavior via .tftest.hcl"]