1. TDD là gì?

Chính xác với nghĩa đen của nó: “Test-Driven Development” có thể được tạm hiểu là mô hình phát triển với trọng tâm hướng về việc kiểm thử. TDD được xây dựng theo hai tiêu chí: Test-First (Kiểm thử trước) và Refactoring (Điều chỉnh mã nguồn) [1]. Trong đó, khi một yêu cầu phần mềm (requirement) được đặt ra:

  • Người developer soạn thảo kịch bản kiểm thử (test case) cho yêu cầu đó trước tiên và chạy thử kịch bản đó lần đầu tiên. Hiển nhiên, việc chạy thử sẽ đưa ra 1 kết quả thất bại vì hiện tại chức năng đó chưa được xây dựng (và thông qua kết quả đó, ta cũng kiểm tra được là kịch bản kiểm thử đó được viết đúng).
  • Theo đó, dựa vào mong muốn (expectation) của kịch bản kia, người developer sẽ xây dựng một lượng mã nguồn (source code) vừa đủ để lần chạy thứ 2 của kịch bản đó thành công.
  • Nếu trong lần chạy thứ 2 vẫn đưa ra 1 kết quả thất bại, điều đó có nghĩa là thiết kế chưa ổn và người developer lại chỉnh sửa mã nguồn và chạy lại kịch bản đến khi thành công.
  • Khi kịch bản kiểm thử được chạy thành công, người developer tiến hành chuẩn hóa đoạn mã nguồn (base-line code) và tiếp tục hồi quy với kịch bản kiểm thử tiếp theo. Việc chuẩn hóa bao gồm thêm các comment, loại bỏ các dư thừa, tối ưu các biến…

Quy trình phát triển TDD vẽ bởi Colin Kloes, đề cập vấn đề khó khăn trong việc hiểu rõ yêu cầu chức năng trước khi viết kịch bản kiểm thử

2. Điểm khác biệt của TDD với một mô hình truyền thống Waterfall là gì?

Thông qua quy trình TDD trình bày ở trên, ta có thể dễ dàng nhận ra là thứ tự các bước xây dựng 1 tính năng phần mềm gần như đã được đảo ngược so với 1 quy trình truyền thống. Vậy điều này giúp ích gì và có gì hay hơn?

Trước hết, hãy nhìn lại 1 mô hình thác đổ (Waterfall Model) thông thường: việc phân tích các yêu cầu (requirements) thường được tiến hành bởi Business Analyst (BA) 1 cách chuyên hóa và khi đến giai đoạn xây dựng (implementing phase) thì đa phần các developer tiếp xúc với các yêu cầu phần mềm dưới dạng các bản thiết kế. Họ chỉ quan tâm đến đầu vào, đầu ra (Input, Output) của tính năng mình xây dựng mà thiếu đi cái nhìn thực tiễn từ góc nhìn người dùng (end-users). Một hệ quả tất yếu là lỗi phần mềm đến từ việc sản phẩm ko tiện dụng với người dùng.

Cùng với Agile, việc ứng dụng TDD góp phần làm gần khoảng cách giữa đội ngũ thiết kế phần mềm và sản phẩm thực tiễn, tối ưu quy trình [2]. Cụ thể như sau:

  • Thông qua kịch bản kiểm thử, developer có cái nhìn trực quan về sản phẩm ngay trước khi xây dựng mã nguồn. Sản phẩm họ tạo ra chính xác và gần gũi người dùng hơn.
  • Phần mã nguồn được thêm vào chỉ vừa đủ để chạy thành công kịch bản kiểm thử, hạn chế dư thừa và qua đó hạn chế khả năng xảy ra lỗi trên những phần dư thừa.
  • Bảo đảm mã nguồn luôn phản ánh đúng và vừa đủ yêu cầu phầm mềm, hạn chế được công sức tối ưu mã nguồn về sau.

3. TDD và Agile

Trong quá trình hình thành, TDD có liên quan mật thiết đến khái niệm “Test-First Programming” trong mô hình eXtreme Programming “XP” thuần túy Agile – thịnh hành từ năm 1999 [3]. Tuy nhiên, bằng việc ứng dụng đa dạng và linh hoạt, TDD cũng có những đặc điểm và tùy biến của riêng nó (mình sẽ trình bày rõ hơn bên dưới).

TDD trong Agile framework phác họa bởi Mohammad Sami

TDD đáp ứng “Tuyên ngôn về Agile” khi bản thân quy trình TDD thúc đẩy tính thực tiễn của sản phẩm, tương tác với người dùng. Để phát huy tối đa những lợi ích mà TDD mang lại, độ lớn của 1 đơn vị tính năng phần mềm (unit of function) cần đủ nhỏ để kịch bản kiểm thử dễ dàng được xây dựng và đọc hiểu, công sức debug kịch bản kiểm thử khi chạy thất bại cũng giảm thiểu hơn.

