いまさらながらSwiftでTDDをやってみた

みなさん、こんにちは！

[Swift その2 Advent Calendar 2017 19日目](https://qiita.com/advent-calendar/2017/swift2)の記事です

Swiftでなにか作ろうと思ったのですが、時間が足りず...
そこで、自分がTestをほとんどやったことなかったので、Test系の記事にしました。（ごめんなさい）
初歩の初歩だと思うので、これから始めようと思ってる方向けになりますが、この記事を読んでTestを始めてもらえたら嬉しいです:relaxed:

# TDD_説明編
最近、よく耳にするようになって来た言葉ですね
TDDとは`test-driven development`の略で、`テスト駆動開発`という開発手法のことを指します。

## 開発手順
基本的な開発手順は3ステップです。

![名称未設定ファイル.jpg](https://qiita-image-store.s3.amazonaws.com/0/150615/0d91a14f-a214-e210-4273-e37ed87c4c5c.jpeg)

> 1. 「Red」：テストを書く
2. 「Green」：実装する
3. 「Refactor」：リファクタリングをする

「Red:no_entry:」は、テストを書きますが、実行するとテストが通らない実装を書きます。
「Green:white_check_mark:」は、実装を行なっていきます。実行するとテストを通ります。
「Refactor:arrows_counterclockwise:」は、「Green」の実装を綺麗にしていきます。

※今回、どの手順かわかりやすくするためにそれっぽい色の絵文字つけていきます

# TDD_実装編
TDDの開発手順に沿って開発していこうと思います。
今回は、簡単なtodo管理アプリを作成しようと思います。
プロジェクト名は`Testable-swift`としておきます。

Xcodeでテストをする場合、`XCTest`を使用します。
`XCTest`についてや、開発の環境の整え方はこちらの記事を参考によろしくお願いします
[[iOS] ユニットテストの始め方 〜 テスト環境の作り方とXCTestの使い方 〜](https://dev.classmethod.jp/smartphone/iphone/how-to-start-ios-unit-test/)

## todoを管理するクラスの作成
todoの文字列を格納する`Array`を変数として持つだけのクラスを作成します。

```Swift:TodoList.swift
class TodoList {
	var list = Array<String>()
}
```

## todoの追加
listへtodoを追加するメソッドを作っていきます。

### Red:no_entry:
追加するメソッドだけでは、テストができないので、取得するメソッドも使用しましょう。

テストを先に書きます。
追加するメソッドを`append()`、最初の値を取得するメソッドを`getTop()`とします。

```Swift:Testable_swiftTests.swift
class Testable_swiftTests: XCTestCase {
    
	var todoList: TodoList!

	override func setUp() {
		todoList = TodoList()
	}

	func test_TodoAppendAndGet() {
		todoList.append("todo_1")
		XCTAssertEqual("todo_1", todoList.getTop())
	}
	
}
```
`setUp()`で`TodoList`のインスタンスを作って、`append`を呼んで、`getTop`で`append`と同じ値が取得出来ているのを確認します。
`TodoList`のインスタンスは他のテストでも使うので、変数にしておきます。

ここまで書くと、エラーが出ます。
`TodoList`に`append()`と`getTop()`がないというエラーが出ているので、`TodoList`に実装していきます。

```Swift:TodoList.swift
class TodoList {

	var list = Array<String>()

	func append(_ todo: String) {		
	}
	
	func getTop() -> String {
		return ""
	}

}
```
`append()`は、メソッドがあればエラーは無くなるので、空実装。`getTop()`は返り値が必要なので、空文字列を返しておきます。
テストを失敗させるので、これだけで良いです（笑）

**Testを実行して見ましょう**:point_up:
Xcodeでは、`command + U`でテストを実行できます。

見事、赤になりましたね:no_entry::clap:

### Green:white_check_mark:
テストを成功させるためには、`getTop()`の返り値を変えれば良いですね（笑）
「Green」では、このような簡単な実装で大丈夫です（笑）
単純にテストに成功すれば良いので、これからのこととかはとりあえずここでは考えません（笑）

```Swift:TodoList.swift
class TodoList {

	var list = Array<String>()

	func append(_ todo: String) {		
	}

	func getTop() -> String {
		return "todo_1"
	}

}
```

**Testを実行して見ましょう**:point_up:
テスト成功です:white_check_mark:

### Refactor:arrows_counterclockwise:
先ほど実装した、`append()`と`getTop()`をリファクタリングしていきましょう。
`append()`で追加した値を、`getTop()`で返すようにしましょう。

```Swift:TodoList.swift
class TodoList {

	var list = Array<String>()

	func append(_ todo: String) {
		list.append(todo)
	}
	
	func getTop() -> String {
		return list[0]
	}

}
```

**Testを実行して見ましょう**:point_up:
テスト成功しましたね！
これで、リファクタリングした後のコードも期待した動きをすることが保証されました。

このような手順をメソッドを実装するたびに行なっていくのが、TDDと呼ばれる開発手法です。
TDDでよく使われる手法を紹介していきます:grinning:


# TDD_手法
## フェイク
単純にテストが成功するように値を入れることを、**フェイク**と呼びます。
先ほど「Green」で行なった、テストが成功するように`todo_1`という値を入れておくのもフェイクです。

```Swift:TodoList.swift
func getTop() -> String {
	return "todo_1"
}
```

例として、listにtodoがあるかどうかを調べるメソッドを追加していきます。

### Red:no_entry:
```Swift:Testable_swiftTests.swift
func test_TodoIsEmpty() {
	XCTAssertTrue(todoList.isEmpty())
}	
```

テストを失敗させるためにfalseを返しましょう。

```Swift:TodoList.swift
class TodoList {
	var list = Array<String>()
	
	func isEmpty() -> Bool {
		return false
	}
	
}
```

**Testを実行して見ましょう**:point_up:
Test失敗！:no_entry:「Red」完了:clap:

### Green:white_check_mark:
テストを成功するには、`isEmpty`からtrueが返ってくればいいので、ここで**フェイク**としてtrueを使います！！

```Swift:TodoList.swift
func isEmpty() -> Bool {
	return true
}
```

**Testを実行して見ましょう**:point_up:
成功！！:white_check_mark:

### Refactor:arrows_counterclockwise:
ここでは省略します:sob:

## トライアンギュレーション（Triangulation）
複数のテストケースを用意し、そのテストケース全てを成功するコードを書くことで、一般解を出す方法です。

例として、listにあるtodoの個数を取得するメソッドを追加していきます。

### Red:no_entry:
```Swift:Testable_swiftTests.swift
func test_TodoAppendAndSize() {
	todoList.append("todo_1")
	XCTAssertEqual(1, todoList.size())
}
```

todoを1つ追加して、個数を取得して、1つかどうかのテストです。

```Swift:TodoList.swift
func size() -> Int {
	return 0
}
```

**Testを実行して見ましょう**:point_up:
失敗！！:no_entry:

### Green:white_check_mark:
```Swift:TodoList.swift
func size() -> Int {
	return 1
}
```

**Testを実行して見ましょう**:point_up:
成功！！:white_check_mark:

ここで**トライアンギュレーション**を使います。
### Red:no_entry:
```Swift:Testable_swiftTests.swift
func test_TodoAppendAndSize() {
	todoList.append("todo_1")
	XCTAssertEqual(1, todoList.size())
	todoList.append("todo_2")
	XCTAssertEqual(2, todoList.size())
}
```
todoの追加をもう一度行い、個数が2つになるテストを追加しました。
これで、テストが複数になりましたね！

**Testを実行して見ましょう**:point_up:
`size()`が`1`しか返さないので失敗しましたね:no_entry:

### Green:white_check_mark:
```Swift:TodoList.swift
func size() -> Int {
	return list.count
}
```
一般解として、listの個数を返すようにしました。

**Testを実行して見ましょう**:point_up:
成功しましたね！！:white_check_mark:

### Refactor:arrows_counterclockwise:
ここでは省略します:sob:

テストが１つだと、**フェイク**で済んでしまいますが、テストが複数個できた場合には対応できません。そこで、**トライアンギュレーション**を使うことで正しい実装に導きます:innocent:

# TDD_結果
```Swift:TodoList.swift
class TodoList {
	var list = Array<String>()
	
	func isEmpty() -> Bool {
		return list.count == 0
	}

	func append(_ todo: String) {
		list.append(todo)
	}
	
	func getTop() throws -> String {
		do {
			try emptyCheck()
		}
		return list[0]
	}
	
	func size() -> Int {
		return list.count
	}
	
	func removeTop() throws {
		do {
			try emptyCheck()
		}
		list.remove(at: 0)
	}
	
	func emptyCheck() throws {
		if isEmpty() {
			throw NSError(domain: "error", code: -1, userInfo: nil)
		}
	}
}
```

```Swift:Testable_swiftTests.swift
class TodoList {
	var list = Array<String>()
	
	func isEmpty() -> Bool {
		return list.count == 0
	}

	func append(_ todo: String) {
		list.append(todo)
	}
	
	func getTop() throws -> String {
		do {
			try emptyCheck()
		}
		return list[0]
	}
	
	func size() -> Int {
		return list.count
	}
	
	func removeTop() throws {
		do {
			try emptyCheck()
		}
		list.remove(at: 0)
	}
	
	func emptyCheck() throws {
		if isEmpty() {
			throw NSError(domain: "error", code: -1, userInfo: nil)
		}
	}
}
```

このようなコードができました！
今回は、[車窓からのTDD](http://objectclub.jp/technicaldoc/testing/stack_tdd.pdf)という記事を元にTDDを行なっていきました:grinning:

# 感想・まとめ
TDDになかなか手を出せずにいたのですが今回やってみました。
メソッド1つ作るにも手順が多く最初は大変ではありましたが、慣れてくると書くのが楽しくなって来ます！
テストケースはこれで足りているのかと不安になったりもしました（笑）
テストが足りてるか不安、逆にテストがあると安心というのはTDDに染まってしまった証拠かもしれませんね（笑）
皆さんもぜひTDDに手を出してみてください:innocent:

# 参考資料
[XCTestのAssert一覧](https://qiita.com/shirochan/items/10271912289dc563cc36)
[Swift 3.0 エラー処理入門](https://qiita.com/koishi/items/67cf4d0f51c4d79f1d22)