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


TDDとは

Test-Driven Developmentのこと。ある機能に対して期待する結果を、プロダクトコードより先に”失敗するテスト”として書き(テストファースト)、実装のゴールを目指す開発スタイル。同時に、「テストしやすい」 == 「よい設計」と捉えるならば、よい設計手法とも言えます。「心理的安全を得たままにプロダクトコードの改善をすること」が目的の1つとなります。

■メリット

  • メンテナンスがしやすい
  • デバッグにかける時間を減らせる
  • 素早い設計判断、設計改善を行える

 
■デメリット

  • チームの成熟度やプロジェクトの状況によっては、アダとなることがある
  • 仕様変更が生じたと時は、テストのメンテナンスも発生する
  • ビルド時間がテストファーストの負荷を上げる

レッド/グリーン/リファクタリング

1、レッド:これから作るもののゴールを定める(テストを書く)
2、グリーン:期待する結果を満たす足場を作る(プロダクトコードを書く)
3、リファクタリング:作った羽柴を保ちながら改良を行う(コード整理、共通化、設計見直し)

開発環境

  • Xcode 10.3
  • Swift
  • XCTest

作るもの

ポーカー

まずは、実装したいことをTODOリストに書き起こす

[目的]
-スート(suit)とランク(rank)を与えて、カード(card)を生成する
-生成したカードから文字列表記(notation)を取得する

↓TODOリストに置き換える

[TODOリスト]

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

プロジェクト作成

1.Xcode > Create a new Xcode Project > Single View App
2.プロジェクト名:TDDPokerBySwift
3.Include Unit Testsのチェックをオン
4.Next > ディレクトリ指定 > Create

初回テスト

Command + U でテスト実行

Build SuccessでOK

実際にテストを作成する

TDDPokerBySwiftTests.swiftを開いて、以下のように修正する

TDDPokerBySwiftTests.swift
import XCTest
@testable import TDDPokerBySwift

class TDDPokerBySwiftTests: XCTestCase {

    func testInitializeCard() {
        let card1 = Card(suit: .heart, rank: .three)
        XCTAssertEqual(card1.suit, .heart)
        XCTAssertEqual(card1.rank, .three)

        let card2 = Card(suit: .spade, rank: .jack)
        XCTAssertEqual(card2.suit, .spade)
        XCTAssertEqual(card2.rank, .jack)
    }
}

Command + U でテスト実行してみましょう。
当然、エラーになります。これは、テストコードのエラーでなはく、単純にCardクラスが存在しないためのコンパイルエラーです。

Cardクラスを作成しましょう。今回もMVVMで作成していければと思いますので、
Project Navigator > TDDPokerBySwiftグループ > 右クリック > New Groupで、
- Models
- Views
- ViewControllers
を作成して、Models/Card.swiftを作成します。

Card.swift
import Foundation

struct Card {

    enum Suit {
        case spade
        case heart
        case club
        case diamond
    }

    enum Rank {
        case ace
        case two
        case three
        case four
        case five
        case six
        case seven
        case eight
        case nine
        case ten
        case jack
        case queen
        case king
    }

    let suit: Suit
    let rank: Rank
}

それでは、Command + U でテスト実行してみましょう。
Build Succeeded > Test Succeeded となれば成功です。

ここまでで、レッド > グリーンまで作業しました。
仕上げに、リファクタリングです。

テストコードを以下に書き換えましょう。

TDDPokerBySwiftTests.swift
import XCTest
@testable import TDDPokerBySwift

class TDDPokerBySwiftTests: XCTestCase {

    func testInitializeCard() {

        var card: Card

        card = Card(suit: .heart, rank: .three)
        XCTAssertEqual(card.suit, .heart)
        XCTAssertEqual(card.rank, .three)

        card = Card(suit: .spade, rank: .jack)
        XCTAssertEqual(card.suit, .spade)
        XCTAssertEqual(card.rank, .jack)
    }
}

ローカル変数cardに統一したことで、直前に代入しているカードにアサーションをかけていると読めるようになりました。
それでは、Command + U でテスト実行してみましょう。

問題なければ、TODOリストにチェックを入れます。

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

今回やったこと、理解したこと

  • TODOリストを書いて、取り組むべき問題を細分化した
  • テストの失敗か環境によるものかを切り分ける為に、テストを書き始める前にプロジェクトのテストを一度実行した
  • テストケースから考えることで、型をうまく利用し、考慮しなければならないテストケースを減らした
  • レッド > グリーン > リファクタリング まで一貫して行った

まとめ

今回やったことのようなシンプルなinitは、実際、テスト対象にはならないようなものだそうです。次節において、本格的なTDDが始まると思いますので、引き続き、頑張っていきましょう。

参考

演習問題
http://devtesting.jp/tddbc/?TDDBC%E4%BB%99%E5%8F%B007%2F%E8%AA%B2%E9%A1%8C

サンプルリポジトリ
https://github.com/ktanaka117/TDDPokerBySwift