[ Agile Software Development, Principles, Patterns, And Practices] Testing

Tiếp nối loạt bài dịch cuốn sách "Agile Software Development, Principles, Patterns, and Practices", ngày hôm nay chúng ta sẽ đến với chương 4 của cuốn sách này. Chương 1 bạn đọc có thể tham khảo ở bài viết Agile Practices. Chương 2, 3 nói về Exteme Programming, và như tôi đã nói ở cuối chương 1, tôi sẽ không phân tích phần này mà sẽ tập trung nói về Scrum sau.

Chương 4. Testing

Fire is the test of gold; adversity, of strong men. - Seneca (c. 3 B.C. - A.D. 65)

(Tiếng Việt: Lửa thử vàng, gian nan thử sức)

Nghệ thuật viết kiểm thử đơn vị giống với nghệ thuật thiết kế hơn là kiểm định thông thường. Nó cũng giống với công việc viết tài liệu hơn là kiểm định. Việc viết kiểm thử đơn vị là một quá trình lặp đi lặp lại, trong đó có một phần nhỏ thuộc về kiểm định các chức năng.

Test Driven Development (Quy trình phát triển định hướng bởi kiểm thử)

Điều gì xảy ra nếu chúng ta thiết kế kịch bản kiểm thử trước khi thiết kế chương trình? Điều gì xảy ra nếu chúng ta từ chối phát triển một chức năng cho đến khi kiểm thử thất bại vì chức năng đó chưa được viết? Điều gì xảy ra nếu chúng ta từ chối viết dù chỉ là 1 dòng code, cho đến khi một bài kiểm thử thất bại chỉ vì không có dòng code đó? Điều gì xảy ra nếu chúng ta liên tục thêm chức năng vào chương trình của mình bằng việc trước tiên viết ra các bài kiểm thử lỗi, và kiểm tra sự tồn taị của chức năng đó để vượt qua bài kiểm tra? Điều đó sẽ gây ảnh hưởng gì đến thiết kế của phần mềm chúng ta đang tạo? Chúng ta sẽ được lợi lộc gì từ hàng tá bài kiểm tra đó?

Ảnh hưởng đầu tiên và hiển nhiên, đó là tất cả các chức năng dù nhỏ nhất được viết lên sẽ được kiểm tra kỹ lưỡng để đảm bảo nó hoạt động. Một tập các bài kiểm tra sẽ đóng vai trò hỗ trợ cho sự phát triển ở mức xa hơn. Nó cho chúng ta biết mỗi khi chúng ta vô tình làm hỏng chức năng hệ thống. Chúng ta có thể thêm chức năng, thay đổi kiến trúc chương trình mà không cần lo sợ mình sẽ làm hỏng cái gì đó quan trọng. Bài kiểm thử cho chúng ta biết chương trình của mình vẫn chạy tốt. Chúng ta sẽ thoải mái hơn khi tiếp tục thay đổi và cải thiện chương trình của mình.

Một điểm quan trọng khác, nhưng không hiển nhiên cho lắm, đó là việc viết các bài kiểm tra trước buộc chúng ta phải có cái nhìn khác về lập trình. Chúng ta cần nhìn chương trình mình sẽ viết ra từ cái nhìn của người gọi chương trình đó. Do vậy, chúng ta cần phải quan tâm đến diện mạo của chương trình cũng như chính các chức năng mình định viết. Bằng việc viết các bài kiểm tra trước, chúng ta đã thiết kế chức năng của mình để nó luôn có thể được gọi đến một cách thuận tiện.

Hơn nữa, bằng cách viết các bài kiểm tra trước, chúng ta buộc chương trình của mình ở trạng thái "có thể kiểm tra được". Việc thiết kế chức năng để nó "có thể gọi được" và "có thể kiểm tra được" là thực sự quan trọng. Để đạt được hai tiêu chí đó, các chức năng cần được tách biệt so với những phần xung quanh. Vì vậy, việc viết các bài kiểm tra trước buộc chúng ta tách biệt các chức năng của hệ thống ra.

