“Best Practice” cần thiết khi viết TDD
Chào mọi người! Trong bài viết này tôi xin trình bày những “Best Practices” cần biết khi phát triển TDD.
“Best Practice” là tập hợp những kỹ thuật và cách làm mà đã được nghiên cứu, chứng minh trong thực tế, khi áp dụng nó sẽ làm cho sản phẩm của chúng ta tốt hơn, tránh được rất nhiều các vấn đề mà người khác đã gặp phải.
“Best Practice” cho TDD sẽ tập hợp các quy tắc mà chúng ta nên áp dụng khi viết TDD. Cụ thể ở đây là tôi giới thiệu việc viết TDD trong Java và Scala, nhưng thực ra chúng có thể áp dụng trong hầu hết các ngôn ngữ. Tôi sẽ trình bầy các quy tắc theo số thứ tự để mọi người dễ nhớ.
Các quy tắc dùng trong phát triển TDD
1. Tách biệt giữa phần test và code
Ưu điểm: Giúp quản lý code dễ dàng hơn, tách biệt giữa phần code và test
Chúng ta cần có ít nhất 2 folder một cho code và một cho test. Ví dụ trong scala + play2 có thể đặt code trong project/app, test trong project/test. Ngoài việc giúp quản lý code dễ dàng hơn, đây còn là yêu cầu của rất nhiều công cụ quản lý source code
2. Tên package giống nhau giữa test code & source code
Ưu điểm: Giúp tìm test code dễ dàng hơn
Khi test code được tổ chức giống như source code sẽ giúp tìm test code dễ dàng hơn và ngược lại. Điều này rất có ý nghĩa trong các dự án lớn với hàng trăm file code & test code.
3. Đặt tên class test dựa trên file code
Ưu điểm: Tìm file test code dễ hơn, đặc biệt là khi cần tim file test cho một file code cụ thể.
Trong các dự án lớn, số lượng các file code rất lớn. Khi đó nếu các file test không được đặt tên theo một quy tắc nào đó sẽ rất khó tìm file test code. Để giải quyết vấn đề này thì một quy tắc hay được dùng là, đặt tên class test bao gồm tên class cần test và hậu tố “Test” hoặc “Spec” ở cuối. Ví dụ ta có class cần được viết test là ApplicationController.scala, class test sẽ là ApplicationControllerTest.scala hoặc ApplicationControllerSpec.Scala
4. Đặt tên phương thức test mô tả đầy đủ ý nghĩa
Ưu điểm: Giúp hiểu đầy đủ ý nghĩa của phương thức test mà không cần xem comment
Ta không nên dựa vào comment để cung cấp thông tin cho phương thức test. Bởi vì comment không xuất hiện khi test chạy, và cũng không xuất hiện trong các report bởi các tool. Do đó ta nên đặt tên phương thức đầy đủ ý nghĩa.
Có rất nhiều cách để đặt tên test method. Nhưng cách được ưa chuộng là đặt tên dùng Given/When/Then trong khái niệm của BDD. Given – đưa ra điều kiện, When – miêu tả hành động, Then – mô tả kết quả mong đợi. Nếu một số test không có điều kiện trước, thì Given có thể bỏ qua
Bên dưới là một ví dụ của đặt tên:
@Test
public final void whenSemicolonDelimiterIsSpecifiedThenItIsUsedToSeparateNumbers() {
…
}
5. Viết test trước khi viết code
Ưu điểm: Đảm bảo rằng test code luôn được viết
Bằng cách viết hoặc sửa đổi test code trước khi viết, người phát triển sẽ tập trung hơn vào yêu cầu trước khi bắt đầu code. Điều này là điểm khác biệt lớn nhất so với việc viết test sau khi viết code. Hơn nữa việc viết test trước giúp chúng ta tăng chất lượng của test code, tránh vấn đề viết test một cách qua loa.
6. Chạy tất cả các test mỗi lần thay đổi code
Ưu điểm: Đảm bảo rằng không có lỗi xảy ra khi do thay đổi code
Mỗi lần thay đổi bất kỳ phần nào trong code, dù lớn hay nhỏ, ta cũng cần chạy lại tất cả các test. Một cách lý tưởng thì các test có thể chạy nhanh ngay trên máy của người phát triển để họ không phải đợi quá lâu. Mỗi khi code được đưa lên git hoặc svn, cần chạy các test lại để đảm bảo rằng không có vấn đề gây ra do merge code. Điều này đặc biệt quan trọng khi có nhiều người cùng tham gia phát triển code. Có thể setup công việc này một cách tự động dùng các phần mềm như Jenkins, Hudson để setup trên client
7. Kiểm tra code(Refactor) chỉ sau khi tất cả test đã thành công
Ưu điểm: quá trình kiểm tra code sẽ an toàn, không bị kiểm tra các phần code bị lỗi
Nếu tất cả test chạy thành công thì nó tương đối an toàn để kiểm tra code. Sau khi refactor code thì không cần phải viết thêm test mới, nhưng sẽ cần sửa đổi test code cho những phần đã thay đổi. Điều kiện lý tưởng là, sau khi refactor thì tất cả các test chạy đều thành công
8. Hạn chế sự phụ thuộc giữa các test
Ưu điểm: Test có thể chạy độc lập mà không phụ thuộc vào các test khác
Mỗi test nên độc lập từ các test khác. Người phát triển nên có thể thực thi bất kỳ phương thức test nào đó, hoặc một tập các test độc lập. Nếu có sự phụ thuộc từ các test thì chúng dễ bị ảnh hưởng khi viết các test mới.
9. Các test nên chạy nhanh
Ưu điểm: Các test được dùng thường xuyên, nên test chạy nhanh sẽ tích kiệm thời gian
Nếu test chạy mất nhiều thời gian, người phát triển sẽ dễ dàng dừng chúng hoặc chỉ chạy một tập nhỏ của các test liên quan tới phần họ sắp thay đổi. Rõ ràng điều này là không tốt vì không thể đảm bảo tất cả các test sẽ thành công khi viết test mới hoặc code mới. Lợi ích từ chạy nhanh là bên cạnh việc tích kiệm thời gian, nó còn giúp phát hiện các vấn đề sớm và có thể giải quyết vấn đề sớm.
10. Dùng các đối tượng dữ liệu giả (mocks)
Ưu điểm: Giảm sự phụ thuộc vào code, test chạy nhanh hơn
Mock là điều kiện cần thiết để làm cho các test nhanh hơn bời vì phương thức test không cần kết nối và đợi lấy dữ liệu từ các đối tượng bên ngoài, tất cả dữ liệu cần thiết đã được cung cấp trong đối tượng mock. Nó cũng giúp người phát triển tập trung hơn vào phần test mà họ đang viết không cần quan tâm đến các đối tượng cung cấp dữ liệu. Có rất nhiều công cụ hỗ trợ tạo ra các đội trượng mock khi viết unit test, ví dụ như: mockito, …
11. Dùng setup và tear-down phương thức
Ưu điểm: Cho phép setup dữ liệu trước khi chạy test và reset dữ liệu sau khi test chạy xong
Phương thức setup cho phép cài đặt dữ liệu trước khi chạy unit test, tear-down để reset dữ liệu sau khi test chạy xong. Điều này đảm bảo các test không bị phụ thuộc lẫn nhau. Dữ liệu sinh ra do test sẽ được reset lại sau khi nó chạy xong.
12. Hạn chế dùng base class
Ưu điểm: Làm cho class test rõ ràng
Class test cần rõ ràng và mạch lạc, dó đó chúng ta nên hạn chế dùng base class khi viết unit test.
13. Dùng các công cụ để hỗ trợ test
Ưu điểm: Đo các thông số liên quan đến test, thực hiện test một cách tự động
Code Coverage: Là công cụ đo phần trăm code được test trong dự án. Có thể dùng một trong các tool như SonarQuebe, Clover. Các công cụ này hỗ trợ hầu hết các ngôn ngữ
CI – Continuous integration (CI): Tool để thực hiện các thao tác tự động theo một kế hoạch lập từ trước đó. Có thể dùng các công cụ như: Jenkins, Hudson