TDD 學習總結(Swift 實踐)

發表于:2016-06-27來源:Colin作者:不詳點擊數: 標簽:tdd
花了幾天時間,看完了 《Test-Driven iOS Development with Swift》 這本書,雖然只有短短 500頁的 epub,但是講解的很生動透徹,全書圍繞一個 ToDo 應用展開,講解了 Test-Driven Development (TDD,即測

什么是 TDD

測試驅動開發(TDD)是極限編程的重要特點,它以不斷的測試推動代碼的開發,既簡化了代碼,又保證了軟件質量。

測試驅動開發的基本思想就是在開發功能代碼之前,先編寫測試代碼。也就是說在明確要開發某個功能后,首先思考如何對這個功能進行測試,并完成測試代碼的編寫,然后編寫相關的代碼滿足這些測試用例。然后循環進行添加其他功能,直到完全部功能的開發。

OK,概括來說,TDD 的開發過程可以用上圖來描述:Red,Green,Refactor。

翻譯過來就是:

  1. 編寫測試用例,測試不通過。(紅色 Error)
  2. 編寫代碼實現功能,測試通過。(綠色 Success)
  3. 重構優化代碼。(Refactor)

再詳細點,測試驅動開發的基本過程如下:

  1. 明確當前要完成的功能。記錄成一個 TODO 列表。
  2. 快速完成針對此功能的測試用例編寫。
  3. 測試代碼編譯不通過。
  4. 編寫對應的功能代碼。
  5. 測試通過。
  6. 對代碼進行重構,并保證測試通過。
  7. 循環完成所有功能的開發。

怎么樣,簡單吧~

是否該用 TDD

簡單是簡單,但是很明顯的,開發前期,工作量絕對不是 1+1 那么簡單,那么是否該用 TDD 呢?對此,我不做過多的闡述。世上并沒有放之四海皆準的法則,TDD 好壞在于你的判斷,方法論的主體在于使用的人,本文并不會給你一個完美的答案,這需要你自己在實踐中取舍。接下去,我將列舉 TDD 目前公認的一些優缺點,以及使用原則,加深大家對 TDD 的理解。

TDD 開發的優點:

  • 可以保證代碼的質量??梢詫ψ约旱乃枰臉I務功能的每一步設計進行驗證,并得到正確的結果,減少bug的出現的,特別對于復雜業務邏輯的項目,以小步慢走的方式,避免后期繁重的測試和維護工作。
  • 找到了重構的信心,必要時候你還可以痛痛快快的并且滿懷信心的對代碼做一場大的變革。這樣我們的代碼變得干凈了,擴展性、可以維護性以及易理解性紛至沓來。
  • 在團隊建設中能夠進行分工,以可執行的形式文檔化你的需求,迫使你分清職責隔離依賴以驅動你的設計,編織安全網以便將Bug扼殺在在搖籃狀態,防止其逃逸。不同于傳統開發(傳統的開發人員開發的軟件的測試是為了找出已經逃逸得bug,可能這個bug已經長成了毒瘤)。注:這兩種活動都是必要的,而且毫不沖突,互為補充。
  • 幫助你養成一個新的思維習慣,不光在你編程的道路上,在你的工作和生活中,你慢慢的會把自己的需求進行分析設計并不斷地驗證,最終更好去實現自己的人生目標。

TDD 開發的缺點:

  • 對于測試驅動不熟練或者喜歡偷懶的的人員,加大了代碼的編寫量,測試代碼是系統代碼的兩倍或更多。
  • 可能不適合時間很緊的軟件開發,更適合于產品和平臺的開發。

TDD 原則:

  • 獨立測試:不同代碼的測試應該相互獨立,一個類對應一個測試類,一個函數對應一個測試函數。用例也應各自獨立,每個用例不能使用其他用例的結果數據,結果也不能依賴于用例執行順序。 一個角色:開發過程包含多種工作,如:編寫測試代碼、編寫產品代碼、代碼重構等。做不同的工作時,應專注于當前的角色,不要過多考慮其他方面的細節。

  • 測試列表:代碼的功能點可能很多,并且需求可能是陸續出現的,任何階段想添加功能時,應把相關功能點加到測試列表中,然后才能繼續手頭工作,避免疏漏。

  • 測試驅動:即利用測試來驅動開發,是TDD的核心。要實現某個功能,要編寫某個類或某個函數,應首先編寫測試代碼,明確這個類、這個函數如何使用,如何測試,然后在對其進行設計、編碼。

  • 先寫斷言:編寫測試代碼時,應該首先編寫判斷代碼功能的斷言語句,然后編寫必要的輔助語句。

  • 可測試性:產品代碼設計、開發時的應盡可能提高可測試性。每個代碼單元的功能應該比較單純,“各家自掃門前雪”,每個類、每個函數應該只做它該做的事,不要弄成大雜燴。尤其是增加新功能時,不要為了圖一時之便,隨便在原有代碼中添加功能。

  • 及時重構:對結構不合理,重復等“味道”不好的代碼,在測試通過后,應及時進行重構。

  • 小步前進:軟件開發是復雜性非常高的工作,小步前進是降低復雜性的好辦法。

    ?

看到這里,如果你還覺得,有必要體驗一把 TDD,那么接著往下看,我將通過一個簡單的例子,走一遍 TDD 開發的流程,加深大家對 TDD 的了解,也為 iOS 中應用 TDD 做個入門介紹。

iOS 中如何使用 TDD

Apple一直致力于在iOS開發中集成更加方便和可用的測試,在Xcode 5中,新的IDE和SDK引入了XCTest來替代原來的SenTestingKit,并且取消了新建工程時的“包括單元測試”的可選項(同樣待遇的還有使用ARC的可選項)。新工程將自動包含測試的target,并且相關框架也搭建完畢,可以說測試終于擺脫了iOS開發中“二等公民”的地位,現在已經變得和產品代碼一樣重要了。 —————— 喵神

簡單 Mark 下 TDD 在 Xcode 中的歷程:

  • In 1998, the Swiss company Sen:te developed OCUnit, a testing framework for Objective-C (hence, the OC prefix). OCUnit was a port of SUnit, a testing framework that Kent Beck had written for Smalltalk in 1994.
  • With Xcode 2.1, Apple added OCUnit to Xcode.
  • In 2008, OCUnit was integrated into the iPhone SDK 2.2 to allow unit testing of iPhone apps.
  • Four years later, OCUnit was renamed XCUnit (XC stands for Xcode).

既然 Xcode 為我們內置了這么方便的 XCTest,我們沒理由不好好使用阿~

接下去通過實現一個簡單的功能:把句子中每個單詞的首字母轉成大寫字母,來走一遍 TDD 的流程。話不多說,開車了~

1. 創建工程

這里創建一個常規的 iOS 工程,記得 “ Include Unit Tests” 即可,語言我們選擇 Swift。

創建完畢后的工程目錄如下:

默認為我們創建了 TDDDemoTests.swift 文件,這里就是我們編寫測試用例的地方。打開該文件,如下所示:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
//
//  TDDDemoTests.swift
//  TDDDemoTests
//
//  Created by Colin on 16/6/3.
//  Copyright © 2016年 Colin. All rights reserved.
//

import XCTest
@testable import TDDDemo

class TDDDemoTests: XCTestCase {
    
    override func setUp() {
        super.setUp()
        // Put setup code here. This method is called before the invocation of each test method in the class.
    }
    
    override func tearDown() {
        // Put teardown code here. This method is called after the invocation of each test method in the class.
        super.tearDown()
    }
    
    func testExample() {
        // This is an example of a functional test case.
        // Use XCTAssert and related functions to verify your tests produce the correct results.
    }
    
    func testPerformanceExample() {
        // This is an example of a performance test case.
        self.measureBlock {
            // Put the code you want to measure the time of here.
        }
    }
}

其中,有幾個地方需要說明一下:

1
2
import XCTest
@testable import TDDDemo

每一個測試用例都需要引入 XCTest 框架,它定義了我們需要的 XCTestCase 類,以及之后會用到的一些斷言,比如XCTAssertEqual 等。另外,還需要手動導入 TDDDemo 模塊,我們之后的相關代碼都會在 TDDDemo 中編寫,但是默認情況下,類,結構體,枚舉以及它們的方法,都是內聯的(internal),這意味著它們所處模塊外無法直接訪問到它們。所以在此之外的測試代碼無法訪問到它們,故而需要使用 @testable 關鍵字來讓測試代碼能訪問它們。

再看 setUp 方法和 tearDown 。在每個測試用例調用前,都會先調用 setUp 方法,在每個測試用例執行結束后,都會調用 tearDown 方法,大體流程就是:setUp — test case — tearDown — setUp — test case — tearDown …. 所以我們一般在 setUp 中做一些初始化操作,在 tearDown 做一些清除釋放操作。

另外,每一個測試方法都需要以 test 開頭,這樣 Xcode 才能自動識別出它。比如默認提供的 testExample 和testPerformanceExample 。

再有,這里建議在 Bulid 開始的時候,新建一個導航欄,并且打印 Build Log,這樣我們能更直觀知道發生了什么,哪里出錯了。具體設置如下: Xcode | Preference | Behaviors

如圖所示:

現在 Command + U,執行測試。毋庸置疑,測試通過(畢竟啥都還沒開始寫…)。你會看到如下界面:

左邊的 Test Navigation 列舉了所有的測試用例以及對應的測試結果。中間的編輯區展示了 Bulid 過程中具體做了什么,以及 Build 結果。

哦,對了。還有一處設置也很有用。

Edit Scheme | Test ,可以看到右邊列舉了所有參與測試的用例。當然我們知道,每個用例的測試都是需要時間的,如果想對某個用例單獨測試,或者不想測試某個用例,相應的勾選和去選就可以了。

2. 編寫測試用例

好了,萬事俱備,是時候展示真正的技術了!

刪除默認的 TDDDemoTests.swift 文件,重新創建一個 CapitalTest.swift 文件。在 TDDDemoTests 分組中,File | New | File | iOS | Source | Unit Test Case Class ,創建一個名為 CapitalTest 并 繼承自 XCTestCase 的類。如圖所示:

刪掉無用的 testExample,testPerformanceExample 方法。

引用 TDDDemo 類。

1
@testable import TDDDemo

編寫測試用例:

這里我們要做的是實現句子中單詞首字母的大寫轉換,所以只要寫個測試用例驗證首字母是否都是大寫即可。

1
2
3
4
5
6
7
8
9
func testMakeHeadline_ReturnsStringWithEachWordStartCapital() {
    
    let viewController = ViewController()
    
    let string = "this is A test headline"
    let headline = viewController.makeHeadline(string)
    
    XCTAssertEqual(headline, "This Is A Test Headline")
}

很簡單,我們希望有這樣一個函數 makeHeadline,它接受一個 String 類型的參數,并返回轉換成功的 String 類型的結果。然后利用 XCTAssertEqual 判斷一下,當左右值相同時,它才會通過。

很顯然,這個時候會保持,且測試不通過,因為我們的 makeHeadline 函數根本就不存在,現在就去實現它。

回到 ViewController.swift 中,添加如下方法。

1
2
3
4
func makeHeadline(string: String) -> String {
    
    return "This Is A Test Headline"
}

Command + U 走一遍,恭喜你,測試走通了。全部顯示綠色的 Build succeeded。(眼尖的朋友可能發現問題了,不過不急,至少目前為止,我們的測試用例已經通過了~)

然后接下去,做的就是重構了。雖然只寫了幾行代碼,但是還是有優化空間的。

我們之前提到過,setUp 方法將在每個 test case 調用前都自動被調用,所以這里可以放一些初始化相關操作。我們這里初始化了一個 ViewController 類型的對象,不出意外的話,在每個測試用例中中需要初始化一個,這無疑是很麻煩的。所以我們可以把 viewController 提出來,當做 CapitalTest 類的一個屬性,然后在 setUp 方法中去初始化它。具體如下:

1
2
3
4
5
6
7
8
9
10
11
12
class CapitalTest: XCTestCase {
    
    var viewController: ViewController!

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

接下去,我們需要在編寫另外一個測試用例,以保證第一個測試用例并不是偶然的。這也是我們在實際開發中需要做的,列舉多個測試用例,來保證某個功能確實通過了。

1
2
3
4
5
6
7
func testMakeHeadline_ReturnsStringWithEachWordStartCapital2() {
    
    let string = "Here is another Example"
    let headline = viewController.makeHeadline(string)
    
    XCTAssertEqual(headline, "Here Is Another Example")
}

再次 Command + U,不出意外,第一個還是通過,第二個則顯示失敗。原因大家都懂~

接下去修改 makeHeadline 的具體實現:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
func makeHeadline(string: String) -> String {
    
    // 1. 通過“ ”分割字符串, 存入數組
    let words = string.componentsSeparatedByString(" ")
    
    // 2. 遍歷數組, 移除首字母, 并插入對應的大寫字母
    var headline = ""
    for var word in words {
        let firstCharacter = word.removeAtIndex(word.startIndex)
        headline += "\(String(firstCharacter).uppercaseString)\(word) "
    }
    
    // 3. 移除最后的“ ”
    headline.removeAtIndex(headline.endIndex.predecessor())
    return headline
}

代碼很簡單,注釋也寫的很清楚,這里就不累述了。再次 Command + U,bingo~ 通過了。

接下去再看看,是否有優化的空間。

  1. 我們的測試用例描述的其實不太清楚,幾個變量之間的關系比較凌亂。
  2. makeHeadline 函數的實現太 Objc 化了,沒有用上 Swift 里的高級功能。

OK,既然不好,那就優化一下唄~

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
func testMakeHeadline_ReturnsStringWithEachWordStartCapital() {

        let inputString =       "this is A test headline"
        let expectedHeadline =  "This Is A Test Headline"
        
        let result = viewController.makeHeadline(inputString)
        XCTAssertEqual(result, expectedHeadline)
    }
    
func makeHeadline(string: String) -> String {

        let words = string.componentsSeparatedByString(" ")
        
        let headline = words.map { (var word) -> String in
          let firstCharacter = word.removeAtIndex(word.startIndex)
          return "\(String(firstCharacter).uppercaseString)\(word)"
          }.joinWithSeparator(" ")
        
        return headline
    }

再次 Command + U,確保測試通過。至此,這個簡單的例子算是介紹完了。

雖然例子簡單,只實現了一個功能,但是 TDD 相關的東西,具體流程也都涉及了,剩下的,只是重復這些操作直至完成所有需求。

原文轉自: http://colin1994.github.io/2016/06/03/TDD-With-Swift/

国产97人人超碰caoprom_尤物国产在线一区手机播放_精品国产一区二区三_色天使久久综合给合久久97