いきなり！TDD(当方テスト初心者でいきなりTDD！) 2日目

#はじめに

[前回](https://qiita.com/drafts/388f56c44526d5321b27)は、TDDの概要と、簡単なinitをサンプルに、

レッド(テストを書く) > グリーン (プロダクトコードを書く) > リファクタリング(コードを整理する)

まで行いました。引き続き、TDDを行なっていきます。


#開発環境
- Xcode 10.3
- Swift
- XCTest

#作るもの

ポーカー

#現在のTODOリスト

[TODOリスト]

- [x]  Cardを定義して、インスタンスを作成する
 - [x]  CardはSuitを持つ
 - [x]  CardはRankを持つ
- [ ]  Cardのインスタンスから文字列表記(notation)を取得する

それでは、最下段のTODOから再開していきましょう。

#実装

まずは、レッドからです。TDDPokerBySwiftTests.swiftに以下を追加します。

```TDDPokerBySwiftTests.swift
class TDDPokerBySwiftTests: XCTestCase {

   (省略)

   func testCardNotation() {
      let card = Card(suit: .heart, rank: .three)
      XCTAssertEqual(card.notation, "3♥")
   }
}
```

Command + U でテストを実行してみましょう。
<img width="700" alt="スクリーンショット 2019-08-08 18.54.50.png" src="https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/468725/9d36bada-cf3a-2a73-585d-11c691c91f1c.png">

Cardにnotationプロパティが存在していないため、当然エラーになります。これはテストエラーではなく、単純にコンパイルエラーです。

それでは、早速、Cardにnotationプロパティを追加して、テストをグリーンにしていきましょう。

###仮実装

Card.swiftに以下を追加します。

```Card.swift
struct Card {
    
   (省略)
    
    var notation: String {
        return "3♥"
    }
}
```
Command + U でテストを実行してみましょう。
テストが成功すると思います。このnotationプロパティは、initで代入されるrankとsuitから生成されるものなので、外から代入された値を保持するStored Propertyではなく、Computed Propertyによる実装になっています。

####Stored Property (保持型プロパティ)
今回で言う所の、Cardクラスのsuitとrankがそうですね。willSet、didSetなどで監視もでき、lazy修飾詞も使用可能です。

```Card.swift
let suit: Suit
let rank: Rank
```

####Computed Property (計算型プロパティ)
それ自体は値を保持せず、setter/getterで値を計算し(set)または返す(get)プロパティです。上記notationの定義がそれになるのですが、上記は、getのみのreadonly定義になっており、get{}が省略されています。省略しない場合には、以下のようになります。

```Card.swift
var notation: String {
    get {
        return "3♥"
    }
}
```
setterを記述する場合には、

```
var notation: String {
    get {
        return "3♥"
    }

    set(t) {
        print("セット！\(t)");
    }
}
```

や、

```
var notation: String {
    get {
        return "3♥"
    }

    set {
        print("セット！\(newValue)");
    }
}
```
で、notationの値がセットされた時に、setが呼ばれます。set{}内では、Stored Propertyを含めた計算なども可能です。

具体的な使い方の例です。

```
class Person {
    var birthday: NSDate    // 生年月日
    var age: Int {          // 年齢
        get {
            let components = self.calendar.components(.CalendarUnitYear, fromDate: birthday, toDate: NSDate.date(), options: .allZeros)
            return components.year
        }
        set(newAge) {
            let diff = self.age - newAge
            if diff != 0 {
                self.birthday = self.calendar.dateByAddingUnit(.CalendarUnitYear, value: diff, toDate: self.birthday, options: .allZeros)
            }
        }
    }
    let dateFormatter: NSDateFormatter  // 日付フォーマッタ
    let calendar: NSCalendar            // カレンダー
    // イニシャライザ
    init(birthday: String) {
        self.dateFormatter = NSDateFormatter();
        self.dateFormatter.dateFormat = "Y/M/d"
        self.calendar = NSCalendar.currentCalendar()
        self.birthday = self.dateFormatter.dateFromString(birthday)!
    }
}

let p = Person(birthday: "1980/12/31")
print(p.age)
p.age = p.age - 2
print(p.birthday)     // 1982/12/31
```

#三角測量

話を戻します。現在、notationは"3♥"を静的に返しているだけなので、一般化が保証されてません。テストを修正して、別のケースも追加します。

```TDDPokerBySwiftTests.swift
class TDDPokerBySwiftTests: XCTestCase {

    (省略)
    
    func testCardNotation() {
        let card1 = Card(suit: .heart, rank: .three)
        XCTAssertEqual(card1.notation, "3♥")
        
        let card2 = Card(suit: .spade, rank: .jack)
        XCTAssertEqual(card2.notation, "J♠")
    }
}

```
このように2つ以上のテストケースを用意することで、機能の一般化を導き出すテクニックを「三角測量」と言います。テストがグリーンになるように、Card.swiftを書き換えます。enumをStringで定義し直し、各caseに文字列を代入します。取り出す際には、rawValueで取り出せます。

```Card.swift
struct Card {
    
    enum Suit: String {
        case spade = "♠"
        case heart = "♥"
        case club = "♣"
        case diamond = "♦"
    }
    
    enum Rank: String {
        case ace    = "A"
        case two    = "2"
        case three  = "3"
        case four   = "4"
        case five   = "5"
        case six    = "6"
        case seven  = "7"
        case eight  = "8"
        case nine   = "9"
        case ten    = "10"
        case jack   = "J"
        case queen  = "Q"
        case king   = "K"
    }
    
    enum CardType {
        case other
    }
    
    let suit: Suit
    let rank: Rank
    
    var notation: String {
        return rank.rawValue + suit.rawValue
    }
}
```
Command + U でテストを実行しましょう。テストが成功すれば、グリーンのステップを終えたことになるので、リファクタリングを行います。今回も、cardの変数をテストケースごとに初期化して、共通利用できるようにするだけです。

```TDDPokerBySwiftTests.swift
class TDDPokerBySwiftTests: XCTestCase {

    (省略)
    
    func testCardNotation() {
        var card: Card

        card = Card(suit: .heart, rank: .three)
        XCTAssertEqual(card.notation, "3♥")
        
        card = Card(suit: .spade, rank: .jack)
        XCTAssertEqual(card.notation, "J♠")
    }
}
```

#まとめ
今回は、

- 仮実装
- 三角測量

について学びました。
次回は、「明白な実装」というものについて、学んで行きたいと思います。

#参考

プロパティ
https://tea-leaves.jp/swift/content/%E3%83%97%E3%83%AD%E3%83%91%E3%83%86%E3%82%A3