Go testing/fstest.MapFS: เทสต์สัญญาไฟล์โดยไม่ต้องสร้าง temp directory
ฟังก์ชันที่รับ fs.FS เทสต์ง่ายกว่าฟังก์ชันที่เปิดไฟล์จากดิสก์เอง เพราะเราส่ง filesystem เล็กๆ ที่อยู่ในหน่วยความจำเข้าไปได้ fstest.MapFS เป็นตัวเลือกมาตรฐานสำหรับ contract test แบบนี้: key คือ path ที่ใช้ slash และ value คือข้อมูลไฟล์
ประโยชน์จริงไม่ใช่แค่เทสต์เร็ว แต่คือ fixture บอกเจตนาชัด เราเห็นทันทีว่าไฟล์ไหนมีอยู่ เนื้อหาอะไร และเคส “หาไม่เจอ” เกิดจากอะไร โดยไม่ต้องมี setup/cleanup ของ temp directory
package example
import (
"io/fs"
"testing"
"testing/fstest"
)
func readFlag(fsys fs.FS) (string, error) {
b, err := fs.ReadFile(fsys, "config/app.txt")
return string(b), err
}
func TestReadFlag(t *testing.T) {
mem := fstest.MapFS{
"config/app.txt": {Data: []byte("safe=true\n"), Mode: 0o444},
}
got, err := readFlag(mem)
if err != nil {
t.Fatal(err)
}
if got != "safe=true\n" {
t.Fatalf("got %q", got)
}
if _, err := fs.ReadFile(mem, "config/missing.txt"); err == nil {
t.Fatal("expected missing-file error")
}
}
ขอบที่ต้องรู้
MapFS จะสร้าง parent directory ที่ขาดให้เสมือนว่ามีอยู่ จึงเหมาะกับ fixture สั้นๆ แต่ไม่ใช่ตัวจำลอง filesystem ทุกพฤติกรรม เอกสารระบุว่าไม่ได้ออกแบบให้แก้ map พร้อมกันระหว่าง goroutine และ directory operation ต้องไล่ map ทั้งก้อน จึงควรใช้กับชุดข้อมูลหลักร้อยรายการ ไม่ใช่ benchmark ต้นไม้ไฟล์ขนาดใหญ่
ถ้าต้องเทสต์ permission ของ OS, symlink ที่ผูกกับเครื่อง หรือ atomic rename ให้กลับไปใช้ t.TempDir() เพราะโจทย์นั้นต้องการ semantics ของ filesystem จริง แต่ถ้าต้องการยืนยันว่า parser อ่าน path และเนื้อหาตามสัญญา MapFS ทำให้ขอบเขตเทสต์เล็กและชัดกว่า
Recap
- ออกแบบ production function ให้รับ
fs.FS - ใช้ path แบบ slash และอย่าใส่ leading slash
- อย่า mutate
MapFSพร้อมกัน - ใช้
t.TempDir()เมื่อเทสต์ต้องพึ่ง behavior ของ OS จริง