07/09/2018, 17:23

Swift - Test-Driven Development (TDD) - Chapter 1 - Part 2 - Understanding TDD

Swift - Test-Driven Development (TDD) - Chapter 1 - Part 2 - Understanding TDD Qua hai phần Part 1a, Part 1b ở bài trước, mình đã hiểu unit test là gì, chúng có ích như thế nào. Tiếp theo, chúng ta sẽ tìm hiểu tiếp về TDD. Rất xin lỗi các bạn nếu khi các bạn đọc vào thấy bỡ ngỡ, mình cũng chưa rõ ...

Swift - Test-Driven Development (TDD) - Chapter 1 - Part 2 - Understanding TDD

Qua hai phần Part 1a, Part 1b ở bài trước, mình đã hiểu unit test là gì, chúng có ích như thế nào. Tiếp theo, chúng ta sẽ tìm hiểu tiếp về TDD. Rất xin lỗi các bạn nếu khi các bạn đọc vào thấy bỡ ngỡ, mình cũng chưa rõ phải trình bày như thế nào để dễ hiểu nhất. Sau khi get được nhiều kiến thức, mình sẽ sắp xếp or bổ sung thêm.

1. The TDD workflow - red, green, refactor

alt text

a. Red

Bạn bắt đầu bằng cách viết 1 test failed. Nó được dùng được kiểm tra các feature mà chưa được element hay những edge case mà bạn muốn đi qua để cover hết.
Một điều quan trọng là phần test sẽ được viết trước trong lúc này sẽ bị fail. Nếu không thì bạn sẽ không thể đảm bảo là test có được không hay nó có thực sự test những feature mà mình mong muốn element hay không.

b. Green

Trong giai đoạn Green, mình chỉ cần viết 1 đoạn code đơn giản nhất có thể để test pass. Code xấu, cùi bắp gì cũng được, miễn làm cho test pass là được. Điều quan trọng nhất là bạn cần viết code thật đơn giản, mà implement được phần code của mình. Nói vậy thôi chứ đừng nhớp quá. Dễ đọc, dễ hiểu, dễ thay đổi là được. Đằng nào thì 1 dev muốn lên trình phải viết code cho dễ đọc chứ
Thông thường những implement đơn giản thì sẽ không đủ những tính năng mong muốn mặc dù vẫn đủ làm tất cả các test cases pass. Có nghĩa là bạn cần thêm những test failed khác để đưa đến việc phát triển xa hơn của tính năng đó

c. Refactor

Như mình đã nói trong phần green, mình chỉ cần viết code đơn giản để cho tất cả các tests pass là được. Đến phần refactor này, là nơi để mình improve code. Remove duplication, xem xét các giá trị common v..v. Refactor code hết mức có thể. Phần test này giúp bạn không bị hư hỏng code trước đó trong khi refactor

Đừng bỏ qua phần này. Cố gắng tìm cách improve hết mức có thể sau khi đã implemented 1 feature. Làm như vậy sẽ giúp code clean, dễ maintain và đẹp đẽ.

Sau mỗi lần refactor, bạn có thể chỉ tốn vài dòng code, cho nên việc thay đổi này không tốn nhiều thời gian cho lắm.

2. TDD trong Xcode

Trong năm 2013, unit testing xuất hiện trong Xcode 5 với việc giới thiệu XCTest. Với XCTest, Apple cho các tính năng cho testing như running 1 vài test mà mình muốn, tìm test fail nhanh, get overview. Ta sẽ đi qua phần testing user interface in Xcode sau. Trước hết, ta sẽ xem qua thử TDD trong Xcode hoạt động như thế nào.

a. Ví dụ về TDD

a.1 Nhẩm trước những rule

Ta sẽ lấy app ở part 1, giả sử ta đang làm 1 app blog. Khi viết 1 post mới, phải có title. Mình muốn tất cả các từ ở phần headline phải viết hoa chữ đầu. Để bắt đầu TDD workflow, ta bắt đầu 1 failing test. Ta cần tuân thủ các rule sau:

Precondition: Trạng thái ban đầu của hệ thống trước khi gọi hàm là gì ?
Invocation: Tín hiệu vào và tín hiệu ra như thế nào ?
Assertion: Mong muốn kết quả như thế nào ?

Đối với ví dụ của mình:

Precondition: Không có gì.
Invocation: Input là string và output cũng vậy. Tên method nên là makeHeadline
Assertion: Viết hoa chữ đầu.

Vậy là đã xong, bây giờ ta đi vào hầm 1: Red

a.2 Red

Bước 1: Add đoạn code sau vào file Test và build:

func test_MakeHeadline_ReturnsStringWithEachWordStartCapital() {
    let viewController = ViewController()
    let string = "this is A test headline"
    let headline = viewController.makeHeadline(from: string)
  }

Và ta sẽ bị lỗi như sau:
alt text

Đoạn code này chưa làm được gì, và đương nhiên các bạn cũng thấy error.
Theo như TDD workflow, ta cần thêm code cho đến khi compiler không in ra lỗi nữa. Nên nhớ code không compile được trong 1 test tức là test đang bị fail. Và 1 failing test nghĩa là mình cần code lại đến khi không fail nữa (nghe hơi ngớ ngẩn).

Ta thêm đoạn code sau ở ViewController:

func makeHeadline(from string: String) -> String {
    return ""
  }

Và run lại, thì ok. Đôi khi lỗi vẫn show ra là vì Xcode quên remove errors cũ. Bạn cũng đừng lo, miễn build success là được. Đến lúc này, vẫn bị warning, đó là vì Một khi đã có test thì phải có XCTAssert.Thêm vào thôi:
XCTAssertEqual(headline, "This Is A Test Headline") và build lại.

Các bạn được như sau:
alt text

  • Các bạn có thấy cái fail ở trên không, đừng để ý vì đó do Xcode bị khùng.
  • Bởi vì headline ở dưới đã trả về "", đúng như bên ViewController, tức là nó có gọi được
  • Như vậy đã đủ để compile. Run test lên -> fail. Nhưng lần này, fail là do Assert chứ không phải do code mình viết sai. Như vậy là đã đáp ứng được yêu cầu của Red. Xong bây giờ qua hầm 2: Green.
a.3 Green

Như đã nói, lỗi do empty string không bằng "This Is A Test Headline". Theo như TDD workflow, ta cần back lại phần implementation và add 1 dòng code đơn giản nhất để làm cho nó pass.

Qua bên ViewController, thay makeHeadline(from:) như sau:

   func makeHeadline(from string: String) -> String {
     return "This Is A Test Headline"
}

Hard-code, đương nhiên chẳng ai làm vậy nhưng nó làm cho test pass. Từ nó mình cần thêm những test khác. Ok, Run test lại, kết quả như sau:
alt text

Hầm hai này có vẻ dễ ta. Qua hầm cuối: Refactor.

a.4 Refactor

Trước khi viết thêm test cases, ta cần refactor những thằng tồn tại đã. Trong phần production code (tức là code ở trong ViewController), không có gì để refactor, ko thể đơn giản hơn. Trong phần test code (tức là code trong phần test), ta để ý có 2 test case và lặp lại let viewController = ViewController() -> có việc để làm rồi!!

Tại nơi khai báo property của Test class, ta khai báo: var viewController: ViewController!

Nhớ lại phần đầu mình có nói, setUp() method được gọi mỗi một test được executed. Đây là nơi lý tưởng để initialize property viewController

   override func setUp() {
     super.setUp()
     viewController = ViewController()
}

Bây giờ ta có thể remove let viewController = ViewController() khỏi mỗi test cases

0