Một điều quan trọng nữa là kịch bản kiểm thử đóng vai trò như một tài liệu vô giá. Nếu bạn muốn tìm hiểu cách để gọi một chức năng hay tạo một dự án, kịch bản đó sẽ giúp bạn. Kịch bản kiểm thử đóng vai trò như là một loạt các ví dụ giúp bạn biết cách làm việc với những dòng code đã được viết ra từ trước. Và nó là một tài liệu đó có thể biên dịch và chạy được. Nó sẽ luôn diễn tả những điều đang diễn ra và không bao giờ "nói dối" bạn (nếu đọc bài viết trước bạn sẽ thấy, đến cả tài liệu chính thức của dự án cũng có thể "nói dối" bạn, nếu nó không được cập nhật kịp thời).

Một ví dụ về thiết kế bằng cách viết kịch bản kiểm thử trước (Test-First Design)

Tôi đã viết một phiên bản cho trò chơi "Hunt of Wumpus" (Săn lùng Wumpus), chỉ để giải trí. Đó là một game nhập vai đơn giản, trong đó người chơi di chuyển giữa các hang động, cố gắng giết Wumpus trước khi Wumpus ăn thịt anh ta. Hang động là tập các phòng nhỏ được kết nối với nhau thông qua các hành lang. Mỗi phòng có thể có lối đi đến các hường Bắc, Nam, Đông hoặc Tây. Người chơi di chuyển bằng cách chỉnh các hướng để máy tính có thể thực hiện thao tác di chuyển đó.

Trong bài kiểm tra đầu tiên, tôi viết một chương trình gọi là testMove như dưới đây. Chương trình đó tạo ra một WumpusGame, kết nối phòng 4 với phòng 5 thông qua một cửa hướng Đông, đặt người chơi ở phòng 4. Thực hiện câu lệnh di chuyển sang hướng Đông, và kiểm tra xem người chơi có đang ở phòng 5 hay không.

public void testMove()
{
  WumpusGame g = new WumpusGame();
  g.connect(4, 5, "E");
  g.setPlayerRoom(4);
  g.east();
  assertEquals(5, g.getPlayerRoom());
}

Những dòng code này được viết trước khi bất kỳ phần nào của WumpusGame được viết. Tôi nghe theo lời khuyên của Ward Cunningham và viết bài kiểm tra theo cách mà tôi muốn đọc. Tôi tin rằng tôi có thể làm cho bài kiểm tra thành công bằng cách viết ra các dòng code thỏa mãn cấu trúc của bài kiểm tra. Đó gọi là lập trình có chủ ý (intentional programming). Bạn chỉ rõ ý định của mình trong một bài kiểm tra trước khi viết nó, làm cho ý định của bạn đơn giản và dễ đọc hết sức có thể. Bạn tin răng sự đơn giản và rõ ràng sẽ hướng đến một chương trình được tổ chức tốt.

Lập trình có chú ý đưa tôi đến một quyết định khá thú vị về thiết kế. Bài kiểm tra không hề dùng đến class Room. Việc kết nối các phòng với nhau thể hiện suy nghĩ của tôi. Tôi không có ý định tạo dựng một class Room để cho kết nối giữa các phòng thuận tiện hơn. Thay vào đó, tôi chỉ dùng các số tự nhiên để miêu tả phòng.

Điều này có thể trái với trực giác của bạn. Suy cho cùng, những gì chương trình này mang đến với bạn có lẽ chỉ là những căn phòng, di chuyển giữa những căn phòng, tìm kiếm những gì trong phòng đó có,... Liệu thiết của tôi có thiếu sót khi mà không có class Room.

Theo quan điểm của tôi, suy nghĩ về sự kết nối quan trọng với trò chơi Wumpus hơn nhiều so với suy nghĩ về các phòng. Tôằi cho rằng bài kiểm tra đầu tiên là mốt cách tiếp cận tốt để giải quyết vấn đề. Hơn nữa, đó chỉ là các trường hợp, không phải những gì tôi sẽ cố gắng làm. Những gì tôi cố gắng làm là là viết ra bài kiểm tra để mô phỏng yêu cầu chính trong trạng thái khởi đầu của chương trình. Nghệ thuật viết kịch bản kiểm thử trước là nghệ thuật tách biệt các quyết định thiết kế.

