Swift 中如何测试驱动开发
在移動開發的過程中,編寫測試代碼已經不再流行,相反的,人們可能為了提高開發效率,盡量避免測試代碼的編寫以節約時間。作為開發菜鳥,我嘗到了單元測試的好處: 不僅能保證您的代碼能如預期運行,還能保護代碼以防被其他小伙伴wu'yi修改。單元測試與項目代碼的結合,能幫助新人快速熟悉并接管您的項目。
TDD(Test-driven Development)
TDD 是一門藝術,他遵循以下規則:
Test 1:
如果 w = 2、h = 2 ,那么我們期待的結果是 4, 在這種情況下,單元測試一般會失敗,因為我的的函數并沒有實現?。那么下面我們稍微改一下:
func calculateAreaOfSquare(w: Int, h: Int) -> Double {return w * h} 復制代碼這樣 Test 1 就能輕松通過了。
Test 2: 如果 w = -1、h = -1 我們期待的結果是 0, 如果還用當前的函數,單元測試也會失敗,因為我們的函數返回的是 1。我們再改一下:
func calculateAreaOfSquare(w: Int, h: Int) -> Double { if w > 0 && h > 0 { return w * h } return 0 } 復制代碼現在 Test 2 也通過了。
像這樣我們進行測試窮舉,直到考慮到所有的邊緣問題,也就完全通過單元測試。 從當前我們的討論可以看出,TDD 不僅能提高我們的代碼質量還能幫我們考慮到很多邊緣問題的處理,除此之外,在結對編程中,一個編寫測試,另一個的代碼則需通過單元測試,從而有效的進行項目的良性推進。
本文中您將了解到:
預備工作:
Xcode 8.3.3、 Swift 3.1 有一定的 swift 的開發經驗
創建一個原始工程:
假設我們有一個任務-----開發一款展示電影海報的簡單應用程序,創建一個名為 MyMovies 的工程,然后基于該工程進行單元測試。
import UIKitclass MoviesTableViewController: UITableViewController {override func viewDidLoad() {super.viewDidLoad()}// MARK: - Table view data sourceoverride func tableView(_ tableView: UITableView, numberOfRowsInSection section: Int) -> Int {return 0} } 復制代碼進行數據填充:
類型枚舉
enum Genre: Int {case Animationcase Actioncase None } 復制代碼這個枚舉用于識別我們的電影類型。
Movie 模型
struct Movie {var title: Stringvar genre: Genre } 復制代碼簡單列一些我們的電影模型數據
class MoviesDataHelper {static func getMovies() -> [Movie] {return [Movie(title: "The Emoji Movie", genre: .Animation),Movie(title: "Logan", genre: .Action),Movie(title: "Wonder Woman", genre: .Action),Movie(title : "Zootopia", genre: .Animation),Movie(title: "The Baby Boss", genre: .Animation),Movie(title: "Despicable Me 3", genre: .Animation),Movie(title: "Spiderman: Homecoming", genre: .Action),Movie(title: "Dunkirk", genre: .Animation)]} } 復制代碼這個類能方便我們很快的返回一組數據 在這個階段,我們沒有執行任何 TDD , 這只是我們的準備階段。?接下來就是我們的主題了,Quick & Nimble!
Quick & Nimble
Quick 是為Swift和Objective-C設計的基于 XCTest的測試框架,它提供了一個DSL來編寫非常類似于RSpec的測試. Nimble 和 Quick 很方便的結合在一起使用,關于 Quick 的更多信息,請看這里
引入 Quick & Nimble :
這里我們使用 Carthage 來做包管理
#CartFile.private github "Quick/Quick" github "Quick/Nimble" 復制代碼CartFile.private 是我們用來管理依賴的,如果您對 Carthage 不是很了解,那么您可以先看看 這里
添加完依賴后確保您的項目如下:
開始 Test #1
我們首先來測試一下我們的數據與視圖數目是否相符。 打開 MyMoviesTests 文件 ,移除 XCTest , 導入 Quick, Nimble 首先確保我們的類是 QuickSpec 的子類,它也是原始 XCTestCase 的子類。 因為 Quick&Nimble的底層仍然是 XCTest。因此我們需要重寫 spec()
import Quick import Nimble import MyMoviesclass MyMoviesTests: QuickSpec {override func spec() {} } 復制代碼Test #1 – Expect Table View Rows Count = Movies Data Count
首先我們在 ViewController 里面引入 subject
import Quick import Nimble import MyMoviesclass MyMoviesTests: QuickSpec {override func spec() {var subject: MoviesTableViewController!describe("MoviesTableViewControllerSpec") {beforeEach {subject = UIStoryboard(name: "Main", bundle: nil).instantiateViewController(withIdentifier: "MoviesTableViewController") as! MoviesTableViewController_ = subject.view}}} } 復制代碼請注意,這里我們使用@testable導入MyMovies,這一行指定了我們將要測試的目標項目。 當我們將測試 ViewController 的視圖層時,我們需要從storyboard中獲取一個實例。 describe閉包聲明了我們將要對 MoviesTableViewController 進行測試
context("when view is loaded") {it("should have 8 movies loaded") {expect(subject.tableView.numberOfRows(inSection: 0)).to(equal(8))} } 復制代碼你將會發現
MoviesTableViewController__when_view_is_loaded__should_have_8_movies_loaded] : expected to equal <8>, got <0>Test Case '-[MyMoviesTests.MoviesTableViewControllerSpec MoviesTableViewController__when_view_is_loaded__should_have_8_movies_loaded]' failed (0.009 seconds). 復制代碼可以看出,剛剛的測試是沒有通過的,這就是 TDD。
修復 Test #1
讓我們看看 MoviesTableViewController 的數據是如何加載的,我們添加幾行代碼后再來運行測試看看
override func tableView(_ tableView: UITableView, numberOfRowsInSection section: Int) -> Int {return MoviesDataHelper.getMovies().count }override func tableView(_ tableView: UITableView, cellForRowAt indexPath: IndexPath) -> UITableViewCell {let cell = tableView.dequeueReusableCell(withIdentifier: "MovieCell")return cell! } 復制代碼恭喜您,通過了我們的測試。
Test #2
下面我們來一個 UI 測試。
context("Table View") {var cell: UITableViewCell!beforeEach {cell = subject.tableView(subject.tableView, cellForRowAt: IndexPath(row: 0, section: 0))}it("should show movie title and genre") {expect(cell.textLabel?.text).to(equal("The Emoji Movie"))expect(cell.detailTextLabel?.text).to(equal("Animation"))} } 復制代碼運行測試
MoviesTableViewController__Table_View__should_show_movie_title_and_genre] : expected to equal <Animation>, got <Subtitle> 復制代碼失敗了。我們修改一下在測試試。
struct Movie {var title: Stringvar genre: Genrefunc genreString() -> String {switch genre {case .Action:return "Action"case .Animation:return "Animation"default:return "None"}} } 復制代碼然后再修改一下 cellForRow這個方法
override func tableView(_ tableView: UITableView, cellForRowAt indexPath: IndexPath) -> UITableViewCell {let cell = tableView.dequeueReusableCell(withIdentifier: "MovieCell")let movie = MoviesDataHelper.getMovies()[indexPath.row]cell?.textLabel?.text = movie.titlecell?.detailTextLabel?.text = movie.genreString()return cell! } 復制代碼Bingo, 又通過了一個測試,接著我們在優化一下:
class MoviesTableViewController: UITableViewController {var movies: [Movie] {return MoviesDataHelper.getMovies()}// MARK: - Table view data sourceoverride func tableView(_ tableView: UITableView, numberOfRowsInSection section: Int) -> Int {return movies.count}override func tableView(_ tableView: UITableView, cellForRowAt indexPath: IndexPath) -> UITableViewCell {let cell = tableView.dequeueReusableCell(withIdentifier: "MovieCell")let movie = movies[indexPath.row]cell?.textLabel?.text = movie.titlecell?.detailTextLabel?.text = movie.genreString()return cell!} } 復制代碼好了,至此一個簡單的 TDD 過程結束了
總結
我們寫了第一個測試來檢查模型個數,它失敗了。 修改了一下加載模型數據的邏輯,然后通過了。 我們寫了第二個測試來檢查UI是否正確,失敗了。 修改了一下UI展示邏輯,然后通過。 然后,我們對代碼做了一次重構,適當優化一下。 這就是TDD的常見開發流程。 如果您有任何疑問,請留言。
您可以在 GitHub上下載完整的源代碼。
總結
以上是生活随笔為你收集整理的Swift 中如何测试驱动开发的全部內容,希望文章能夠幫你解決所遇到的問題。
- 上一篇: 在iOS中使用FilesApp
- 下一篇: Linux基础(day59)