Thực tế cho thấy một số sự kết hợp giữa TDD và mô hình Agile khác như Scrum có thể hỗ trợ và tối ưu lợi ích của nhau. Ví dụ, việc chia nhỏ Backlog thành các User Story của Scrum khiến việc xây dựng kịch bản kiểm thử hướng TDD trở nên dễ dàng và thuận tiện. Thêm vào đó, cả Scrum và TDD tương đồng trong việc loại bỏ sự chuyên hóa về vai trò của bộ đôi Developer – Tester. Vì lý do đó, đôi lúc có thể bạn sẽ thấy vừa TDD vừa Scrum được áp dụng trong cùng 1 dự án.

4. Behavior-Driven Development – BDD

Như vậy, trong mô hình TDD nhiệm vụ kiểm thử do developer đảm nhiệm và vai trò chuyên hóa của người tester gần như không còn nữa. Chắc hẳn các bạn sẽ tự hỏi: “Vậy một Acceptance Tester như chúng ta có vai trò gì trong mô hình?”, “Tại sao tôi phải hiểu về TDD khi người ta ko cần tôi trong quy trình đó?”

Quả thực, trong mô hình TDD người Acceptance Tester thực sự đã chết. Tuy nhiên, việc cộng gộp vai trò phát sinh vấn đề quá tải cho người developer [2]. Để làm tốt công việc, xuyên suốt chu trình người developer phải chú ý thêm những vấn đề thuần túy của kiểm thử (test) như: “Cái gì cần test và cái gì không?” “Viết bao nhiêu kịch bản là đủ?” “Làm sao để hiểu là test đó thất bại?” “Bắt đầu test từ đâu?” …

Để giải quyết vần đề phát sinh mà vẫn tận dụng triệt để lợi ích mà TDD mang lại, Dan North phát triển một mô hình mới với tên gọi: Behavior-Driven Development – BDD (hoặc ta có thể hiểu là Acceptance Test-Driven Development – ATDD) [5]. Trong đó, một vai trò mới trong việc thiết kế kiểm thử (Test Design) được đặt ra:

  • Thay vì chờ đợi sản phẩm hoàn thành và kiểm thử, người tester/analyst tham gia vào quá trình xây dựng mã nguồn với vai trò phân tích và xây dựng hệ thống kịch bản kiểm thử dưới góc độ ngôn ngữ tự nhiên dễ hiểu từ các yêu cầu (requirement).
  • Đồng thời, họ giúp đỡ developer trong việc giải thích và đưa ra các phương án xây dựng mã nguồn mang tính thực tiễn với người dùng ngay trước khi bắt tay xây dựng.
  • Người developer liên hệ mật thiết với người tester và xây dựng mã nguồn với những phương án mà tester cung cấp theo mô hình TDD.
  • Kịch bản kiểm thử được phân chia làm 2 lớp: Lớp chấp nhận (feature/acceptance test) và Lớp đơn vị (unit test) [1].
  • Theo đó, kịch bản kiểm thử lớp đơn vị mang thuần tính thiết kế và phục vụ cho việc kiểm thử lớp đơn vị (Unit test) còn kịch bản kiểm thử lớp chấp nhận có thể được tái sử dụng cho quá trình kiểm thử hồi quy về sau (Regression Test)

Mô hình BDD – TDD trong Agile mô phỏng bởi Paul Littlebury

Từ mô hình trên ta dễ dàng nhìn nhận được sự ưu việt BDD mang lại đặc biệt là trong các dự án phần mềm lớn và phức tạp, khi cả hai khía cạnh phân hóa vai trò và chất lượng phải đi đôi. Ngoài ra, việc chạy kịch bản kiểm thử và xử lý sớm các vấn đề thiết kế ngay trong khâu xây dựng giúp giảm thiểu tối đa chi phí và công sức sữa chữa lỗi.

Trong khi khái niệm BDD mang tính lý thuyết, việc ứng dụng của nó lại đặt nặng sự thực nghiệm. Để phát huy lợi ích về thời gian trong việc xây dựng kịch bản kiểm thử, ngôn ngữ và cách truyền tải là 1 thử thách khi phải đáp ứng khả năng đọc hiểu từ cả 2 khía cạnh: tự nhiên và thiết kế. Bằng sự vay mượn từ ngôn ngữ viết User Story, ngôn ngữ Gherkin được phát triển để phục vụ nhu cầu đó với cấu trúc đơn giản, hướng đối tượng và tương đồng cho mọi kịch bản: Given – When – Then (mình sẽ trình bày rõ hơn về ngôn ngữ này ở các loạt bài khác)