Chú ý rằng bài kiểm tra nói cho bạn biết chương trình hoạt động như thế nào. Hầu hết chúng ta đều có thể viết ra bốn phương thức cho WumpusGame từ yêu cầu đơn giản hiện taị. Chúng ta cũng có thể dễ dàng với thêm 3 câu lệnh di chuyển hướng mà không gặp bất kỳ khó khăn gì. Nếu sau đó chúng ta muốn biết cách mà các phòng được kết nối hay cách di chuyển theo một hướng nhất định, bài kiểm tra này cũng cho chúng ta thấy chúng rất đơn giản. Do vậy, bài kiểm tra này đóng vai trò như một tài liệu có thể biên dịch và chạy được, mô phỏng chương trình của chúng ta.

Cô lập công việc kiểm thử

Việc viết kịch bản kiểm thử trước khi viết chương trình chính dẫn đến việc tách rời các thành phần của chương trình ra. Ví dụ, hình 4-1 chỉ ra một biểu đồ UML đơn giản cho ứng dụng tính lương. class Payroll sử dụng class EmployeeDatabase nhằm lấy thông tin từ đối tượng Employee. Nó yêu cầu Employee tính toán tiền lương. Sau đó nó đưa số liệu đó cho CheckWriter để thực hiện kiểm tra. Cuối cùng thay toán cho đối tượng Employee và cập nhật thông tin trong cơ sở dữ liệu.

4-1.png

Giả sử rằng chúng ta chưa viết bất cứ dòng code nào. Và sơ đồ trên chỉ được đưa ra trên bảng sau một quyết định nhanh chóng. Bây giờ chúng ta cần viết kịch bản kiểm thử cho đối tượng Payroll. Sẽ có rất nhiều vấn đề gặp phải khi thực hiện thao tác này. Thứ nhất, chúng ta sẽ dùng cơ sở dữ liệu nào? Payroll cần đọc thông tin từ cơ sở dữ liệu. Liệu chúng ta có cần viết đầy đủ chức năng cho hệ quản trị cơ sở dữ liệu trước khi kiểm tra class Payroll. Chúng ta sẽ lấy thông tin nào từ đó? Thứ hai, làm sao để chắc chắn rằng bản kê khai tiền lương đã được in ra? Chúng ta không thể viết chương trình tự động theo dõi máy in để kiểm tra những gì nó in ra!

Lời giải cho vấn đề này là sử dụng mô hình MOCK OBJECT (đối tượng giả). Chúng ta có thể bổ sung các lớp thực hiện giao tiếp giữa các lớp liên quan đến Payroll và viết kịch bản kiểm thử cho các lớp này.

Hình 4-2 cho bạn thấy mô hình các lớp. Class Payroll sử dụng các lớp trung gian để giao tiếp với EmployeeDatabase, CheckWriterEmployee. Ba MOCK OBJECTS cần được tạo ra và thực hiện thao tác trung gian này. MOCK OBJECTS được gọi đến từ PayrollTest để đảm bảo chắc chắn Payroll đã sử dụng nó đúng cách.

4-2.png

Đoạn code dưới đây cho thấy kiến trúc cơ bản của việc kiểm tra. Nó tạo ra những MOCK OBJECTS thích hợp, truyền cho đối tượng Payroll, thông báo với Payroll đối tượng nào thực hiện việc chi trả tiền lương, và yêu cầu kiểm tra xem bản kê khai đã chính xác hay chưa, đã được gửi đi đúng quy định hay chưa.

// TestPayroll
public void testPayroll()
{
  MockEmployeeDatabase db = new MockEmployeeDatabase();
  MockCheckWriter w = new MockCheckWriter();
  Payroll p = new Payroll(db, w);
  p.payEmployees();
  assert(w.checksWereWrittenCorrectly());
  assert(db.paymentsWerePostedCorrectly());
}

Rõ ràng quá trình kiểm tra trên đảm bảo rằng Payroll gọi đúng các hàm cần thiết với dữ liệu chính xác. Nó không kiểm tra xem bản kê khai đã được in ra chưa. Nó cũng không thực sự kiểm tra xem cơ sỡ dữ liệu đã được cập nhật hay chưa. Thay vào đó, nó kiểm tra xem class Payroll có thực sự hoạt động đúng theo những gì đã được vạch định ra hay không.

