Viết các đơn vị kiểm thử nhanh chóng, hiệu quả đòi hỏi nhiều kinh nghiệm của người kiểm thử (Testers). Tuy nhiên, có một vài nguyên tắc giúp định hướng cho những người ít kinh nghiệm hay mới bắt đầu kiểm thử để hạn chế tối đa rủi ro. Một trong những nguyên tắc đó là RIGHT – BICEP.

RIGHT – BICEP là gì?

Nguyên tắc tập trung vào các câu hỏi như sau:

  • RIGHT: Các kết quả là đúng hay chưa?
  • B (Boundary conditions ): Các điều kiện biên (Boundary conditions) đã chính xác chưa?
  • I (Inverse relationships): Có thể kiểm tra các mối quan hệ đảo chiều (Inverse relationships)?
  • C (Cross – Check): Có thể kiểm tra chéo (Cross-Check) theo những cách khác nhau?
  • E (Error conditions): Có thể kích hoạt các điều kiện lỗi (Error conditions) xảy ra được không?
  • P (Performance characteristics): Các đặc điểm hiệu suất (Performance characteristics) có giới hạn không?

 RIGHT

Điều đầu tiên khi kiểm thử là kiểm tra xem kết quả có đúng như mong đợi hay không. Các đơn vị kiểm thử chúng ta xây dựng trong bài trước là dễ dàng nhưng trong nhiều trường hợp thì phức tạp hơn. Câu hỏi chúng ta cần đặt ra là:

Nếu chương trình chạy chính xác thì làm cách nào để tôi biết?

Chúng ta không thể trả lời chính xác câu hỏi này thì việc viết chương trình hay các kiểm thử chỉ lãng phí thời gian. Khái niệm “chính xác” (correct) thay đổi theo thời gian sống của chương trình và có thể kết quả “sai” theo quan điểm người dùng nhưng chúng ta cần biết chính xác mình đang làm gì và kết quả mong đợi là gì để thuyết phục khách hàng hay người dùng. Tất nhiên, thỉnh thoảng những góp ý của người dùng là chính xác và là một kênh tham khảo cần được xem trọng.

Khi kiểm thử với số lượng lớn dữ liệu chúng ta có thể lưu dữ liệu hay kết quả trong các tập tin dữ liệu chú thích một cách chi tiết. Một ví dụ về tập tin kiểu này:


#
# Simple tests:
#
9 7 8 9
9 9 8 7
9 9 8 9
#
# Negative number tests:
#
-7 -7 -8 -9
-7 -8 -7 -8
-7 -9 -7 -8
#
# Mixture:
#
7 -9 -7 -8 7 6 4
9 -1 0 9 -7 4
#
# Boundary conditions:
#
1 1
0 0
2147483647 2147483647
-2147483648 -2147483648

Trong một vài trường hợp, dữ liệu dùng cho việc kiểm thử có thể sai do quá trình tính toán (tính nhẩm, tính tay hay dùng một vài hệ thống khác) trước khi thực thi các đơn vị kiểm thử. Do đó, cần kiểm tra dữ liệu một cách cẩn thận trước khi đưa ra bất cứ kết luận nào về tính chính xác của kết quả kiểm thử.

Trong những trường hợp khác, nếu phát hiện chương trình chưa được thực hiện kiểm tra các trường hợp ngoại lệ đầy đủ, chúng ta cũng có thể viết các phương thức (helper methods) xử lý các ngoại lệ này.

Điều kiện biên (B)

Khi thực hiện thao tác với thêm một phần tử đến ngăn xếp (stack) chúng ta cần kiểm tra ngăn xếp có đầy không hay khi xóa một phần tử khỏi ngăn xếp chúng ta cần kiểm tra ngăn xếp đó có rỗng hay không. Đó chính là các ví dụ về điều kiện biên.

Xác định chính xác điều kiện biên là phần quan trọng nhất trong kiểm thử vì đây là nơi dễ phát sinh lỗi nhất. Một trong những cách xác định điều kiện biên hiệu quả nhất là tuân theo nguyên tắc CORRECT.

Nguyên tắc CORRECT xác định điều kiện biên xuất phát từ các từ:

  • C – Conformance (Tương ứng): Giá trị có tương ứng với định dạng mong đợi không?
  • O – Ordering (Thứ tự): Tập giá trị sắp thứ tự hay không sắp thứ tự?
  • R – Range (Phạm vi): Phạm vi với giá trị nhỏ nhất và lớn nhất hợp lý chưa?
  • R – Reference (Tham chiếu): Đoạn mã có thêm chiếu đến bất kỳ thứ gì bên ngoài vượt quá sự kiểm soát của đoạn mã đó?
  • E – Existence (Tồn tại): Giá trị có tồn tại hay không? (khác null, khác không, v.v.)
  • C – Cardinality (Số lượng): Có đầy đủ giá trị chưa?
  • T – Time (Thời gian trong ý nghĩa tương đối và tuyệt đối): Mọi thứ xảy ra đúng thời điểm? Đúng trình tự? Đúng giờ?

