Using Copilot to generate Unit Test
🇬🇧 English version 🇬🇧
Introduction
Sau hơn 2 năm ra mắt, Copilot chắc hẳn không còn xa lạ gì với anh em cốt đờ. Với những lời tán dương có cánh như change the game, revolutionizing developer collaboration, reshape the way we write code, intelligent suggestions, ... chắc hẳn ai trong chúng ta cũng đôi chút tò mò, xem thực sự Copilot có thể làm được gì?
Liệu chỉ với 10$/ tháng, ta có thể trở thành lập trình viên 2 nút (Tab
+ Enter
)? Hay xa hơn, liệu anh em cốt đờ chúng ta có thể bị đào thải bởi AI trong tương lai? Mình cũng có khá nhiều câu hỏi đặt ra trong đầu.
Rất may mắn là công ty cũng không đứng ngoài xu thế, đã cho anh em có cơ hội trải nghiệm Copilot, nên phần nào những câu hỏi của mình cũng đã được giải đáp.
Trong quá trình demo, mình đã thử áp dụng cho dự án hiện tại (đang trong giai đoạn viết Unit Test), nên trong bài viết này, chúng ta sẽ cùng nhau đi tìm hiểu về Copilot, và xem thử khả năng viết UT của nó có thực sự bá đạo như lời đồn không nhé.
Nội dung chính:
- Copilot có những tính năng gì?
- Copilot support chúng ta như thế nào trong việc viết Unit Test?
- Rút ra nhận xét.
OK! Gét gô! 🚀
Copilot features
Trước tiên, anh em hãy đảo qua một số tính năng của Copilot, để xem thiên hạ đang dùng thằng personal AI pair programmer này như thế nào nha.
- Suggest code: Tính năng mạnh nhất. Copilot có khả năng đọc, hiểu những gì bạn đang viết, từ đó đưa ra suggested code phù hợp - Coding không còn cô đơn, vì bạn đã có côpilot.
- Explain code: Giải thích chi tiết xem đoạn code này làm gì, step by step luôn. Tính năng này rất hữu ích khi chúng ta muốn đọc hiểu code cũ, hoặc muốn hiểu về 1 solution nào đó trên StackOverflow.
- Copilot Chat: Tư vấn và giải đáp thắc mắc tại khung chat của Editor luôn, không cần mất thời gian switch qua lại giữa browser và editor như khi dùng ChatGPT hoặc các tool AI khác. Đặc biệt, do được tích hợp vào editor, nên Copilot Chat có thể hiểu được context dự án tốt hơn, từ đó đưa ra câu trả lời phù hợp nhất.
- Copilot Labs: Dự án độc lập với Github Copilot, nhưng được rất nhiều anh em sử dụng bởi những tính năng rất hữu ích của nó: Refactor code, Fix bug, Debug, Comment code, ...
- Others
- Translate từ ngôn ngữ này sang ngôn ngữ khác.
- Explore library: Dùng khi chúng ta muốn học hỏi 1 ngôn ngữ/ thư viện mới. Copilot có thể suggest cú pháp, methods, .... phù hợp với ngôn ngữ/ thư viện hiện tại.
- Những dự án đang được thử nghiệm: Copilot CLI, Copilot for PR, ...
- Joke =)): Mình chỉ biết có tính năng này khi đọc những bài review về Copilot. Hãy thử gõ "Database tables walk into a bar" vào editor xem sao =))
Với Generating Unit Test, Github Copilot team cũng đang triển khai 1 project rất đáng mong chờ:
Automate automated testing.
Missed a test? GitHub Copilot can point out missing unit tests and generate new test cases for you after every change.
Và trong khi chờ đợi tính năng này, hôm nay chúng ta sẽ xem thử xem, với phiên bản hiện tại, Copilot có thể giúp chúng ta được phần nào khi viết test không nhé
We don't have time to write tests ..
Copilot and Unit Tests
Để minh họa cho bài viết này, mình có tạo 1 Rails project đơn giản, với 2 models chính là: Author
và Post
, sử dụng các thư viện phổ biến khi viết test như rspec, factory-bot, shoulda-matcher và faker .
Trong bài viết này, mình dùng VSCode. Cách cài đặt Copilot trên VSCode, bạn có thể tham khảo ở đây.
Để generate test bằng Copilot trên VSCode có 3 cách:
-
Sử dụng
Cmd + Shift + P
(HoặcCtrl + Shift + P
trên Windows) +Generate Tests
-
Viết prompt hoặc code như bình thường, Copilot sẽ show gợi ý. Mình sẽ chọn Accept hoặc Discard.
-
Sử dụng Copilot Chat, yêu cầu Copilot Chat generate tests.
Trong bài viết này, mình sẽ sử dụng cả 3 cách, để compare thử xem ưu/ nhược điểm của từng cách, và nên dùng chúng trong trường hợp nào.
Model
Copilot có thể viết test cho tất cả các đoạn code thông thường ở trong model không?
Để trả lời cho câu hỏi này, ta sẽ thử liệt kê xem chúng ta thường viết gì trong 1 file model?
Associations, Validations, Scopes, Instance methods, Class methods, Callbacks, Enum, Delegate, Custom Validations, ...
Tùy từng dự án, có thể có thêm các đoạn code khác, nhưng trong quá trình làm việc của mình, đại đa số các model sẽ chứa thông tin dạng như thế này.
Yoh. Giờ chúng ta sẽ bắt đầu thử nghiệm:
- Add thêm associations, validations, scopes, .... vào trong file model.
- Generate tests sử dụng Copilot.
Associations
Sau khi thêm associations has_many :posts
, belongs_to :author
vào model tương ứng, đến giờ cho Copilot làm việc rồi!!
Nice
- Điểm cộng:
- Code được generate khá nhanh, developer có thể review trước.
- Biết tự thêm vào file spec tương ứng.
- Biết sử dụng cú pháp của
shoulda-matcher
- Điểm trừ:
- Khi bấm apply, Copilot không nhận diện được existed content, mà sẽ append vào cuối file đó luôn.
Đối với belongs_to
association, mình tự vào file test và viết test manual.
- Điểm trừ:
- Code gợi ý lần đầu không liên quan gì =))
- Điểm cộng:
- Sau khi gõ
describe
thì Copilot đã gợi ý khá tốt, ra test choassociations
và phần còn lại.
- Sau khi gõ
Scopes
Mình lựa chọn 2 scopes để test thử
- Lấy ra các
posts
được tạo trong khoảng thời gian từstart_date
->end_date
- Order posts by raw ids.
Lần này mình dùng Copilot Chat để generate test.
Hmm. Mặc dù "selected code" là 2 scopes, nhưng không hiểu sao Copilot chỉ tạo test cho 1 scope. 🤔
Với scope còn lại, mình phải gõ thử bằng tay. Tuy nhiên, Copilot cũng hiểu được bối cảnh, và generate test khá ok.
- Điểm cộng:
- Về format, test được generate khá chuẩn, có cả dữ liệu
include
vànot include
- Biết fake data sử dụng cú pháp của
FactoryBot
.
- Về format, test được generate khá chuẩn, có cả dữ liệu
- Điểm trừ:
- Không generate test cho hết cho "selected code".
- Và một điểm trừ khá lớn, đó là khi mình chạy test thì test case
returns posts created between the given dates
bị fail =)) (Nguyên nhân là do Time dependency - lag time giữa thời điểm tạo dữ liệu test và thời điểm chạy test case).
Như vậy, chúng ta có thể thấy rằng, Copilot có thể generate ra tests, nhưng có đúng không, hoặc chạy được không thì chưa chắc =))
Validations
Đối với phần test cho Validations, mình lựa chọn 1 validation đơn giản cho trường name, và 1 email regex để validate email.
VALID_EMAIL_REGEX= /^(|(([A-Za-z0-9]+_+)|([A-Za-z0-9]+\-+)|([A-Za-z0-9]+\.+)|([A-Za-z0-9]+\++))*[A-Za-z0-9]+@((\w+\-+)|(\w+\.))*\w{1,63}\.[a-zA-Z]{2,6})$/i
validates :email , presence: true,uniqueness: {case_sensetive: false},
format:{with:VALID_EMAIL_REGEX, multiline: true}
validates :name, presence: true, length: { minimum: 3, maximum: 50 }
Tới đây thì mình bắt đầu thấy có chút vấn đề. Copilot suggest một số test basic. Phần test cho email thì bỏ trống. Mặc dù mình đã tab để accept kết quả hiện tại, nhưng Copilot vẫn không show thêm gợi ý gì.
Dù đã viết thử comment: # Read VALID_EMAIL_REGEX in author.rb model, generate tests for email validations based this regex
, vẫn không có test mới nào được generate ra.
Mình phải request qua Copilot Chat mới nhận lại được kết quả.
Đoạn này mình nghĩ nên cộng điểm cho Copilot, vì nó biết sử dụng build(:author)
thay vì create(:author)
, sẽ tiết kiệm được chút thời gian chạy test. ⏱️
Others
Mình có test thử với những phần còn lại: instance methods, class methods, enum, delegation, ...
thì Copilot có thể đáp ứng được các usecases đơn giản. Đối với một số case phức tạp hơn, hoặc "hiếm" gặp hơn, thì Copilot không đáp ứng được yêu cầu:
def normalize_content
convert = {"\u309B" => "\u3099", "\u309C" => "\u309A"}
content.gsub(/\u309B|\u309C/, convert).gsub(/\p{L}[\u3099|\u309A]/) do |str|
str.unicode_normalize(:nfkc)
end
end
Ví dụ, mình có một function như trên, làm nhiệm vụ normalize 1 vài kí tự đặc biệt. Tuy nhiên, khi viết test thì Copilot đã lấy một ví dụ không liên quan tới logic của function normalize_content
. Có thể là do Copilot generate test bằng cách "đoán" từ tên hàm❓
Như vậy, chúng ta có thể rút ra một vài nhận xét:
- Copilot có khả năng generate ra những test đơn giản, phổ biến. Giúp đỡ chúng ta tốt trong việc typing repeated snippet.
- Đối với một số đoạn code có logic phức tạp, Copilot thường không suggest hoặc nếu suggest thì đưa ra những đoạn code sai/ không liên quan. Với những case như vậy, chúng ta cần viết comment để guide Copilot, break nhỏ function hoặc phải tự code và để Copilot suggest phần còn lại.
- Chúng ta vẫn cần verify lại tính đúng đắn của generated code.
Controller
Để demo cho phần generate test cho controller, mình có thử với hàm index
, lấy ra 1 list các published posts và paginate cho nó.
Ban đầu, do thiếu context, Copilot sẽ chỉ generate ra test cases đơn giản (Kiểm tra request successful). Tuy nhiên, sau khi được guide theo ý tưởng viết test của mình, Copilot đã bắt kịp rất nhanh.
- Copilot có khả năng học rất nhanh. Chúng ta chỉ cần "làm mẫu" một lần, là nó có thể học được style code/ cách đặt tên/ cách comment và cả phong cách viết test. Điều này hỗ trợ rất tốt trong việc đảm bảo tính nhất quán của source code.
- Khi viết test, Copilot có thể chủ động áp dụng các kĩ thuật Mock/Stub.
Cá nhân mình thấy Copilot support tốt nhất cho phần viết test Controller. Lý do là vì test của controller có tính lặp lại theo một format chung. Mà đây lại đúng là sở trường của Copilot - Xử lý repeated snippet.
Workers/ Services and others
Đối với Worker và Services, chúng ta quay trở lại bài toán viết Unit Test cho các class Ruby thông thường.
Ở đây mình có viết thử 1 đoạn code, gọi lên dev.to để lấy bài post mới và import vào database.
Logic cũng không có gì phức tạp, nên Copilot xử lý khá ổn, đặc biệt trong khoản mock/stub.
Real-Life project
Trong quá trình làm việc thực tế, mình thường gặp 1 số vấn đề khi sử dụng Copilot generate test cho services/workers/ custom libraries/....
- Nếu business logic trong các class này phức tạp, Copilot gặp khó trong việc generate ra test cases cho cả file. Dẫn tới trường hợp lack cases, coverage không đạt yêu cầu.
- Copilot generate test tốt trong trường hợp mình viết class từ đầu. Đối với những trường hợp sửa code cũ (fix bugs, update logic business, refactor, ...) khi được yêu cầu generate test, nó thường tạo test cho cả file, chứ không chỉ tập trung test phần mình vừa thêm vào.
Để xử lý những vấn đề này, hãy thử:
- Chia logic ra thành các function nhỏ: Copilot generate test tốt hơn với những đoạn code mà logic không quá phức tạp.
- Đừng đuổi theo Copilot, hãy để Copilot theo đuổi bạn: Về cơ bản, Copilot cũng chỉ là 1 tool hỗ trợ viết code thôi, phần quan trọng nhất vẫn là ở bạn. Vì thế, không ỷ lại, không chờ đợi, hãy cứ bắt tay vào viết code, và để Copilot follow/support bạn .
- Đừng tin tưởng 100% vào Copilot, đặc biệt là khi nó suggest một đoạn code dài =))
Summary
Copilot đang tỏ ra là một trợ thủ đắc lực, có thể support ở mọi bước trong coding cycle: chia nhỏ vấn đề, viết Unit Test, viết code, viết document, refactor code, ...
Đặc biệt, Github vẫn đang liên tục phát triển, cải tiến Copilot từng ngày để đưa ra những tính năng mới. Biết đâu, một vài năm nữa, thay vì TDD, BDD, chúng ta còn có thêm cả CDD - Copilot Driven Development cũng nên =))
Tuy nhiên, ở hiện tại, mình vẫn giữ quan điểm:
Công nghệ chưa thể hoàn toàn thay thế con người, nhưng những người biết dùng công nghệ có thể thay thế những người còn lại.
Vì thế, nếu được, hãy thử cho Copilot một cơ hội anh em nhé :v
Key takeaways
- Copilot hỗ trợ tốt cho việc viết Unit Test, đặc biệt là với các simple/ repeated code.
- Copilot boost productivity (mình thấy tăng khoảng 40% khi viết Unit Test), cho phép chúng ta có nhiều thời gian hơn để focus vào logic problems hơn là typing stuff.
- Đôi khi, Copilot sẽ gợi ý những đoạn code không liên quan (thỉnh thoảng là cả những đoạn code sai syntax). Vậy nên, đừng quá tin tưởng vào Copilot, hãy guide nó bằng các prompt, recheck lại code, và chịu trách nhiệm với kết quả cuối cùng.
References
- Prompting GitHub Copilot Chat to become your personal AI assistant for accessibility
- The pros and cons of using GitHub Copilot for software development (survey results)
- My Week With GitHub Copilot: AI Pair Programming Review
- Using Github Copilot for unit testing
- A Beginner's Guide to Prompt Engineering with GitHub Copilot
- Our Experiment with AI-Powered Dev Tools: The GitHub Copilot Experience of One Company
All rights reserved