Bạn có thể tự hỏi MockEmployee để làm gì. Sẽ khả thi hơn nếu không phải đối tượng giả, mà chính Employee được khởi tạo. Nếu làm được như vậy, tôi sẽ không có chút hối hận nào khi dùng nó. Tuy nhiên, trong trường hợp này. class Employee phức tạp hơn rất nhiều, so với việc chúng ta chỉ kiểm tra Payroll.

Sự ràng buộc được tách biệt một cách ngẫu nhiên

Việc tách Payroll là một điều tốt. Nó cho phép bạn thoải mái thay đổi cơ sở dữ liệu hoặc máy in, nhằm mục đích đơn giản hóa kiểm thử cũng như khả năng mở rộng của ứng dụng. Tôi cho rằng quá trình tách biệt các lớp được thực hiện bởi ý tưởng của kịch bản kiểm thử. Rõ ràng, yêu cầu tách biệt các chức năng để kiểm thử buộc chúng ta phải tách biệt các lớp sao cho hiệu quả nhất đến kiển trúc tổng thể của hệ thống. Do vậy, viết kịch bản kiểm thử trước khi bắt tay vào làm triển khai hệ thống thực sự giúp cải thiện thiết kế của chúng ta.

Một phần lớn của cuốn sách này nói về các quy tắc thiết kế giúp quản lý các thành phần có sự phụ thuộc vào nhau. Những quy tắc này sẽ hướng dẫn bạn các kỹ thuật để phân tách các lớp và gói phần mềm. Bạn sẽ thấy chúng thực sự hữu dụng nếu bạn thực hành nó cùng với việc thiết kế kiểm thử đơn vị. Kiểm thử đơn vị sẽ đóng vai trò quan trọng trong việc thúc đẩy chúng ta tách biệt các lớp trong chương trình của mình ra.

Acceptance Tests (Kiểm thử chấp nhận)

Kiểm thử đơn vị là cần thiết, nhưng nó không đủ để đóng vai trò một công cụ xác thực cuối cùng. Kiểm thử đơn vị đảm bảo rằng các phần nhỏ của hệ thống sẽ làm việc tốt như mong đợi, nhưng không đảm bảo rằng hệ thống sẽ hoạt động đúng như yêu cầu. Kiểm thử đơn vị là kiểm thử hộp trắng (white-box tests) giúp kiểm tra các thành phần riêng biệt trong hệ thống. Kiểm thử chấp nhận là kiểm thử hộp đen (black-box tests) giúp xác minh những yêu cầu của khách hàng đã được đáp ứng.

Kiểm thử chấp nhận được viết bởi những người không hiểu kiến trúc bên trong của hệ thống. Chúng có thể được viết trực tiếp bởi khách hàng, hoặc ai đó hiểu biết về kỹ thuật có liên hệ với khách hàng, thường là QA (Quality Assurance). Kiểm thử chấp nhận là chương trình, do vậy nó có thể thực thi được. Tuy nhiên, chúng thường được viết bằng các ngôn ngữ script dành riêng cho khách hàng.

Kiểm thử chấp nhận là tài liệu cuối cùng miêu tả các đặc trưng của hệ thống. Một khi khách hàng đã viết ra kịch bản kiểm thử chấp nhận, những thứ xác nhận các đặc trưng của hệ thống, thì lập trình viên có thể đọc theo kịch bản đó và hiểu được hệ thống. Do vậy, bên cạnh việc sử dụng kiểm thử đơn vị để kiểm tra kiến trúc bên trong hệ thống, kiểm thử chấp nhận đóng vai trò kiểm tra các chức năng, đặc trưng của hệ thống.

Hơn nữa, việc viết kịch bản kiểm thử chấp nhận có ảnh hưởng sâu sắc đến kiến trúc của hệ thống. Để hệ thống có thể kiểm tra được, nó cần được tách biệt từ lớp kiến trúc cao nhất. Ví dụ, giao diện người dùng (UI) cần được tách biệt dựa theo các nguyên tắc kinh doanh, theo cách mà kiểm thử chấp nhận có thể truy cập vào các nguyên tắc đó mà không cần phải chạy qua tất cả các màn hình.

