読者です 読者をやめる 読者になる 読者になる

Appleの新言語「Swift」を使ったテスト駆動開発と、機能の紹介

こんにちは、2014年新卒エンジニアの小笠原です。今日は、最近話題の「Swift」についての記事をお届けします。

新言語「Swift」とは

 


新プログラミング「Swift」は、先日のWWDCで突如として発表された、Appleの作った新プログラミング言語です。Objective-Cに比べてモダンな文法が盛り込まれていたり(どことなくScalaC#に似ていたり)、速度が早くなっている特徴があります。

Xcodeとの親和性の高い連携も示唆されており、今後広まっていく可能性が十分にあると思います。

FizzBuzzとは

FizzBuzzとは、プログラミングの課題などでよく出される問題で、1から順番に数字のループを行い、3の倍数のときは"Fizz"、5の倍数の時は"Buzz"、両方の倍数のとき(15の倍数のとき)は"Fizz Buzz"、どちらの倍数でもないときはその数字を出力せよ、という問題です。


今回は、ある数字に対応したFizzBuzz文字列を出力する関数のユニットテストを通して、 Swiftの言語機能の紹介 や、 Swiftでのテスト駆動開発を紹介します。

クラスを作成

まずは、FizzBuzzのテストクラスを作成しましょう。Objective-Cのときは XCTestCaseを継承したクラスを作り、test~で始まるメソッドを実装していました。Swiftでも同様かは、まだ情報が公開されていませんが、同じようにテストクラスを作ってみます。

import XCTest

class FizzBuzzTest: XCTestCase {
}


通常であれば、FizzBuzzクラスとFizzBuzzTestクラスは別のファイルなので、FizzBuzzクラスをimportする必要がありますが、今回は1ファイルに定義してしまいます。

class FizzBuzz {
}

 

テストを追加

次に、FizzBuzzの仕様を、テストケースとして追加してあげます。今回考えるFizzBuzzの仕様は、次のような感じです。

FizzBuzz.echo(1) // "1"
FizzBuzz.echo(3) // "Fizz"
FizzBuzz.echo(5) // "Buzz"
FizzBuzz.echo(15) // "Fizz Buzz"


まずは最初のテストケースとして、3でも5でも割り切れない数に対して(1を受け取ったら"1")、数字がそのまま戻ってくることを確かめてみます。

import XCTest


class FizzBuzzTest: XCTestCase {
    func testNumber() {
        XCTAssertEqual(FizzBuzz.echo(2), "2")
        XCTAssertEqual(FizzBuzz.echo(113), "113")
    }
}


当然のことながら、まだFizzBuzz.echo()は定義されていないので、コンパイルが通りません。そこで、空の定義を追加してあげます。

class FizzBuzz {
    class func echo(i:Int) -> String {
        return ""
    }
}

とりあえずコンパイルは通るようになりましたが、現在のFizzBuzz.echo(2)""(空の文字列)を返しますので、もちろんテストは失敗します
次は、テストを通すように、 FizzBuzz クラスを書き換えてみます。

class FizzBuzz {
    class func echo(i:Int) -> String {
        return String(i)
    }
}


すると、テストが成功するのが確認できると思います! しかし、"Fizz"や"Buzz"や"Fizz Buzz"を返すべきケースに対しては正しく動かないはずですので、これを確認するテストケースを追加してあげます。
本当なら一つづつテストケースを追加してあげたほうがいいと思いますが、今回は省略のため一気に追加しました。

import XCTest


class FizzBuzzTest: XCTestCase {
    func testFizz() {
        XCTAssertEqual(FizzBuzz.echo(3), "Fizz")
        XCTAssertEqual(FizzBuzz.echo(42), "Fizz")
    }


    func testBuzz() {
        XCTAssertEqual(FizzBuzz.echo(5), "Buzz")
        XCTAssertEqual(FizzBuzz.echo(50), "Buzz")
    }


    func testFizzBuzz() {
        XCTAssertEqual(FizzBuzz.echo(15), "Fizz Buzz")
        XCTAssertEqual(FizzBuzz.echo(150), "Fizz Buzz")
    }


    func testNumber() {
        // 省略
    }
}

testFizztestBuzztestFizzBuzz、いずれもテストが失敗することがわかると思います。
数字を返したときと同様に、テストが通るようなコードを追加してあげます。

class FizzBuzz {
    class func echo(i:Int) -> String {
        switch i % 15 {
        case 0:
            return "Fizz Buzz"
        case 3, 6, 9, 12:
            return "Fizz"
        case 5, 10:
            return "Buzz"
        default:
            return String(i)
        }
    }
}


すると、テストが成功することが確認できると思います!

文法の紹介

ここでSwiftの新しい文法を確認してみましょう。

1つは、関数の定義方法が違います。関数の定義方法は

func 関数名(引数名:引数の型) -> returnの型 {
    // 関数の中身
}


というふうに定義します。少しScalaに似ている感じがしますね! ちなみに、funcの前のclassは、クラスメソッド(スタティックメソッド)を表すためのものです。

次に目を引くのは、新しい switch 文です。 case に複数の値を選択できるようになったりしています。

そして最後に、セミコロンがない! 素晴らしい!

数値リストに対するFizzBuzz

先ほどの例は、一つの数値に対して、対応するFizzBuzzを返しました(例: FizzBuzz.echo(5) == "Buzz")。次は、数値列(数値の配列型)に対して、対応するFizzBuzzを返すような関数を定義してみましょう。

class FizzBuzz {
    class func echo(i:Int) -> String {
        // 省略
    }


    class func echo(numbers:Integer[]) -> String[] {
        return String[]()
    }
}


こちらは、空のString配列を返すように、一旦定義してみました。次に、対応するテストケースを追加します。

 func testNumberArray() {
        XCTAssertEqualObjects(FizzBuzz.echo([1, 2, 3, 4, 5]), ["1", "2", "Fizz", "4", "Buzz"])
        XCTAssertEqualObjects(FizzBuzz.echo([14, 15, 16]), ["14", "Fizz Buzz", "16"])
    }


テストが失敗することを確認したら、テストが成功するようなコードを書いてあげます。

class FizzBuzz {
    class func echo(i:Int) -> String {
        // 省略
    }


    class func echo(numbers:Int[]) -> String[] {
        var result = String[]()
        for i in numbers {
            result += FizzBuzz.echo(i)
        }
        return result
    }
}


そうすると、テストが成功することが確認できると思います!

文法の紹介

ここで、新しい文法を確認してみます。

1つめは、配列の定義です。配列型は Type[] というように定義することができます。

次に、for...inの構文です。配列に対しては、for...inの構文を使って、要素に対するループを行うことができます。

そして3つめは、配列への値の追加を、+= 演算子で行うことができます。

リファクタリング&Closure

実は、このメソッドは、冗長な書き方をしました。Swiftは、配列に対するmapをサポートしていますので、一行で書くことができます。

class FizzBuzz {
    class func echo(i:Int) -> String {
        // 省略
    }


    class func echo(numbers:Int[]) -> String[] {
        return numbers.map({ i in FizzBuzz.echo(i) })
    }
}


こちらも同様にテストが成功するはずです。コード中の型が自明なときは、引数の型や戻り値の型を省略して書くことができます。

return numbers.map({ i in FizzBuzz.echo(i) })


上記は、Swiftの機能のクロージャショートハンド表記で、以下のものと同じです。

 return numbers.map({
            (i:Int) -> String in
            return FizzBuzz.echo(i)
        })

まとめ

ここまで書いてきたコードをまとめると、以下のようになります。

import XCTest


class FizzBuzz {
    class func echo(i:Int) -> String {
        switch i % 15 {
        case 0:
            return "Fizz Buzz"
        case 3, 6, 9, 12:
            return "Fizz"
        case 5, 10:
            return "Buzz"
        default:
            return String(i)
        }
    }


    class func echo(numbers:Int[]) -> String[] {
        return numbers.map({ i in FizzBuzz.echo(i) })


    }
}


class FizzBuzzTest: XCTestCase {
    func testFizz() {
        XCTAssertEqual(FizzBuzz.echo(3), "Fizz")
        XCTAssertEqual(FizzBuzz.echo(42), "Fizz")
    }


    func testBuzz() {
        XCTAssertEqual(FizzBuzz.echo(5), "Buzz")
        XCTAssertEqual(FizzBuzz.echo(50), "Buzz")
    }


    func testFizzBuzz() {
        XCTAssertEqual(FizzBuzz.echo(15), "Fizz Buzz")
        XCTAssertEqual(FizzBuzz.echo(150), "Fizz Buzz")
    }


    func testNumber() {
        XCTAssertEqual(FizzBuzz.echo(2), "2")
        XCTAssertEqual(FizzBuzz.echo(113), "113")
    }


    func testNumberArray() {
        XCTAssertEqualObjects(FizzBuzz.echo([1, 2, 3, 4, 5]), ["1", "2", "Fizz", "4", "Buzz"])
        XCTAssertEqualObjects(FizzBuzz.echo([14, 15, 16]), ["14", "Fizz Buzz", "16"])
    }}


簡単なFizzBuzzという問題でしたが、SwiftでのTDDと、Swiftの機能をいくつか紹介できたと思います。


また、SwiftObjective-Cは、共存することが可能だそうです。1つのプロジェクトの中で、2種類の言語を使うことができることが記されています(Using Swift with Cocoa and Objective-C: Swift and Objective-C in the Same Project)。


Swiftを使ってみたい方々は、テストコードだけでもSwiftで書いてみて、徐々にSwiftに移行してみてはいかがでしょうか・・・!