+1

Ba template thường được sử dụng trong unit testing.

Testing hay kiểm thử phần mềm là một giai đoạn trong quá trình sản xuất phần mềm. Sự quan trọng của testing chắc hẳn mọi người đều đã biết rõ, nó là bước đệm ở giữa sản xuất và sử dụng phần mềm, đảm bảo chất lượng các tính năng của phần mềm trước khi đưa ra sử dụng.

Testing có nhiều mức độ (level) từ unit test (kiểm thử đơn vị), regression test(kiểm tra quy hồi), integration test(kiểm tra tích hợp), system test (kiểm thử hệ thống),acceptance test (kiểm tra chấp nhận sản phẩm)..., ở mỗi mức độ, việc kiểm thử đều có nhiệm vụ, tầm quan trọng riêng và không thể bỏ qua.

Trong bài viết này, mình xin giới thiệu qua những template thường dùng cho việc unit test, và hy vọng mọi người đều cố gắng giữ mindsetunit test là bắt buộc mà bất cứ lập trình viên nào cũng phải làm.

Given-When-Then template

Given-When-Then (GWT) là một phong cách test hiện đại - chỉ định hành vi hệ thống bằng cách sử dụng - specifying a system's behavior using SpecificationByExample(Đặc điểm kĩ thuật bởi ví dụ).

GWT là một cách tiếp cận được phát triển bởi Daniel Terhorst-NorthChris Matts như là một phần của phát triển hướng hành vi (BDD). Nó xuất hiện như một cách tiếp cận cấu trúc cho nhiều framework như Cucumber. Bạn cũng có thể xem nó như một sự cải tổ của The Four-Phase Test pattern.

Ý tưởng chủ đạo là chia nhỏ việc viết một kịch bản (scenario) (hoặc test) thành ba phần:

  1. Given - Mô tả trạng thái của thế giới trước khi bạn bắt đầu hành vi mà bạn chỉ định trong kịch bản này (scenario). Bạn có thể nghĩ về nó như là điều kiện tiền đề (pre-conditions) của kiểm thử.
  2. When - Là phần hành vi mà bạn chỉ định.
  3. Then - Là phần mô tả những thay đổi mà bạn mong đợi từ hành vi được chỉ định ở trên (when).

Ex:

Feature: User trades stocks
  Scenario: User requests a sell before close of trading
    Given I have 100 shares of MSFT stock
       And I have 150 shares of APPL stock
       And the time is before close of trading

    When I ask to sell 20 shares of MSFT stock
     
     Then I should have 80 shares of MSFT stock
      And I should have 150 shares of APPL stock
      And a sell order for 20 shares of MSFT stock should have been executed

Ví dụ trên sử dụng Cucumber, một cách viết phổ biến của BusinessFacesTests (là một kiểu test nhằm mục đích sử dụng của devs như một công cụ trợ giúp để giao tiếp với các thành viên không lập trình, như khách hàng, người dùng, người phân tích kinh doanh và tương tự). Tuy nhiên, bạn có thể sử dụng GWT style cho bất cứ kiểu test nào, thậm chí vài người còn dùng nó như là comment để đánh dấu các testcase trong unit test.

Ở trên, ta định nghĩa Given như là một mô tả trạng thái điều kiện tiền đề (pre-condition), tuy nhiên một testing framwork, giải thích Given là một tập hợp các lệnh để đưa system-under-test(hệ thống được kiểm thử) vào trạng thái chính xác trước khi thực hiện lệnh (When) (Đây là lý do tại sao các quy ước đặt tên khác thường gọi đây là setup). Các testing framwork cung cấp các phương thức truy vấn khác nhau cho các lệnh Then, các phương thức này tự do side-effects.

Mặc dù, template Given-When-Then mang dấu triệu BDD, nhưng ý tưởng cơ bản của nó khá phổ biến khi viết các bài kiểm tra hoặc đặc tả bằng ví dụ (SpecificationByExample).

3A - Arrange, Act, Assert template

Các hành vi khác nhau của 1 đối tượng:

  • Constructors
  • Mutators, hay modifiers hay commands
  • Accessors, hay queries (các truy vấn)
  • Iterators (vòng lặp)