Trong những bước đầu tiên của dự án, cách chúng ta làm là thực hiện kiểm thử chấp nhận bằng tay. Đây thực sự là một cách làm không khôn ngoan, bởi nó làm mất đi áp lực phải tách biệt các thành phần ra phục vụ cho tự động hóa quá trình kiểm thử chấp nhận sau này. Khi bắt đầu dự án, nếu bạn hiểu rõ việc cần phải tự động hóa quá trình kiểm thử chấp nhận, thiết kế của bạn sẽ có những thay đổi đáng kể. Và khi đó, giống như kiểm thử đơn vị cho bạn một thiết kế vượt trội ở mức thấp, kiểm thử chấp nhận sẽ giúp bạn có một thiết kế tổng thế chính xác.

Tạo ra một thư viện hay framework cho việc kiểm thử chấp nhận có thể là công việc rất khó khăn. Tuy nhiên, một khi bạn đã làm được những bước đầu tiên và tạo ra những thành phần của thư viện phục vụ cho kiểm thử chấp nhận, bạn sẽ thấy nó không còn quá khó khăn. Dần dần, bạn cũng thấy mình không còn tốn quá nhiều công sức cho việc đó.

Ví dụ về kiểm thử chấp nhận

Cùng xem lại ứng dụng tính toàn tiền lương. Ở bước đầu tiên, chúng ta cần chức năng thêm và xóa nhân viên trong cơ sở dữ liệu. Chúng ta cũng cần tạo ra bảng tính tiền công cho nhân viện đang có trong cơ sở dữ liệu. Thậy may mắn, chúng ta chỉ cần làm việc với những nhân viên được trả lương. Những kiểu nhân viên khác chỉ thực sự xuất hiện trong các bước phát triển tiếp theo.

Chúng ta chưa viết bất cứ dòng code nào, và cũng chưa thực sự điều tra kỹ càng thiết kế của mình. Đây là lúc chúng ta nghĩ về kiểm thử chấp nhận. Một lần nữa lập trình có chủ ý trở nên hữu dụng. Chúng ta cần viết kiểm thử chấp nhận theo cái cách mà chúng ta nghĩ nó sẽ xảy ra, và chúng ta có thể tổ chức ngôn ngữ cho kiểm thử chấp nhận cũng như hệ thống của mình dựa theo sự tổ chức đó.

Tôi muốn kiểm thử chấp nhận cần được viết một cách đơn giản và dễ thay đổi. Tôi muốn nó được đặt trong một công cụ quản lý các thiết lập, để tôi có thể lưu lại và thực hiện nó bất cứ lức nào tôi muốn. Do vậy, kiểm thử chấp nhận cần được viết ở một tập tin văn bản đơn giản.

Sau đây là một ví dụ về đoạn mã kiểm thử chấp nhận

AddEmp 1429 "Robert Martin" 3215.88
Payday
Verify Paycheck EmpId 1429 GrossPay 3215.88

Trong ví dụ này, chúng ta đã thêm nhân viên với số hiệu 1429 vào cơ sở dữ liệu. Tên nhân viên này là "Robert Martin", và tiền lương tháng của anh ta là 3215.88$. Tiếp theo chúng ta thông báo cho hệ thống là đã đến ngày trả lương cho nhân viên. Cuối cùng, chúng ta kiểm tra rằng biên bản lương đã được tạo ra và nhân viên có mã số 1429GrossPay3215.88$.

Rõ ràng, dạng ngôn ngữ này rất dễ dàng để khách hàng có thể viết. Chúng ta cũng rất dễ dàng bổ sung thêm chức năng cho loại mã này. Tuy nhiên, chúng ta cần xem xét xem nó có ý nghĩa gì đến hệ thống của mình.

Hai dòng đầu tiên là chức năng của hệ thống tính lương. Chúng ta gọi đó là giao dịch chi trả lương. Đó là những gì người dùng yêu cầu. Tuy nhiên, dòng xác nhận Verify không phải là một giao dịch mà người dùng mong chờ. Nó nhằm tới kiểm thử chấp nhận.

