How to Write Testable Code

bài viết Introduction to Unit Test đã nói lên được sự quan trọng của Unit Test. Nhưng trước khi áp dụng được Unit Test vào dự án hiện tại hoặc tương lai thì ta cần phải có một “bước đệm” đó học cách viết Testable Code (là Production Code cho phép thực hiện Unit Test dễ dàng). Việc này theo mình là khó khăn và vì thế bài viết này sẽ tổng hợp một số kinh nghiệm của mình để giúp bạn thực hiện việc này dễ dàng hơn.

Trước khi đọc nội dung bài viết. Mình mong bạn hãy đọc những bài sau để có kiến thức nền cho bài viết này:

Write a simple Unit

Khi ta viết một Unit[1] đơn giản sẽ giúp ích việc Unit Test trở nên đơn giản vì các yếu tố sau:

  • Số Test Case cho Unit ít hơn.
  • Các dependency mà Unit phụ thuộc cũng ít hơn từ đó việc chuẩn bị hoặc Mock[2] cho các Test Case cũng trở nên đơn giản.
  • Hạn chế việc miss Test Case.

Vậy đối với những yêu cầu phức tạp ta cần làm gì?

Câu trả lời là tách yêu cầu trên thành các yêu cầu nhỏ hơn và giải quyết từng yêu cầu. Giống việc áp dụng chiến thuật “chia để trị” vậy đó ;))

Separation Between Logic and Presentation

Việc tách biệt giữa code xử lý logic và code lưu trữ, hiển thị hoặc call API là việc bắt buộc khi muốn dự án của ta có thể dễ dàng bảo trì và mở rộng về sau. Ý tưởng tách biệt này là ý tưởng chính trong các mô hình MVC, MVVM… Vì thế mà ngay cả khi chiến lược đảm bảo chất lượng của dự án không yêu cầu Unit Test thì ta cũng nên thực hiện việc này.

Ngoài ra việc tách biệt này cũng sẽ giúp cho việc đảm bảo Consistency Unit ở phần sau được thực hiện.

Consistency Unit

Tính Consistency ở đây được biểu hiện qua việc với cùng một input thì Unit luôn trả về duy nhất một output. Việc này giúp việc kiểm tra input và output như mình đề cập ở Introduction to Unit Test có thể được thực hiện.

Một số trường hợp không thể thực hiện tính Consistency như:

  • Hàm liên quan tới random.
  • Các hàm liên quan tới thời gian hiện tại như tính tuổi hiện tại từ ngày sinh, số ngày chênh lệch ở một thời điểm nào đó so với hiện tại…
  • Các lời gọi tới external system như DB, API, Cache…
  • Các lời gọi liên quan tới context như thread context, security context, scope context…

Và mình đề xuất 2 cách giải quyết các trường hợp trên như sau:

  1. Tách code logic và code inconsistency kia thành 2 phần riêng biệt. Lúc này input của code logic sẽ là output của code inconsistency. Và ta chỉ Unit Test phần code logic.
  2. Tương tự #1 khi tách ra 2 phần riêng biệt. Áp dụng IoC/DI, lúc này code logic là Client, code inconsistency là Service Implement. Và cũng chỉ cần Unit Test phần code logic với việc Mock2 phần code inconsistency.

Apply Ioc/DI

Việc áp dụng IoC/DI giúp ta có thể dễ dàng Mock2 các phụ thuộc cho các class cần Unit Test một cách dễ dàng. Ngoài ra việc áp dụng IoC/DI cũng sẽ giúp thỏa được nguyên lý DIP ở phần dưới.

Apply S.O.L.I.D principles

Việc áp dụng S.O.L.I.D đặc biệt có lợi cho việc viết Unit Test vì:

  • Single responsibility priciple: giữ cho các class chứa các Unit cần test đơn giản nhất có thể. Từ đó số lượng Test Case cho class được giảm xuống.

  • Open/Closed principle: Giúp ta không phải viết lại các Test Case cho phần code cũ khi cần mở rộng tính năng.

  • Liskov substitution principe: Đây là nguyên lý khó giải thích được sự liên hệ của nó với Testable Code :(. Ở đây mình giả sử class A phụ thuộc class B, ta đã Unit Test class A và class B rồi. Vậy khi mình viết thêm một class C kế thừa class B và thay thế class B bằng class C lúc này ta chỉ cần Unit Test thêm class C mà không cần phải Unit Test lại class A nếu thỏa LSP. Nếu chương trình thỏa nguyên lý này. Ta sẽ không cần lại Unit Test những class đã được

  • Interface segregation principle: nguyên lý này đi kèm với SRP sẽ giúp đơn giản hóa việc Unit Test cho các class implement các interface thỏa nguyên lý này.

  • Dependency inversion principle: Đây là nguyên lý quan trọng để có thể viết Testable Code được. Nếu bạn đã áp dụng IoC/DI như phần trên thì nguyên lý này cũng tự động thỏa.

Kết: Để viết được Testable Code không phải dễ nhưng khi đã làm được thì xem như bạn có thể bước một chân vào thế giới của các developer “xịn” rồi đó. Ngoài ra viết Testable Code nên thực hiện càng sớm càng tốt vì sẽ tốn rất nhiều thời gian, công sức và rủi ro để chuyển code thường sang Testable Code.

Cảm ơn các bạn đã đọc tới đây và mong các bạn sẽ thích bài viết này.

Reference articles

  1. Unit ở đây đối với lập trình hướng đối tượng là method, đối với lập trình hàm là function, đối với lập trình thủ tục là procedure

  2. Mock hiểu đơn giản là cách để giả lập hành vi của các class, method bên ngoài Unit đang được Unit Test. Còn chi tiết thì ở Use Mock to make Unit Test easy  ↩︎ ↩︎

updatedupdated2021-09-192021-09-19
Load Comments?