Appleの新言語「Swift」を使ったテスト駆動開発と、機能の紹介
こんにちは、2014年新卒エンジニアの小笠原です。今日は、最近話題の「Swift」についての記事をお届けします。
新言語「Swift」とは
新プログラミング「Swift」は、先日のWWDCで突如として発表された、Appleの作った新プログラミング言語です。Objective-Cに比べてモダンな文法が盛り込まれていたり(どことなくScalaやC#に似ていたり)、速度が早くなっている特徴があります。
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() {
// 省略
}
}
testFizz
、testBuzz
、testFizzBuzz
、いずれもテストが失敗することがわかると思います。
数字を返したときと同様に、テストが通るようなコードを追加してあげます。
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の機能をいくつか紹介できたと思います。
また、SwiftとObjective-Cは、共存することが可能だそうです。1つのプロジェクトの中で、2種類の言語を使うことができることが記されています(Using Swift with Cocoa and Objective-C: Swift and Objective-C in the Same Project)。
Swiftを使ってみたい方々は、テストコードだけでもSwiftで書いてみて、徐々にSwiftに移行してみてはいかがでしょうか・・・!