Công cụ cải thiện kiểm thử đơn vị tự động của Meta: TestGen-LLM
- Công cụ TestGen-LLM do Meta phát triển sử dụng các mô hình ngôn ngữ lớn (LLMs) để tự động cải tiến các kiểm thử trước đó do con người viết.
- Các lớp kiểm thử do TestGen-LLM tạo ra đã vượt qua thành công một loạt bộ lọc nhằm đảm bảo có sự cải tiến có thể đo lường được so với test suite gốc, nhờ đó giải quyết được vấn đề ảo giác của LLM.
- Bài viết mô tả việc triển khai TestGen-LLM trong các test-a-thons cho nền tảng Instagram và Facebook của Meta.
Đánh giá hiệu suất của TestGen-LLM
- Trong các bài đánh giá cho sản phẩm Reels và Stories của Instagram, 75% test case do TestGen-LLM tạo đã build thành công, 57% chạy thành công đáng tin cậy và 25% làm tăng độ phủ.
- Trong các test-a-thons của Instagram và Facebook tại Meta, TestGen-LLM cải thiện 11.5% tất cả các lớp được áp dụng, và kỹ sư phần mềm của Meta chấp nhận 73% đề xuất để triển khai.
- Đây là báo cáo đầu tiên về việc triển khai quy mô công nghiệp mã do LLM tạo ra và có lời cam kết cải thiện mã như vậy.
Ý kiến GN+
- TestGen-LLM là công cụ có thể mang lại thay đổi đột phá cho việc tự động hóa và nâng cao chất lượng kiểm thử phần mềm, thành công trong việc sử dụng LLM để cải tiến các kiểm thử hiện có.
- Công cụ này đóng góp quan trọng cho cộng đồng kỹ thuật phần mềm bằng cách tăng độ phủ kiểm thử trong môi trường doanh nghiệp thực tế và tạo ra các test case đáng tin cậy.
- Việc ứng dụng thành công trong các test-a-thons của Meta cho thấy TestGen-LLM có thể được tích hợp vào quá trình phát triển sản phẩm thực tế, mở ra tiến bộ quan trọng giúp nâng cao hiệu quả và độ ổn định trong phát triển phần mềm.
1 bình luận
Ý kiến trên Hacker News
Ở một công ty bảo hiểm lớn tôi từng làm trước đây, ban lãnh đạo đặt mục tiêu độ bao phủ kiểm thử 80% cho toàn bộ codebase, thế là mọi người bắt đầu viết các bài kiểm thử đơn vị vô nghĩa cho getter/setter của Java DTO chỉ để đạt chỉ tiêu
Tất nhiên, các lập trình viên cũng không thể thay đổi quy tắc đo độ bao phủ của Sonar, và hồi còn là dev trẻ tôi đã học được rằng chỉ nhìn KPI có thể dẫn tới những hành vi lệch khỏi ý định ban đầu
Có lẽ chỉ cần vài kịch bản kiểm thử E2E được thiết kế tốt còn tốt hơn cho chất lượng phần mềm
Vấn đề là mớ mã cũ bừa bộn đó lại được kiểm thử rất kỹ với độ bao phủ 95%. Mã mới thì đạt 100% bao phủ, nhưng vì ngắn hơn rất nhiều nên tổng độ bao phủ của cả hệ thống lại giảm và không được thông qua
Phần còn lại chỉ là mã Swing UI vừa khó kiểm thử vừa chẳng có nhiều ý nghĩa để kiểm thử, nên trưởng nhóm phát triển quyết định thay vì mất 1–2 tuần viết kiểm thử cho Swing, cứ giữ lại mã cũ đâu đó trong repo và để bài kiểm thử tiếp tục trỏ vào phần đó
Kết quả là hàng nghìn dòng dead code không bao giờ được gọi trong production vẫn bị giữ lại trong repo chỉ để làm Sonar hài lòng
Kết quả là xuất hiện hàng nghìn hằng số kiểu
static const unsigned ONE = 1;,TWO = 2;,THREE = 3;trong phần headerhttps://en.m.wikipedia.org/wiki/Mutation_testing
Hầu như ngôn ngữ nào cũng có công cụ và framework hỗ trợ, ví dụ như stryker-mutator (C#, TypeScript), pitest (Java), mutatest (Python)
Trong 6 năm làm nghề, đó là ứng dụng tệ nhất tôi từng thấy; không chỉ dở về style mà còn có rất nhiều chỗ thực sự hỏng hoàn toàn, nhưng chẳng ai biết chuyện gì đang sai
Tôi thực sự ghét Sonar. Nó chỉ nên dùng để báo cáo lỗ hổng, chứ không nên bảo bạn đổi tên biến hay “phải refactor đoạn mã trùng lặp này”. Chúng tôi đã có backlog Jira rồi, đừng ra lệnh cả việc gì cần làm và khi nào phải làm
Nhưng các quản lý lại cực kỳ thích kiểu công cụ chơi trò quyền lực này
Vấn đề lớn là họ biến nó thành bắt buộc, rồi để tránh những điều ngớ ngẩn đó thì bạn phải đi qua cả một quy trình quan liêu khổng lồ. Tuần trước tôi còn phải cãi nhau vì công cụ chất lượng mã bắt buộc than phiền rằng
res.status(200).json()không có header HSTSDù tôi cấu hình thủ công hay dùng
app.use(helmet())thì nó vẫn tiếp tục phàn nàn, cuối cùng trông như thể nó muốn toàn bộ backend phải được viết trong một file duy nhất. Trong khi đó, HSTS lại được xử lý thanh lịch và tự động hơn ở ingress hoặc load balancerTôi có thể dành 1–2 tuần để đánh dấu đó là false positive rồi giải thích HSTS là gì cho quản lý cấp trên để xin phê duyệt, nhưng cuối cùng tôi chỉ thêm
res.sendJson(data, status = 200)vào prototype của đối tượng response. Rõ ràng đó là một cách triển khai ngớ ngẩn, nhưng nó khiến tôi nhận ra rằng trong những lĩnh vực nặng tính quan liêu, phần mềm tồi thường được tạo nên từ tổng hòa của những cách triển khai tồi như vậyĐoạn “75% test case của TestGen-LLM được build thành công, 57% pass ổn định và 25% giúp tăng coverage” cho thấy vấn đề là các bài test do LLM tạo ra có vẻ rất dễ “chứng nhận” những hành vi đang có bug
Đặc biệt, điều này có lẽ còn đúng hơn với những codebase vốn đã có độ phủ test thấp. Nếu con người tự viết test mới, ít nhất vẫn có lợi thế là sẽ có người đánh giá được hệ thống đang ngu hay test đang sai
Tối thiểu thì nên tách các test kiểu này vào một thư mục test riêng và xử lý chúng với mức độ hoài nghi thích hợp
Tuy vậy, một codebase có coverage tốt sẽ giúp thực hiện những đợt refactor quy mô lớn một cách an toàn, không gây regression; và đó vẫn là một đặc tính hữu ích ngay cả khi có bug nhưng lần refactor vẫn giữ nguyên bug đó
Rủi ro của công cụ sinh test được thiết kế để mã hóa hành vi hiện tại là nó có thể tạo ra cảm giác an toàn giả tạo, trong khi thực tế chỉ mới mã hóa hành vi hiện tại mà thôi
Có lẽ có thể giải quyết phần nào nếu đừng gọi đây là “test”, mà đặt tên như “ảnh chụp hành vi” chẳng hạn. Cái tên phải thể hiện rằng nó ghi lại hành vi hiện tại, chứ không phải hành vi đúng
Vì vậy, để xác định kịch bản nào thực sự đã xảy ra thì theo tôi lúc nào cũng cần một mức độ giám sát của con người
Chuyện này xảy ra trong đủ loại hệ thống, và mọi người có xu hướng nghĩ rằng chỉ cần chồng thêm một lớp tự động hóa như ở đây là sẽ giải quyết được. Test vốn được tạo ra để kiểm tra xem chương trình gốc có hoạt động đúng không; nếu ngay cả việc đó cũng tự động hóa thì ta lại gặp đúng vấn đề cũ ở một lớp mã lớn hơn, tức ở dạng test thay vì assertion
Có những lúc bạn không biết phải tạo factory cho các đầu vào phụ trợ phục vụ test như thế nào, nhưng vẫn biết code đáng lẽ phải hoạt động ra sao
Nếu LLM có thể dựng sẵn bộ khung test và giúp lập trình viên dễ viết phần kiểm chứng business logic, đó có thể là một lợi ích lớn
Tuy nhiên, nếu các test được sinh ra bị gắn quá chặt với implementation như phần lớn unit test hiện nay thì nó sẽ làm chậm tốc độ phát triển. Nếu việc sửa từng test riêng lẻ quá khó, có khi người ta sẽ xóa sạch toàn bộ test trong một thay đổi lớn rồi sinh lại từ đầu
Một phần code khác có thể đang phụ thuộc vào bug ấy, và việc sửa nó, dù vô tình hay có chủ ý, có thể gây ra vấn đề còn nghiêm trọng hơn
Tất nhiên, loại test này không thể thay thế các test xác nhận yêu cầu thực sự
Nhưng có vô số hệ thống legacy đã bước vào chế độ bảo trì với coverage thấp, và trong những trường hợp đó, việc sinh test để xác minh hành vi hiện tại là cực kỳ hữu ích. Nó giúp kiểm tra xem khi ai đó thay đổi một phần thì phần còn lại có giữ nguyên hay không
Đọc PDF thì có vẻ đây “chỉ là” tạo ra những bài test pass lặp đi lặp lại, tức là không bị flaky
Mục tiêu chính là tạo ra một bộ regression test để cố định hành vi của code hiện có, chứ không phải thay thế các bài test do lập trình viên viết dựa trên hiểu biết về yêu cầu chức năng
Gần 20 năm trước, công ty tôi từng làm cũng đã thử AgitarOne, với lời hứa là nó sẽ tự động sinh các test case khám phá hành vi của code Java. Nó cũng có thể gần như tự động tạo ra các test pass để dùng làm bộ regression test
Cá nhân tôi không thích nó. Có quá nhiều thứ được tạo ra, và ban quản lý lại hiểu rằng coverage tăng thì chất lượng cũng tăng. Tôi tò mò không biết cách tiếp cận LLM mà FB nói ở đây tốt hơn thời đó đến mức nào
http://www.agitar.com/solutions/products/agitarone.html
Có vẻ khó đi xa hơn mức này cho tới khi LLM có thể đánh giá độ chính xác thực sự mà không phụ thuộc vào giả định hay oracle kiểu “test tốt thì sẽ pass”. Có lẽ trong prompt phải chứa kỳ vọng về hành vi theo cách nào đó
Giá trị của test nằm ở chỗ đảm bảo không làm hỏng thứ mà ai đó thực sự quan tâm, chứ không phải cố định vĩnh viễn mọi hành vi edge case hầu như không ai dùng tới mà chỉ là sản phẩm phụ của một implementation cụ thể
Theo kinh nghiệm của tôi, viết test thường là một cách tuyệt vời để đánh giá chất lượng mã
Nếu test phức tạp hoặc khó đạt coverage thì rất có thể phần code được test cần được cải thiện
Code có độ kết dính thấp, độ gắn kết cao và độ phức tạp thấp thì đáng ra phải dễ unit test
Trong đánh giá sản phẩm Reels và Stories của Instagram, 75% test case của TestGen-LLM được build thành công, 57% pass ổn định và 25% giúp tăng coverage
Tại các đợt thử nghiệm test ở Instagram và Facebook của Meta, hệ thống đã cải thiện 11,5% tổng số class được áp dụng, và 73% đề xuất đã được các kỹ sư phần mềm Meta phê duyệt để triển khai production
Tôi không biết đây có phải là tỷ lệ tốt hay không. Cần đọc thêm để biết những thứ không được chấp nhận là các lỗi vặt mà code review vẫn bắt được, hay là vấn đề nghiêm trọng. Nếu một kỹ sư con người có tỷ lệ thất bại 25% thì, tùy kiểu thất bại, có khi cũng chẳng giúp ích được nhiều
Tôi cũng nghi ngờ liệu toàn bộ nhiệm vụ tự động hóa việc sinh unit test cho mã Android có thực sự đi đúng hướng hay không. Phe TDD chắc đang trằn trọc trong mộ, hoặc trên giường ở nhà. Dù vậy, có lẽ họ cũng sẽ thêm điều kiện ràng buộc ở phía sau
Một thời gian, trọng tâm chính của unlogged.io là tự động tạo kiểm thử JUnit, nhưng vì một vài lý do nên không thật sự cất cánh được
Lượng mã kiểm thử được sinh ra quá nhiều khiến các lập trình viên không muốn bảo trì, lại không mô phỏng được các kịch bản thực tế, và độ bao phủ mã chỉ là một chỉ số hão huyền. Các lập trình viên đã tìm ra cách lách để đạt mục tiêu bằng những kịch bản vô nghĩa
Hiện tại, họ đang làm việc để cung cấp kiểm thử phát lại không cần code, có thể mô phỏng mọi kịch bản vận hành riêng biệt và cho phép lập trình viên phát lại cục bộ trong khi mock các phụ thuộc bên ngoài
Nhân tiện, tôi là nhà sáng lập của unlogged.io
Tôi muốn đi theo hướng ngược lại. Nếu nhập tiêu chí chấp nhận, tôi muốn nó tạo ra các bài kiểm thử để xác minh điều đó, rồi sau đó tạo mã để vượt qua các bài kiểm thử ấy
Với Copilot thì đôi khi có thể làm hơi giống vậy ở mức hạn chế, nhưng tôi không hiểu vì sao lại có cảm giác như chẳng ai tập trung vào trình tự này
TestGen-LLM đúng là một sản phẩm rất kỳ lạ. Có vẻ nó có thể dùng như bước đầu cho việc refactor hoặc viết lại, nhưng việc bài báo nhấn mạnh độ bao phủ mã khiến tôi cảm thấy họ đã đánh giá lệch hẳn
Nếu một tổ chức đã bị hỏng vì vốn dĩ yêu cầu độ bao phủ cao thì có thể nó sẽ hữu ích, nhưng TestGen-LLM không làm mã dự án tốt hơn theo bất kỳ cách nào mà chỉ làm tăng ma sát khi triển khai cải tiến thực sự
Sẽ hữu ích hơn nhiều nếu nó tạo ra các bài kiểm thử cho những edge case có thể pass hoặc không, nhưng TestGen-LLM lại dựa vào việc lọc rác do LLM sinh ra bằng lỗi biên dịch và kiểm thử thất bại
Việc bài báo hoàn toàn không có ví dụ nào về các bài kiểm thử được tạo ra khiến tôi nghi ngờ rằng nó có lẽ cũng nghiệp dư như những đoạn mã do LLM tạo khác mà tôi từng thấy
Thậm chí nó còn giúp tôi hiểu đoạn mã đó đang cố gắng làm gì
Điều thú vị là đây là một bài báo dài 12 trang do nhân viên Meta viết để quảng bá AI cho lập trình viên, và họ còn lôi cả biểu đồ Sankey ra
Có thể tôi sai, nhưng nếu công bố theo kiểu này thì chẳng phải họ cũng nên cung cấp thông tin có thể tái lập được sao
Đây không phải thuyết âm mưu gì, chỉ là tôi không có lượng dữ liệu ở mức như Meta dùng để huấn luyện. Tôi tò mò không biết họ có công bố gì không
Tôi tự hỏi chi phí bảo trì một kho ngữ liệu kiểm thử khổng lồ được tự động tạo ra trong tương lai sẽ là bao nhiêu
Không chỉ cần tạo test case mà còn phải cung cấp cách tự động hóa việc cập nhật chúng nữa