Đây là bài viết thứ hai của tôi về unit test, theo dự định trước đó thì tôi muốn viết về các khía cạnh nâng cao hơn so với một ví dụ quá đơn giản về unit test. Nhưng gần đây, qua công việc tôi nhận thấy việc viết unit test sẽ trở lên vô nghĩa nếu developer không nắm được vai trò của unit test những mục tiêu và nguyên tắc để có thể viết unit test tốt hơn.
Unit test có thể làm tăng đáng kể chất lượng dự án của chúng ta, nhưng với một điều kiện là unit test được viết một cách hợp lý và có vai trò nhất quán, rõ ràng. Nếu không, unit test sẽ lấy mất rất nhiều công sức của team mà không mang lại rất ít giá trị và rất khó để có thể nhận ra mục đích của những method unit test như vậy hoặc có thể đơn giản là nó chả có mục đích gì hết, việc tìm hiểu nó chỉ là phí công mà thôi.
A. Unit Test không phải là pass hết test case
Khi tôi bắt đầu viết unit test, hầu như tất cả mọi cố gắng của tôi là làm sao cho tất cả các test case đều xanh. Nhưng mục đích của unit test không phải là như vậy, hãy viết unit test sao cho mọi việc của bạn là modify source code implement chứ không phải là nỗ lực chỉnh sửa code test. Nhìn thấy test case fail mới chính là điều chúng ta mong đợi từ unit test, điều đó có nghĩa là unit test của chúng ta đã hoạt động và thực sự có ý nghĩa. Chúng ta nên vui mừng vì điều đó, qua unit test fail chúng ta nhận thấy rõ ràng có sự sai sót trong code implement và có thể nhanh chóng fix nó tại bước unit test này.
Còn trường hợp thứ hai là chúng ta nhận được phản hồi về sai sót của chương trình từ người khác. Hãy kiểm tra lại test case của class test liên quan, bổ sung unit test còn thiếu và sau đó là run lại method test với hy vọng là test method sẽ fail. Vì như thế bug của chương trình sẽ được khoanh vùng lại tại method test của unit test thôi và một bug unit test sẽ đơn giản hơn rất nhiều một bug từ intergration test.
=> Màu đỏ trong unit test mới chính là thứ chúng ta mong chờ nhiều nhất từ unit test. Vậy mục tiêu của unit test là gì?
B. Unit Test dùng để xác định lại requirement và hỗ trợ cho detail design của chương trình
Có vẻ mục đích tôi đưa ra không liên quan gì nhiều đến từ “unit test”, nhưng qua công việc, tôi thấy rằng đây mới chân chính là mục đích chân chính của việc viết unit test. Chúng tôi làm việc theo mô hình TDD, lên unit test luôn được ưu tiên viết trước khi implement code. Điều đó có nghĩa rằng chúng tôi cần hình dung ra code unit test trong quá trình thiết kế source code, chứ không phải trong quá trình test.
Unit test cần được viết base trên các ý có trong spec requirement, từ các phân tích spec, chúng ta define ra các thành phần phần mềm, các class, interface v.v.. cần thiết và các logic tương tác giữa các unit này được chuyển từ mô tả trong spec sang test case method trong unit test.
1. Unit test mô tả tất cả requirement
Yêu cầu tối thiểu của unit test là không được để sót các ý có sẵn trong spec. Một khi chúng ta đã đạt được yêu cầu tối thiểu của unit test tức là đã đạt được mục tiêu đầu tiên đó là verify, xác định chính xác source code đã tuân theo requirement được yêu cầu.
Và đương nhiên, unit test cũng cần phải được modify, chỉnh sửa mỗi khi có sự thay đổi của requirement liên quan. Hãy đảm bảo các bug hướng chức năng của chương trình luôn được cập nhật vào code unit test vì khi có bug liên quan đến chức năng chương trình, chúng ta luôn phải confirm lại về spec và việc add thêm hoặc chỉnh sửa các test case liên quan là cần thiết. Một khi unit test không bám sát được spec, thì đó chính là unit test chết, nó sẽ mất dần ý nghĩa tồn tại của chinh bản thân code test.
2. Unit test & detail design
Bản thân tôi, khi làm việc theo mô hình TDD, việc viết unit test chính là định hướng căn bản cho việc detail design của chương trình. Tôi sử dụng behavior test case method để xác định khung chương trình trước khi bắt tay vào implement code thực thi.
=> Theo một cách rất tự nhiên, unit test đã định hướng detail desin của class implement tuân theo một số nguyên tắc cơ bản và khiến code trở lên thông thoáng và dễ hiểu hơn rất nhiều.
3. Unit test & Maintaince
Xuất phát từ lý do unit test chính là sự thể hiện trực quan ý hiểu của developer đối với spec => Đọc test case chúng ta có thể hiểu được bối cảnh của logic, các hành vi (behavior) và kêt quả xử lý mong muốn đạt được của logic. Ở bước này, chúng ta cần giữ số lượng test method tối thiểu mà vẫn thể hiện được đầy đủ các unit test, với tên method rõ ràng. Như vậy developer sau có thể dễ dàng chỉnh sửa code test hoặc bổ sung code test do có sự thay đổi từ spec.
Unit test hỗ trợ rất tốt việc refactor source code, vì nó đảm bảo source code sau khi refactor vẫn đáp ứng đủ các yêu cầu requirement đề ra, nếu chúng ta có thể pass tất cả các test case và bộ unit test case là đầy đủ.
Tuy nhiên, unit test không thể trợ giúp các mục đích sau:
4. Unit test không thể phát hiện hết bug của chương trình
Vì đơn giản, unit test là các kịch bản, các hành vi của các thành phần lập trình được viết theo spec và theo ý hiểu của người lập trình. Chúng ta không thể mong chờ unit test xác định được hoàn toàn code chạy chính xác hay chưa. Vì unit test chỉ hỗ trợ xác định tính đúng đắn của các unit lập trình một cách độc lập. Khi kết hợp các class, các module lại với nhau, chúng ta có thể gặp rất nhiều các loại lỗi khác uni test không thể bao phủ hết được. Đặc biệt các yêu cầu phi chức năng thì không thể yêu cầu unit test xác minh được.
Với unit test, chúng ta có thể tránh được hầu hết các lỗi đơn giản của chương trình. Nhưng với các lỗi phức tạp khác thì unit test không thể trợ giúp gì cho bạn được.
5. Pass unit test chưa hẳn code của unit class đã đúng
Nếu bạn viết sai ngày từ công đoạn viết unit test, thì chắc chắn rằng code của bạn cũng sẽ sai và unit test không thể trợ giúp gì cho bạn trong trường hợp này, Việc đầu tiên bạn phải làm là correct lại code unit test và chỉnh sửa lại code thực thi sau đó. Việc chỉnh sửa này sẽ tránh cho tương lai bạn không còn bị những lỗi tương tự xảy ra nữa. Nhưng một cách khách quan thì unit test bó tay với trường hợp này. => Unit test method cũng cần được review một cách kĩ lưỡng.
C. Nguyên tắc viết Unit Test
Sau đây, tôi sẽ đưa ra những nguyên tắc của tôi khi viết unit test, mong các bạn góp ý thêm. Nó trợ giúp tôi rất nhiều khi viết unit test.
1. Viết unit test theo nguyên tắc Top to Down
Hãy bắt đầu viết unit test case cho logic chính và bắt đầu với method test cho behavior, hành vi của method trước. Ví dụ chúng ta có spec như sau: Chức năng Login, người dùng nhập user và password, Nếu đúng redirect tới trang quản lý với user là staff, trang home với user là customer. Nếu sai thì show error message.
Chúng ta bắt đầu với unit test cho case main follow 1: redirectOtherPageWhenPassAuthenticate trong method này chúng ta có 2 behavior nhất định phải qua là: authenticateUser(account, password), (mock method với giả thiết method trả về class User và isAuthenticate = true) và sau đó là redirectPage(user). Chúng ta chưa cần quan tâm đến authenticateUser và redirectPage cần implement như thế nào, chỉ đơn giản là viết các ý theo spec yêu cầu.
case thứ 2 là: showErrorWhenFailAuthenticate, có 2 method: authenticateUser(account, password), (mock method với giả thiết method trả về class User và isAuthenticate = false) và sau đó là showErrorLoginMessage method.
Tiếp theo chúng ta đọc tiếp spec ở các mục nhỏ hơn và dần hoàn thiện unit test code. Ưu điểm của phương pháp này là code chúng ta implement theo hướng tự nhiên nhất có thể, luôn bám sát vào spec. Cả code unit test và code thực thi sẽ dần được hoàn thiện theo cấu trúc của spec.
2. Viết unit test theo đúng nghĩa unit test (độc lập và không phụ thuộc vào các thành phần lập trình khác.)
3. Đặt tên các unit test của bạn một cách rõ ràng và nhất quán
Chắc chắn việc đặt tên rõ ràng và nhất quán là rất quan trọng và cần thiết để cho mọi người có thể hiểu nhanh hơn và rõ hơn về logic bạn định viết.
Unit test cho behavior logic không quan trọng đến giá trị trả về Không giống như assertion test, behavior test không quan trọng đến kết quả chúng ta mong muốn nhận được hay không mà nó như một tấm bản đồ chỉ đường cho ta biết lộ trình của logic cần đi.
Tôi lấy lại ví dụ lúc nãy với chức năng login và ở trường hợp thứ 2: showErrorWhenFailAuthenticate, có 2 method: authenticateUser(account, password), (mock method với giả thiết method trả về class User và isAuthenticate = false) và sau đó là showErrorLoginMessage method. Ở đây chúng ta không cần biết account và password người dùng nhập vào là gì, nhưng chúng ta nên viết như sau: authenticateUser(“invalid_account”, “invalid_password”); result = new User with isAuthenticate = false. => chúng ta cần cân nhắc parameter truyền vào cho unit test cần rõ ràng. Có thể lấy từ data thật hoặc một từ meaning, tránh trường hợp truyền vào như sau: method truncateString(value, length). Có người viết như sau: truncateString(“AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA”, 16) => Nên đổi lại thành truncateString(“length more than 16.length more t”, 16) và nhớ đếm đủ 17 kí tự nhé các bạn.
You need to login in order to like this post: click here
YOU MIGHT ALSO LIKE