Vì điều kiện biên rất quan trọng nên chúng ta sẽ tìm hiểu kỹ hơn về nguyên tắc này trong bài kế tiếp.

Kiểm tra các quan hệ đảo chiều (I)

Một vài phương thức có thể được kiểm tra bằng cách áp dụng quan hệ đảo chiều. Ví dụ, chúng ta có thể kiểm tra phương thức tính căn bậc hai của một giá trị bằng cách bình phương kết quả và xem có bằng (hay gần bằng) giá trị gốc hay không như đoạn mã sau:


[Test]
public void SquareRootUsingInverse() {
   double x = MyMath.SquareRoot(4.0);
   Assert.That(4.0, Is.EqualTo(x*x).Within(0.0001));
}

Hay chúng ta có thể kiểm tra một bản ghi được chèn vào cơ sở dữ liệu thành công hay không bằng cách tìm kiếm hay xóa bảng ghi đó trong cơ sở dữ liệu.

Nhưng vì kiểm tra đảo chiều sử dụng cả phương thức gốc và phương thức đảo chiều của nó nên sẽ tiềm ẩn các lỗi có thể xảy ra từ cả hai phương thức. Một cách để ngăn ngừa lỗi từ cách thức kiểm tra này là sử dụng các nguồn kiểm thử khác khi kiểm tra đảo chiều. Ví dụ kiểm tra phương thức SquareRoot ở đoạn mã trên chúng ta dùng phép nhân thông thường hay sử dụng phương thức tìm kiếm hoặc xóa bản ghi trong cơ sở dữ liệu từ một nhà cung cấp độc lập nào đó.

Kiểm tra chéo bằng nhiều cách khác nhau (C)

Một cách kiểm thử khác là sử dụng nhiều cách hay phương thức khác nhau để kiểm tra xem với cùng một tập giá trị đầu vào có cho ra cùng kết quả đầu ra hay không. Ví dụ kiểm tra kết quả phương thức SquareRoot bằng cách so sánh với kết quả của phương thức Sqrt từ lớp Math:

[Test]
public void SquareRootUsingStd() {
  double number = 3880900.0;
  double root1 = MyMath.SquareRoot(number);
  double root2 = Math.Sqrt(number);
  Assert.That(root2, Is.EqualTo(root1).Within(0.0001));
}

Kích hoạt các lỗi xảy ra ( E)

Trước khi triển khai ứng dụng trong thực tế, một trong những cách kiểm thử hiệu quả là kích hoạt các lỗi mà ứng dụng có thể gặp phải bằng một vài kỹ thuật đặc biệt. Ví dụ, chúng ta muốn biết ứng dụng sẽ phản ứng ra sao nếu bộ nhớ hay không gian đĩa bị tràn, mạng chập chờn, v.v.

Đây là chủ đề khá quan trọng và chúng ta sẽ còn gặp lại trong các bài viết sau về Testing.

Các đặc điểm hiệu suất (P)

Các đặc điểm hiệu suất không phải bản thân hiệu suất mà là các khuynh hướng như kích cỡ đầu vào tăng đột ngột, độ phức tạp của vấn đề hay thuật toán, v.v.  Để ngăn ngừa những lỗi kiểu này, chúng ta cần thiết kế một vài đơn vị kiểm thử với các ràng buộc (hay giới hạn) để đảm bảo rằng đường cong hiệu suất của ứng dụng được duy trì một cách ổn định.

Giả sử chúng ta muốn viết một ứng dụng lọc các website độc hại. Đoạn mã làm việc hiệu quả với danh sách vài trăm hay vài nghìn website nhưng liệu có hiệu quả với danh sách chứa hàng chục nghìn hay hàng trăm nghìn website? Đoạn mã sau là một đơn vị kiểm thử dùng để kiểm tra ứng dụng lọc của chúng ta:


[TestFixture]
public class FilterTest
{
  Timer timer;
  String naughty_url = "http://www.xxxxxxxxx.com";
  URLFilter filter;

  [SetUp]
  public void Initialize()
  {
    timer = new Timer();
  }
  [Test]
  public void SmallList()
  {
    filter = new URLFilter(SMALL_LIST);
    timer.Start();
    filter.Check(naughty_url);
    timer.End();
    Assert.That(timer.ElapsedTime, Is.LessThan(1.0));
  }
  [Test]
  [Category("Long")]
  public void HugeList()
  {
   filter = new URLFilter(HUGE_LIST);
   timer.Start();
   filter.Check(naughty_url);
   timer.End();
   Assert.That(timer.ElapsedTime, Is.LessThan(10.0));
  }
}

Đơn vị kiểm thử này mất khoảng 6-7 giây để chạy nên chúng ta không muốn nó chạy thường xuyên mà sẽ cài đặt tự động chạy một vài lần trong ngày để phát hiện và cảnh báo các trường hợp bất thường có thể xảy ra để có những điều chỉnh.