Edited at

QuickとTDD(テスト駆動開発)でiOS開発を始めてみた

More than 1 year has passed since last update.

最近テスト駆動でiOS開発を進めるようになり、それに際してQuickも導入しました。

Quickの設定方法なども含めて、どういう流れで開発を進めているかと、現時点での感想など記します。


Quickの導入

まずはquickを使うための準備。(今回はcarthageを使う)

Cartfile.private

github "Quick/Quick" "v0.9.3"

github "Quick/Nimble" "v4.1.0"

Cartfileの作成が終わったらcarthage updateする

carthage update --platform iOS

※ ライブラリを自動でアップデートしたくない場合はver指定しておく(今回はswift3対応前アプリだったので)

※ --platformで必要な分だけ指定した方がcarthageのbuildが早く終わる

※ テストなどでしか使わないものはCartfile.privateに書く方が良い

Xcodeの設定などこの記事が詳しい

(最初Copy Filesの設定のとこ忘れてて、ビルドは走るのにテスト実行されなくてめちゃくちゃハマってたので非常に助かりました...mm)

http://qiita.com/akatsuki174/items/77cb95265919b5ad4965


TDDの考え方

TDDで進める上で意識していることは以下3つ。

・テストから考える

機能を追加したい時には、まず最初にどういうテストを書けば良いかを考える

(どういう実装をするかは後回し)

・レッド→グリーン→リファクタのリズム

レッド(テスト失敗)→グリーン(テスト成功)→リファクタの順番でコードを書くことを意識する

・最小限の実装

実装は上のリズムを守るための最小限の実装にとどめる(イケテナイ実装でも後でリファクタするので気にしない)


実践

実際の流れ

例) ViewControllerに"test"というUILabelを追加したい場合


1.テスト作成

期待するのは「ViewControllerのviewに"test"というテキストを持つUILabelが追加されていること」なので、

expect(viewController.view.containLabelWithText("test")).to(beTrue())

というように書いてみる

(このときcontainLabelWithText(String)というメソッドはまだ存在しない)

import Quick

import Nimble
@testable import Sample

class ViewControllerTests: QuickSpec {
override func spec() {
describe("the view layout"){
it("has a label"){
let viewController = ViewController()
expect(viewController.view.containLabelWithText("test")).to(beTrue())
}
}
}
}

※ quickの基本的な書き方は公式のドキュメントを参照

https://github.com/Quick/Quick/tree/master/Documentation/ja


2.最初のテスト実行

containLabelWithTextの実装がまだなのでコンパイルエラー

(レッドの状態)


3.最初の実装

コンパイルエラーを解消し、グリーンの状態にするためcontainLabelWithTextを実装する(今回はUIViewのextensionとして実装した)

extension UIView {

func containLabelWithText(name: String) -> Bool {
return true
}
}


4.2回目のテスト実行

テストを実行すればコンパイル成功しグリーンの状態になる。(が、勿論今のままでは意味のあるテストになっていない)


5.最初の修正

意味のあるテストにするためcontainLabelWithTextを修正する(viewのsubviewsにUILabelが存在し、そのテキストがテスト対象テキストと一致すればtrue、そうでなければfalseを返却するように実装した)

extension UIView {

func containLabelWithText(name: String) -> Bool {
for subview in self.subviews {
if let label = subview as? UILabel {
if label.text == name {
return true
}
}
}
return false
}
}


6.3回目のテスト実行

ViewControllerにラベルを追加する実装をしていないのでテストは失敗する


7.ViewControllerの実装

ViewControllerにラベルを追加する(※レイアウトに関しては今回は触れません)

import UIKit

class ViewController: UIViewController {

override func viewDidLoad() {
super.viewDidLoad()
let label = UILabel()
label.text = "test"
view.addSubview(label)
}
}


8.4回目のテスト実行

意味のあるテストが成功し、グリーンの状態になった。


9.2回目の修正

入れ子になっているviewの中にラベルが追加されてもテストで検知したいので、以下のようにリファクタを行う。

extension UIView {

func containLabelWithText(name: String) -> Bool {
for subview in self.subviews {
if let label = subview as? UILabel {
if label.text == name {
return true
}
}
else {
if subview.subviews.count > 0 {
if subview.containLabelWithText(name) {
return true
}
}
}
}
return false
}
}

この後もリファクタは繰り返すが、以上でひとまず目的のテストが達成された


感想

かなり雑に書いてますが、だいたい上記のような流れで開発を進めてます。

最初はテストから書き始める思考に慣れないのと、今までのように一気に実装が書けないことのもどかしさがありましたが、機能追加によるバグの発生や今までのようなリファクタへの不安感は軽減されたように思います。

今のところは満足していますが、まだ始めたばかりなので、これから問題点などを感じることもあるかもしれません。その場合は追記します。