Chúng ta muốn test các hành vi của đối tượng. Một cách tiếp cận tốt đó là đặt vào đối tượng những cấu hình thú vị ("interesting" configuration) mà nó có thể có, và thử các hành động khác nhau trên nó.

  • Arrange: Thiết lập đối tượng cần kiểm tra. Chúng ta có thể cần bao quanh đối tượng và những đối tượng liên quan (collaborator). Đối với mục đích kiểm tra, những đối tượng có liên quan đó có thể là đối tượng kiểm tra (test object) (hoặc mock, fake, ...) hoặc đối tượng thật (real object).
  • Act: Hành động trên đối tượng (thông qua một số trình biến đổi (mutator, modifier hay command)). Bạn có thể cần cung cấp cho nó các tham số (có thể là các đối tượng kiểm tra).
  • Assert: Đưa ra khẳng định về đối tượng, các đối tượng liên quan, các tham số của nó và có thể (hiếm khi) là trạng thái toàn cục (global state).

Bạn bắt đầu từ đâu?

Có thể bạn nghĩ rằng Arrange nên được nghĩ trước tiên, hoặc khi làm việc một cách có hệ thống thông qua hành vi đối tượng thì Act có thể viết trước? Tuy nhiên, thực tế Assert lại là điểm tuyệt vời để bắt đầu.

Khi bạn có một hành vi mới bạn biết bạn cần test nó, Assert trước cho phép bạn bắt đầu yêu cầu Giả sử nó hoạt động, nó sẽ như thế nào? và với assert đã có, bạn có thể bắt đầu thực hiện "thêm vào chỗ trống" trên IDE theo Logic mong muốn.

Một vài tips với 3A Test

  • Template này không khuyến khích một chuỗi test như > Arrange > Act > Assert > Act > Assert > Arrange more > Act > Assert ..... Vì thật khó để thấy đối tượng nào là trọng tâm của bài test, và thật khó để thấy rằng bạn đã bao quát từng trường hợp. Những (unit) testcase nhiều bước như vậy thì không tốt bằng việc được chia thành nhiều testcase.
  • Giữa các testcase hoặc setup quá rối, bạn cảm thấy không tin tưởng vào trạng thái khởi tạo của đối tượng test, bạn có thể kiểm tra nó. Luồng trông thế này > Arrange > Assert that the setup is OK > Act > Assert that the behavior is right.
  • Khi test, nếu cảm thấy 1 đối tượng có quá nhiều accessors, nó chỉ ra đối tượng làm quá nhiều thứ.
  • Khi test sự thay đổi 1 đối tượng, combine nhiều accessors mà bạn có thể test cùng nhau

Ví dụ:

    List list = new List();
    list.add(3);
    assertEquals(1, list.size());
    assertEquals(3, list.max());

The Four Phases Test template

Theo design mỗi test có bốn giai đoạn riêng biệt được thực hiện theo trình tự. Bốn phần bao gồm fixture setup, exercise SUT, result verification và fixture teardown.

  1. Đầu tiên, chúng ta setup những thứ cố định (fixture) (hình ảnh "trước") cần thiết cho SUT (system under test- hệ thống cần test) để thể hiện hành vi mong đợi cũng như mọi thứ bạn cần đặt vào để có thể quan sát kết quả thực tế.
  2. Bước thứ hai, chúng ta tương tác với SUT.
  3. Bước thứ ba, chúng ta làm bất cứ điều gì cần thiết để xác định xem kết quả mong đợi đã đạt được hay chưa.
  4. Bước thứ tư, chúng tôi loại bỏ những gì đã setup để đưa thế giới trở lại trạng thái mà bạn tìm thấy nó.

Lý do ra đời

Điều quan trọng là người đọc testcase có thể nhanh chóng xác định hành vi mà testcase đang xác minh. Có thể rất khó hiểu khi các hành vi khác nhau của hệ thống được kiểm tra (system under test - SUT) đang được thực thi, một số để thiết lập trạng thái trước kiểm tra (pre-test) (fixture) của SUT, những hành vi khác để thực hiện SUT và các hành vi khác để xác minh trạng thái post-test (sau kiểm tra) của SUT. Xác định rõ ràng bốn giai đoạn làm cho ý định của testcase dễ nhìn hơn nhiều.