Do vậy, thư viện sử dụng cho việc kiểm thử chấp nhận cần phải đọc, phân tích tập tin trên, tách biệt các giao dịch của hệ thống với câu lệnh kiểm tra. Nó cần đưa các giao dịch của hệ thống đến ứng dụng của chúng ta, đồng thời thực hiện các thao tác kiểm tra để xác thực thông tin và chức năng của hệ thống.

Đây thực sự tạo ra khó khăn cho thiết kế chương trình của chúng ta. Chương trình cần chấp nhận đầu vào trực tiếp từ người dùng cũng như từ bài kiểm thử chấp nhận. Chúng ta cần làm được cả hai điều này sớm nhất có thể. Do vậy, chương trình tính lương của chúng ta sẽ cần một bộ xử lý các giao dịch, có thể đáp ứng được giao dịch từ AddEmpPayDay từ nhiều hơn một nguồn. Chúng ta cần tìm những điểm chung cho các giao dịch này, do đó giảm tối thiểu những dòng code đặc biệt.

Một lời giải cho vấn đề này là đưa giao dịch vào tập tin XML. Thư viện kiểm thử chấp nhận cần sinh ra tập tin XML, và cả giao diện của ứng dụng cũng cần làm được điều đó. Khi đó, giao dịch sẽ có dạng như sau.

<AddEmp PayType=Salaried>
  <EmpId>1429</EmpId>
  <Name>Repobert Martin</Name>
  <Salary>3215.88</Salary>
</AddEmp>

Các giao dịch này có thể được truyền vào ứng dụng của chúng ta thông qua một lời gọi chương trình con hoặc một loạt các tập tin đầu vào. Thực ra, vấn đề thay đổi cách truyền thông tin vào ứng dụng không phải là vấn đề gì quá phức tạp. Do vậy, trong quá trình phát triển, chúng ta có thể quyết định đọc từ tập tin đơn giản trước, sau đó mới nghĩ đến những cách phức tạp hơn.

Làm cách nào để thư viện kiểm thử chấp nhận có thể gọi đến câu lệnh Verify? Một cách rõ ràng, chúng ta cần cách nào đó để truy cập dữ liệu tạo ra bởi ứng dụng tính lương. Một lần nữa, chúng ta không cần thư viện của mình phải đọc được thông tin từ máy in, nhưng chúng ta có thể làm những điều tốt hơn nữa.

Chúng ta có thể tạo ra ứng dụng có thể sinh ra bản kê khai lương dạng XML. Thư viện của chúng ta có thể đọc tập tin XML đó và kiểm tra dữ liệu. Quy trình cuối cùng về việc in ra bản kê khai từ tập tin XML có thể kiểm tra một cách đơn giản bằng tay.

Do vậy, ứng dụng tính lương có thể tạo ra tâpj tin XML chứa thông tin về bảng lương. Nó có thể như sau.

<Paycheck>
  <EmpId>1429</EmpId>
  <Name>Robert Martin</Name>
  <GrossPay>3125.88</GrossPay>
</Paycheck>

Rõ ràng, thư viện cho việc kiểm thử chấp nhận cần thực hiện thao tác Verify khi nó được cung cấp tập tin XML.

Một lần nữa, chúng ta có thể tách XML với các kỹ thuật phức tạp khác. Ở những bước đầu tiên, sử dụng các tập tin đơn giản là cách làm dễ dàng nhất. Do vậy, ứng dụng tính lương sẽ bắt đầu bằng đọc giao dịch từ tập tin XML và xuất ra tập tin XML cho bảng kê khai lương. Thư viện dùng cho kiểm thử chấp nhận sẽ đọc các giao dịch đó dưới dạng văn bản, truyển đổi sang định dạng XML và viết chúng ra tập tin riêng biệt. Sau đó nó sẽ gọi đến chương trình của chúng ta. Cuối cùng đọc tập tin XML được sinh ra từ chương trình và thực hiện thao tác kiểm tra Verify.

Kiến trúc đến một cách tình cờ

Hãy nhớ đến áp lực mà kiểm thử chấp nhận đặt lên hệ thống của chúng ta. Rõ ràng là quá trình kiểm thử đưa chúng ta đến việc sử dụng tập tin XML cho các tập tin đầu vào và đầu ra. Kiến trúc này đã tách riêng các giao dịch với ứng dụng của chúng ta. Nó cũng tách quá trình kiểm tra bảng kê khai từ ứng dụng hiện tại. Đó là những quyết định thiết kế thực sự tốt.

