TDD

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

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

Phần 2b này, chúng ta sẽ đi qua một ví dụ khác để có cái nhìn rõ hơn về cấu trúc cũng như quy tắc của TDD.

Ví dụ 2

1. Red

Như đã đề cập ở phần trước, ta cần 1 cái test khác, bởi vì production code chỉ work với 1 input cụ thể. Feature của mình cần phải work với tất cả các input. Add vào phần test, đoạn code sau:

func test_MakeHeadline_ReturnsStringWithEachWordStartCapital2() {
     let string = "Here is another Example"
     let headline = viewController.makeHeadline(from: string)
     XCTAssertEqual(headline, "Here Is Another Example")
   }

Xong rồi Run test nào. Đương nhiên là nó fail gòy. Fail này là do XCAssert. Không phải do phần code, cho nên ta coi như đã xong phần Red. Qua phần Green nào.

2. Green

Bây giờ quay trở lại phần code production và thay dòng hard-code bằng đoạn code sau:

func makeHeadline(from string: String) -> String {
    let words = string.components(separatedBy: " ")
    var headline = ""
    for var word in words {
      let firstCharacter = word.remove(at: word.startIndex)
      headline += "\(String(firstCharacter).uppercased())\(word) "
    }
    headline.remove(at: headline.index(before: headline.endIndex))
    return headline
  }

Mình sẽ phân tích từng phần:

  • let words = string.components(separatedBy: " ") đoạn này lấy từng từ trong string truyền vào
  • trong vòng lặp for, lấy từng từ một. Lấy kí tự đầu tiên ra bằng dòng let firstCharacter = word.remove(at: word.startIndex)
  • viết hoa nó lên String(firstCharacter).uppercased() xong rồi ghép nó với khúc phía sau "\(String(firstCharacter).uppercased())\(word) " -> để ý có space ở đây để cách với từ tiếp theo.
  • ghép từng từ lại thành 1 câu trở lại headline += "\(String(firstCharacter).uppercased())\(word) "
  • xoá space sau cùng headline.remove(at: headline.index(before: headline.endIndex)) và return lại kết quả.

Run test và kết quả pass hết. Phần tiếp theo chính là Refactor.

Lưu ý phần refactor cần phải có. Nếu bạn thấy mình không có gì để refactor tức là bạn vẫn chưa xong.

3. Refactor

Nhìn vào 2 đoạn code mà bạn vừa viết dưới đây:
alt text

Nhìn vào, thật sự mình chả hiểu input là gì? Output là gì? Mặc dù chính mình vừa mới viết xong ^^ Ta refactor như sau:

func test_MakeHeadline_ReturnsStringWithEachWordStartCapital() {
     let input           = "this is A test headline"
     let expectedOutput  = "This Is A Test Headline"
     let headline = viewController.makeHeadline(from: input)
     XCTAssertEqual(headline, expectedOutput)
   }

func test_MakeHeadline_ReturnsStringWithEachWordStartCapital2() {
     let input           = "Here is another Example"
     let expectedOutput  = "Here Is Another Example"
     let headline = viewController.makeHeadline(from: input)
     XCTAssertEqual(headline, expectedOutput)
   }

Các bạn đã thấy nó chia ra rõ ràng, input là gì? expectedOutput là gì? dòng nào thực hiện hàm v.v. Nó tuân theo cấu trúc logic mà mình đã bàn ở phần một: precondition, invocation, and assertion.

alt text

4. Check test code

Về cơ bản, những lần thay đổi code ở phần test không cần phải được tested. Nhưng làm sao ta biết được liệu những test case này có được update kịp thời không? Hay là vẫn test như trước đó ? Tốt nhất là nên thử rằng tests ok. Có nghĩa là ta làm nó fail đi. Comment dòng code sau cùng trong production code đi:

// headline.remove(at: headline.index(before: headline.endIndex))

Như vậy, string được trả về sẽ dư 1 space. Run nào. Oh Yeahhh Fail cả hai. Như vậy là ok.

Note: Nếu bạn để ý, 1 test failed sẽ không làm dừng lại quá trình test. Nhìn chung là vậy, nhưng bạn cũng có thể thay đổi continueAfterFailure thành false in setUp().

Trước khi kết bài, mình nhìn lại production code một chút. Nhìn nó như kiểu Objective-C translate qua Swift vậy. Mình cho nó nhìn dễ thương như Swift một chút, như sau:

func makeHeadline(from string: String) -> String {
     let words = string.components(separatedBy: " ")
     let headlineWords = words.map { (word) -> String in
       var mutableWord = word
       let first = mutableWord.remove(at: mutableWord.startIndex)
       return String(first).uppercased() + mutableWord
       }
     return headlineWords.joined(separator: " ")
   }

Có hai sự khác biệt là dùng map thay cho for và joined, tức là kết hợp các phần tử riêng lại. Run lại thì thấy ok, test pass hết.

5. Tổng kết

Qua hai phần này, mặc dù các ví dụ đơn giản nhưng ít nhiều mình cũng đã biết thêm về TDD workflowworkflow như thế nào. Hi vọng những bài tới sẽ tốt thêm.

Registration Login
Sign in with social account
or
Lost your Password?
Registration Login
Sign in with social account
or
A password will be send on your post
Registration Login
Registration