Giai đoạn fixture setup của testcase thiết lập trạng thái trước của testcase là đầu vào quan trọng của testcase. Giai đoạn exercise SUT là nơi chúng ta thực sự khiến phần mềm chúng ta chạy thử. Khi đọc testcase, điều quan trọng là có thể xem phần nào đang được chạy. Giai đoạn result verification của testcase là nơi chúng ta chỉ định kết quả mong đợi. Giai đoạn cuối cùng, fixture teardown, tất cả là dọn dẹp.

Chúng ta nên tránh sự cám dỗ để kiểm tra càng nhiều chức năng càng tốt trong một testcase duy nhất vì điều đó có thể dẫn đến các kiểm tra tối nghĩa (Obscure Tests). Trên thực tế, tốt hơn là nên có nhiều testcase điều kiện đơn nhỏ (Single Condition Test). Sử dụng các comment để đánh dấu các giai đoạn của Four-Phase Test là một điều tốt ở chỗ nó sẽ trở nên rõ ràng khi các testcase của chúng ta không phải là Single Condition Test.

Sẽ là hiển nhiên nếu chúng ta có nhiều giai đoạn exercise SUT cách nhau bởi các giai đoạn result verification hoặc chúng ta có các fixture setup xen kẽ và các giai đoạn exercise SUT. Chắc chắn, các testcase có thể hoạt động nhưng chúng sẽ cung cấp ít Defect Localization so với khi chúng ta có một loạt các Single Condition Tests độc lập.

Lưu ý khi triển khai:

Chúng ta có một số tùy chọn để thực hiện the Four-Phase Test. Trong trường hợp đơn giản nhất, mỗi testcase là hoàn toàn tự do. Nó thực hiện tất cả bốn giai đoạn trong cùng một test method. Đây là lựa chọn thích hợp nhất khi chúng ta đang sử dụng Testcase Class per Class hoặc Testcase Class per Feature để tổ chức testcase. Trong một số trường hợp thuận lợi, ta có thể nhóm các fixture setup chung vào method setUp, phần riêng sẽ để trong mỗi test method tương ứng. Ví dụ:

 public void testGetFlightsByOriginAirport_NoFlights_inline() throws Exception {
  // Fixture setup
  NonTxFlightMngtFacade facade =new NonTxFlightMngtFacade();
  BigDecimal airportId = facade.createTestAirport("1OF");
  try {
     // Exercise System
     List flightsAtDestination1 = facade.getFlightsByOriginAirport(airportId);
     // Result verification
     assertEquals( 0, flightsAtDestination1.size() );
  } finally {
     // Fixture teardown
     facade.removeAirport( airportId );
  }
}

Lựa chọn khác là tận dụng sự hỗ trợ của Test Automation Framework. Chúng ta đưa yếu tố fixture setup chung và fixture teardown vào các phương thức setUptearDown trong test class. Do đó, trong test method chỉ cònexercise SUTresult verification. Cách tiếp cận này là lựa chọn phù hợp khi chúng ta đang sử dụng Testcase Class per Feature.

Ví dụ:

NonTxFlightMngtFacade facade = new NonTxFlightMngtFacade();
   private BigDecimal airportId;
  
   protected void setUp() throws Exception {
      // Fixture setup
      super.setUp();
      airportId = facade.createTestAirport("1OF");
   }
   
   public void testGetFlightsByOriginAirport_NoFlights_implicit() throws Exception {
      // Exercise SUT
      List flightsAtDestination1 = facade.getFlightsByOriginAirport(airportId);
      // Result verification
      assertEquals( 0, flightsAtDestination1.size() );
   }   
  
   protected void tearDown() throws Exception {
      // Fixture teardown
      facade.removeAirport(airportId);
      super.tearDown();
   }
}

Tổng kết

Nhìn chung cả 3 mẫu test template này đều trải qua những bước tương đối giống nhau

  1. Tạo và cài đặt các đối tượng test.
  2. Giả định các hành vi của đối tượng.
  3. Expect kết quả mong muốn.
  4. Có thể có hoặc không việc reset các đối tượng test về trạng thái ban đầu.

Tất nhiên, mỗi ngôn ngữ, mỗi testing framework đều có hỗ trợ tất cả những gì bạn cần để tạo một unit test testcase theo các bước ở trên, điều bạn cần là chọn cái quen thuộc nhất và template thường được ưa chuộng vào project của bạn.

HAPPY CODING !

References


All rights reserved

Viblo
Hãy đăng ký một tài khoản Viblo để nhận được nhiều bài viết thú vị hơn.
Đăng kí