Kết luận

Việc thực hiện kiểm thử đơn giản giúp chúng ta có thêm nhiều cơ hội thực hiện thao tác đó. Việc chúng ta thực hiện kiểm thử càng nhiều, chúng ta càng nhanh chóng tìm ra các sai sót trong hệ thống. Nếu chúng ta thực hiện tất cả các bài kiểm tra nhiều lần trong ngày, hệ thống sẽ không bao giờ sụp độ thậm chị là trong vài phút. Đây thực là một mục tiêu hợp lý. Chúng ta không bao giờ cho phép hệ thống của mình thụt lùi. Một khi nó đã hoạt động, nó không thể bị kéo tụt về trạng thái chất lượng thấp hơn.

Rõ ràng, kiểm tra xác minh là một trong những lợi ích của kiểm thử. Cả kiểm thử đơn vị và kiểm thử chấp nhận đều đóng vai trò tài liệu. Đó là tài liệu có thể biên dịch và chạy được, do vậy chúng chính xác và đáng tin cậy. Hơn nữa, kịch bản kiểm thử được viết bởi ngôn ngữ rõ ràng, dó vậy chúng có thể được đọc đơn giản bới những bên liên quan. Lập trình viên có thể đọc kiểm thử đơn vị bởi nó được viết bằng ngôn ngữ lập trình họ sử dụng. Khách hàng có thể đọc được kịch bản kiểm thử chấp nhận, bởi chúng được viết bằng ngôn ngữ do chính họ thiết kế.

Có lẽ, ích lợi lớn nhất của công việc kiểm thử này, là ảnh hưởng của nó đến kiến trúc và thiết kế. Muốn một chức năng hay ứng dụng có thể kiểm tra được, nó cần được tách biệt với những phần khác. Nó càng có thể kiểm tra được nhiều bao nhiêu thì nó càng được tách biệt rõ ràng bấy nhiêu. Rõ ràng, việc thiết kế kịch bản kiểm thử đơn vị và kiểm thử chấp nhận một cách chi tiết có ảnh hưởng sâu sắc đến kiến trúc của phần mềm.

Quan điểm cá nhân

Cho đến thời điểm này thì tôi đã từng viết kiểm thử đơn vị cũng như kiểm thử chấp nhận. Cá nhân tôi cho rằng việc viết kịch bản kiểm thử trước khi bắt đầu phát triển các chức năng của hệ thống là điều rất khó khăn. Một là kịch bản kiểm thử của bạn cũng có thể sai khi bạn chưa nắm rõ yêu cầu của hệ thống. Kiểm thử trên một kịch bản sai sẽ gây ra hậu quả tai hại. Hai là bạn chưa có đủ khả năng để thiết kế, xem hệ thông sẽ có những thành phần tách biệt nào và chúng hoạt động ra sao. Bạn thường có ý niệm mơ hồ về hệ thống, bắt tay code rồi mới suy nghĩ kỹ hơn về nó.

Từ kinh nghiệm bản thân, tôi cho rằng có thể viết song song hai quá trình: viết kiểm thử và viết chức năng hệ thống, trong đó ưu tiên viết kịch bản kiểm thử trước. Việc này thực sự hữu ích cho những lập trình viên còn ít năm kinh nghiệm cũng như chưa quen với viết kịch bản kiểm thử.

Cuối cùng, đúng như điều tác giả cuốn sách có nhắc đi nhắc lại, viết kiểm thử đóng vai trò quan trọng trong thiết kế hệ thống của bạn. Việc tách biệt các phần của hệ thống để nó có thể kiểm tra được, biến hệ thống của bạn trở thành một hệ thống tốt, trong đó các thành phần hoạt động độc lập, rất thuận tiện cho phát triển lâu dài. Nếu bạn bắt tay phát triển chức năng trước khi viết kịch bản kiểm thử, bạn có thể sẽ phải tái cấu trúc lại chương trình, để nó trở thành một thứ có thể kiểm tra được. Nhưng dù mất thời gian, thì đó cũng là một công đoạn rất có ích cho sự phát triển xa hơn.