เครื่องมือปรับปรุงการทดสอบหน่วยอัตโนมัติของเมตา: TestGen-LLM
- TestGen-LLM ที่เมตาพัฒนาขึ้นใช้โมเดลภาษาใหญ่ (LLMs) เพื่อปรับปรุงเทสต์ที่มนุษย์เขียนไว้เดิมอย่างอัตโนมัติ
- การทดสอบที่สร้างโดย TestGen-LLM ผ่านชุดตัวกรองที่รับประกันการปรับปรุงแบบวัดผลได้เหนือชุดเทสต์ดั้งเดิมได้สำเร็จ และแก้ปัญหา LLM hallucination
- รายงานการนำ TestGen-LLM ไปใช้ใน test-a-thons สำหรับแพลตฟอร์ม Instagram และ Facebook ของเมตา
ประสิทธิภาพของ TestGen-LLM
- ในการประเมินบน Instagram Reels และ Stories ของ Instagram, เทสต์เคสของ TestGen-LLM 75% สร้างขึ้นได้สำเร็จ, 57% ผ่านได้อย่างน่าเชื่อถือ, และ 25% ช่วยเพิ่มความครอบคลุม
- ใน test-a-thons ของ Instagram และ Facebook ของเมตา, TestGen-LLM ปรับปรุง 11.5% ของคลาสที่ใช้ทั้งหมด และวิศวกรซอฟต์แวร์ของเมตายอมรับข้อเสนอแนะ 73% เพื่อนำไปใช้งานจริง
- นี่คือรายงานครั้งแรกที่มีการนำโค้ดที่สร้างจาก LLM ไปใช้งานในระดับอุตสาหกรรม และได้รับการรับรองการปรับปรุงโค้ดเช่นนี้
ความคิดเห็นของ GN⁺
- TestGen-LLM เป็นเครื่องมือที่มีศักยภาพในการนำการทดสอบซอฟต์แวร์ไปสู่การทำงานอัตโนมัติและคุณภาพที่ดีขึ้น โดยใช้ LLM เพื่อปรับปรุงเทสต์ที่มีอยู่เดิมได้สำเร็จ
- เครื่องมือนี้มีส่วนช่วยเพิ่ม test coverage ในสภาพแวดล้อมอุตสาหกรรมจริง และสร้างเทสต์เคสที่เชื่อถือได้ จึงเป็นประโยชน์สำคัญต่อชุมชนวิศวกรรมซอฟต์แวร์
- ตัวอย่างการนำไปใช้ที่ประสบความสำเร็จใน test-a-thons ของเมตาแสดงให้เห็นว่า TestGen-LLM สามารถบูรณาการเข้ากับการพัฒนาผลิตภัณฑ์จริงได้ และเป็นความก้าวหน้าที่สำคัญในการยกระดับประสิทธิภาพและความเสถียรของการพัฒนาซอฟต์แวร์
1 ความคิดเห็น
ความคิดเห็นบน Hacker News
ที่บริษัทประกันรายใหญ่แห่งหนึ่งที่เคยทำงาน ผู้บริหารตั้งเป้าให้โค้ดเบสทั้งหมดมี test coverage 80% ผู้คนเลยเริ่มเขียน unit test ที่ไม่มีประโยชน์ให้กับ getter/setter ของ Java DTO เพื่อให้ถึงเป้า
แน่นอนว่านักพัฒนาก็เปลี่ยนกฎการวัด coverage ของ Sonar ไม่ได้ และตอนยังเป็นนักพัฒนาอายุน้อยก็ได้เรียนรู้ว่า ถ้ามองแต่ KPI อย่างเดียว มันอาจชักนำให้เกิดพฤติกรรมที่ไม่ตรงกับเจตนาเดิมได้
มีความเป็นไปได้สูงว่า สถานการณ์ทดสอบ E2E ที่ออกแบบมาดีไม่กี่ชุดจะดีกว่าต่อคุณภาพซอฟต์แวร์
ปัญหาคือโค้ดเดิมที่ยุ่งเหยิงนั้นถูกทดสอบไว้อย่างดีมากด้วย coverage 95% โค้ดใหม่มี coverage 100% แต่เพราะมันสั้นลงมาก ทำให้ coverage รวมกลับลดลงและไม่ผ่าน
โค้ดที่เหลืออยู่มีแค่โค้ด Swing UI ที่ทดสอบยากและการทดสอบก็แทบไม่มีความหมาย ดังนั้นแทนที่หัวหน้าทีมพัฒนาจะใช้เวลา 1–2 สัปดาห์เขียนเทสต์ Swing เขากลับทิ้งโค้ดเดิมไว้ที่ไหนสักแห่งใน repository แล้วให้เทสต์ชี้ไปที่โค้ดนั้น
สุดท้ายก็มี dead code หลายพันบรรทัดที่ไม่มีทางถูกเรียกใน production เหลืออยู่ใน repository เพียงเพื่อให้ Sonar พอใจ
ผลลัพธ์คือใน header มีค่าคงที่อย่าง
static const unsigned ONE = 1;,TWO = 2;,THREE = 3;เพิ่มขึ้นเป็นหลักพันhttps://en.m.wikipedia.org/wiki/Mutation_testing
แทบทุกภาษามีเครื่องมือและเฟรมเวิร์กอยู่แล้ว ตัวอย่างเช่น stryker-mutator(C#, TypeScript), pitest(Java), mutatest(Python)
ตลอดประสบการณ์ 6 ปี ผมไม่เคยเห็นแอปพลิเคชันที่เขียนแย่ขนาดนั้นมาก่อน ไม่ใช่แค่เรื่องสไตล์ แต่มีหลายส่วนที่พังจริง ๆ อย่างสมบูรณ์ด้วย และไม่มีใครรู้ว่าอะไรผิด
ผมเกลียด Sonar จริง ๆ ควรใช้มันเฉพาะสำหรับรายงานช่องโหว่เท่านั้น ไม่ใช่มาบอกให้เปลี่ยนชื่อตัวแปร หรือบอกว่า “ต้อง refactor โค้ดซ้ำส่วนนี้” เรามี backlog ตั๋ว Jira อยู่แล้ว ไม่อยากให้มันมาชี้อีกว่าต้องทำอะไรเมื่อไหร่
แต่ผู้จัดการชอบ เครื่องมือเล่นบทผู้มีอำนาจ แบบนี้มาก
ปัญหาใหญ่คือพอบังคับใช้มันแล้ว ถ้าจะหลีกเลี่ยงความโง่ก็ต้องผ่านกระบวนการราชการขนาดมหึมา สัปดาห์ที่แล้วผมยังต้องทะเลาะกับเครื่องมือคุณภาพโค้ดภาคบังคับ เพราะมันบ่นว่า
res.status(200).json()ไม่มี header HSTSต่อให้ตั้งค่าด้วยมือ หรือใช้
app.use(helmet())มันก็ยังบ่นไม่หยุด และสุดท้ายดูเหมือนมันอยากให้เขียน backend ทั้งหมดไว้ในไฟล์เดียวเสียมากกว่า ทั้งที่จริง ๆ แล้ว HSTS จัดการที่ ingress หรือ load balancer ได้อย่างสง่างามและอัตโนมัติกว่าผมอาจทำเครื่องหมายว่าเป็น false positive แล้วใช้เวลา 1–2 สัปดาห์อธิบายให้ผู้บริหารระดับสูงฟังว่า HSTS คืออะไรเพื่อขออนุมัติก็ได้ แต่สุดท้ายผมเพิ่ม
res.sendJson(data, status = 200)เข้าไปใน prototype ของ response object แทน แน่นอนว่ามันเป็น implementation ที่โง่ แต่ก็ทำให้ตระหนักว่าในสายงานที่มี bureaucracy หนัก ๆ ซอฟต์แวร์แย่ ๆ มักเกิดจากผลรวมของ implementation แย่ ๆ แบบนี้เองจากประโยคที่ว่า “75% ของเคสทดสอบ TestGen-LLM build ได้ตามปกติ, 57% ผ่านอย่างเสถียร และ 25% เพิ่ม coverage” ปัญหาคือดูเหมือนมีความเป็นไปได้สูงที่ การทดสอบที่ LLM สร้าง จะ “รับรอง” พฤติกรรมที่มีบั๊ก
โดยเฉพาะถ้าเป็น codebase ที่มี test coverage ต่ำอยู่แล้วก็น่าจะยิ่งเป็นแบบนั้น ข้อดีของการที่คนเขียน test ใหม่เองคือมีคนคอยตัดสินได้ว่าระบบโง่หรือ test ผิด
อย่างน้อย test แบบนี้ควรแยกไว้ในโฟลเดอร์ test พิเศษ และปฏิบัติกับมันด้วยความสงสัยในระดับที่เหมาะสม
อย่างไรก็ตาม codebase ที่มี coverage ดีช่วยให้ทำ refactoring ขนาดใหญ่ได้อย่างปลอดภัยโดยไม่เกิด regression และแม้จะมีบั๊กอยู่และ refactoring นั้นยังคงรักษาบั๊กนั้นไว้ ก็ยังเป็นคุณสมบัติที่มีประโยชน์
ความเสี่ยงของเครื่องมือสร้าง test ที่ออกแบบมาเพื่อ encode พฤติกรรมปัจจุบันคือ อาจทำให้เกิด ความรู้สึกปลอดภัยปลอม ๆ ทั้งที่จริงแล้วมันแค่ encode พฤติกรรมปัจจุบันเท่านั้น
บางทีเรื่องนี้อาจแก้ได้ด้วยการไม่เรียกมันว่า “test” แต่เรียกว่า “behavior snapshot” อะไรทำนองนั้น ชื่อต้องสื่อว่ามัน capture พฤติกรรมปัจจุบัน ไม่ใช่พฤติกรรมที่ถูกต้อง
ดังนั้นผมคิดว่าจำเป็นต้องมีการกำกับดูแลจากมนุษย์ในระดับหนึ่งเสมอ เพื่อ判断ว่าเกิด scenario แบบไหนขึ้น
เรื่องแบบนี้เกิดขึ้นได้กับระบบทุกชนิด และผู้คนมักคิดว่าจะแก้ได้ด้วยการเพิ่มชั้น automation เข้าไปอีกชั้น เหมือนกรณีนี้ เดิมที test ถูกประดิษฐ์ขึ้นมาเพื่อยืนยันว่าโปรแกรมทำงานถูกต้อง แต่พอแม้แต่สิ่งนั้นก็ถูกทำให้อัตโนมัติ เราก็เจอปัญหาเดิมอีกครั้งในโค้ดที่ใหญ่กว่า คือในรูปแบบของ test แทนที่จะเป็น assertion
บางครั้งอาจไม่รู้ว่าควรสร้าง factory สำหรับ input ข้างเคียงที่จำเป็นต่อ test อย่างไร แต่รู้ว่าโค้ดเองควรทำงานอย่างไร
ถ้า LLM ช่วยวางโครง test และทำให้นักพัฒนาเขียนการตรวจสอบ business logic ได้ง่ายขึ้น ก็อาจให้ประโยชน์มาก
อย่างไรก็ตาม ถ้า test ที่สร้างขึ้นผูกติดกับ implementation มากเกินไปเหมือน unit test ส่วนใหญ่ มันจะทำให้ความเร็วในการพัฒนาช้าลง ถ้าแก้ test รายตัวได้ยากเกินไป เราอาจถึงขั้นเห็นคนลบ test ทั้งหมดแล้วสร้างใหม่เมื่อมีการเปลี่ยนแปลงใหญ่ ๆ
โค้ดบางส่วนอาจพึ่งพาบั๊กนั้นอยู่ และถ้าแก้มันไม่ว่าจะโดยบังเอิญหรือตั้งใจก็อาจก่อปัญหาร้ายแรงกว่าเดิม
แน่นอนว่า test แบบนี้ไม่สามารถแทนที่ test ที่ตรวจสอบ requirement จริงได้
แต่มี legacy system นับไม่ถ้วนที่เข้าสู่โหมด maintenance ด้วย coverage ต่ำ และในกรณีแบบนั้น การสร้าง test เพื่อตรวจสอบพฤติกรรมปัจจุบันมีประโยชน์มาก ทำให้เมื่อมีใครเปลี่ยนแปลงอะไร สามารถตรวจได้ว่าส่วนที่เหลือยังเหมือนเดิมหรือไม่
พออ่าน PDF แล้ว ดูเหมือนว่านี่ “แค่” เป็นการสร้าง test ที่ผ่านซ้ำ ๆ ได้ กล่าวคือไม่ flaky
เป้าหมายหลักคือสร้าง ชุด regression test ด้วย test ที่ตรึงพฤติกรรมของโค้ดเดิมไว้ ไม่ใช่แทนที่ developer test ที่เขียนโดยคนที่รู้ functional requirements
บริษัทที่ผมเคยทำงานเมื่อเกือบ 20 ปีก่อนก็เคยลอง AgitarOne ซึ่งสัญญาว่าจะสร้าง test case อัตโนมัติที่สำรวจพฤติกรรมของโค้ด Java ได้ นอกจากนี้ยังสร้าง test ที่ผ่านได้แทบอัตโนมัติเพื่อใช้เป็นชุด regression test ได้ด้วย
ส่วนตัวผมไม่ชอบ มันสร้างของออกมามากเกินไป และฝ่ายบริหารก็เข้าใจว่า coverage สูงขึ้นแปลว่าคุณภาพสูงขึ้นด้วย เลยสงสัยว่าแนวทาง LLM ที่ FB พูดถึงตรงนี้ดีกว่าสมัยนั้นแค่ไหน
http://www.agitar.com/solutions/products/agitarone.html
จนกว่า LLM จะสามารถตัดสินความถูกต้องจริงได้โดยไม่ต้องพึ่งสมมติฐานหรือ oracle ว่า test ที่ดีต้องผ่าน ก็ดูยากที่จะไปถึงจุดนั้นได้ คงต้องใส่ความคาดหวังของพฤติกรรมไว้ใน prompt ไม่ทางใดก็ทางหนึ่ง
คุณค่าของ test อยู่ที่การรับประกันว่าจะไม่ทำลายสิ่งที่มีคนใส่ใจ ไม่ใช่การตรึงพฤติกรรม edge case แทบทั้งหมดที่แทบไม่มีใครใช้และเป็นเพียงผลผลิตของ implementation เฉพาะหนึ่ง ๆ ไว้ตลอดไป
จากประสบการณ์ การเขียน test มักเป็นวิธีที่ยอดเยี่ยมในการประเมิน คุณภาพโค้ด
ถ้า test ซับซ้อนหรือทำ coverage ให้ถึงได้ยาก ก็มีความเป็นไปได้สูงว่าโค้ดที่ถูกทดสอบควรถูกปรับปรุง
โค้ดที่มี coupling ต่ำ, cohesion สูง และ complexity ต่ำ ควรจะเขียน unit test ได้ง่าย
ในการประเมินผลิตภัณฑ์ Reels และ Stories ของ Instagram ระบุว่า 75% ของ test case จาก TestGen-LLM build ได้ตามปกติ, 57% ผ่านอย่างเสถียร และ 25% เพิ่ม coverage
ในกิจกรรมทดสอบของ Instagram และ Facebook ของ Meta ระบุว่าปรับปรุงได้ 11.5% ของ class ทั้งหมดที่นำไปใช้ และ 73% ของ recommendation ได้รับการอนุมัติจาก software engineer ของ Meta สำหรับ deploy ไป production
ไม่รู้ว่านี่เป็นสัดส่วนที่ดีไหม คงต้องอ่านเพิ่มว่าสิ่งที่รับไม่ได้เป็นข้อผิดพลาดเล็ก ๆ ที่เราจับได้ในการ code review หรือเป็นปัญหาร้ายแรง ถ้าวิศวกรมนุษย์มีอัตราล้มเหลว 25% ก็อาจไม่ค่อยช่วยเท่าไร ขึ้นอยู่กับประเภทของความล้มเหลว
ภารกิจโดยรวมที่ทำให้การสร้าง unit test สำหรับโค้ด Android เป็นอัตโนมัติก็น่าสงสัยว่าเป็นทิศทางที่ดีหรือไม่ คนสาย TDD คงนอนพลิกตัวอยู่ในหลุมศพ หรือไม่ก็บนเตียงที่บ้าน แต่ก็คงมีเงื่อนไขกำกับไว้เบื้องหลังอยู่ดี
ที่ unlogged.io ช่วงหนึ่งสิ่งที่เราโฟกัสหลักคือ การสร้างเทสต์ JUnit อัตโนมัติ แต่ด้วยเหตุผลหลายอย่าง มันไม่ค่อยได้รับความนิยม
โค้ดเทสต์ที่สร้างออกมามีมากเกินไปจนทีมพัฒนาไม่อยากดูแลรักษา ไม่สามารถจำลองสถานการณ์จริงได้ และ code coverage ก็เป็นแค่ตัวชี้วัดลวงตา นักพัฒนาจึงหาวิธีเลี่ยงเพื่อให้ถึงเป้าหมายด้วยสถานการณ์ที่ไม่มีความหมาย
ตอนนี้เรากำลังทำงานเพื่อให้บริการ เทสต์แบบ replay แบบ no-code ที่จำลองทุกสถานการณ์การทำงานที่ไม่ซ้ำกัน และให้นักพัฒนาสามารถ replay บนเครื่อง local ได้โดย mock external dependencies ไว้
เผื่ออ้างอิง ผมเป็นผู้ก่อตั้ง unlogged.io
ผมอยากไปในทิศทางตรงกันข้าม คือป้อน acceptance criteria แล้วให้สร้างเทสต์ที่ตรวจสอบมัน จากนั้นค่อยสร้างโค้ดที่ผ่านเทสต์นั้น
บางครั้งทำอะไรคล้าย ๆ กันได้แบบจำกัดด้วย Copilot แต่ไม่เข้าใจว่าทำไมถึงรู้สึกว่าไม่มีใครโฟกัสลำดับนี้เลย
TestGen-LLM เป็นผลงานที่แปลกจริง ๆ อาจใช้เป็นขั้นตอนแรกของการ refactor หรือเขียนใหม่ได้ แต่การที่论文เน้น code coverage ให้ความรู้สึกว่าการตัดสินใจผิดเพี้ยนไปหมด
ถ้าองค์กรพังอยู่แล้วเพราะบังคับให้มี coverage สูง มันอาจจะดี แต่ TestGen-LLM ไม่ได้ทำให้โค้ดโปรเจกต์ดีขึ้นในทางใดเลย และมีแต่จะเพิ่ม friction ให้การ implement การปรับปรุงจริง
การสร้างเทสต์ edge case ที่อาจจะผ่านหรือไม่ผ่านน่าจะมีประโยชน์กว่ามาก แต่ TestGen-LLM พึ่งพา compile error กับเทสต์ที่ล้มเหลวเพื่อกรองขยะจาก LLM
จากการที่ใน论文ไม่มีตัวอย่างเทสต์ที่สร้างขึ้นเลย ผมสงสัยว่ามันคงดูสมัครเล่นเหมือนโค้ดที่ LLM สร้างตัวอื่น ๆ ที่เคยเห็นมา
แถมยังช่วยให้เข้าใจด้วยว่าโค้ดพยายามทำอะไรอยู่
น่าสนใจที่พนักงาน Meta เขียน论文 12 หน้าเพื่อโปรโมต AI สำหรับนักพัฒนา และถึงกับหยิบ Sankey diagram มาใช้
ผมอาจจะผิดก็ได้ แต่ถ้าจะนำเสนอแบบนี้ ก็น่าจะให้ข้อมูลที่ reproducible ได้ด้วยไม่ใช่หรือ
ไม่ได้จะพูดเชิงทฤษฎีสมคบคิด แค่ผมไม่มีข้อมูลระดับที่ Meta ใช้สำหรับ training เลย เลยสงสัยว่ามีอะไรเปิดเผยไว้บ้างไหม
สงสัยว่าในอนาคตต้นทุนการดูแลรักษาคลังเทสต์ขนาดมหึมาที่สร้างอัตโนมัติจะมากแค่ไหน
ไม่ใช่แค่สร้าง test case เท่านั้น แต่ต้องมี วิธีอัตโนมัติในการอัปเดต ให้ด้วย