Swiftで遊ぼう! - 379 - Define Your Data Model
- Swiftで遊ぼう!の前書き-> Life-LOG OtherSide
- 初心者はここから!-> 50オヤジでもできるiOS開発
- 私の本業、オフィシャルなブログ-> Life-LOG
- Swift 3 対応
2017年3月28日:Swift 3に対応*1
入力したデータを保持するデータ・モデルの実装に取りかかる。
- データ・モデルの作成
- カスタム・クラスをコードするときにfailableイニシャライザ(失敗許容イニシャライザ)を書く
- failableとnonfailableイニシャライザの相違点を概念的に理解する。
- ユニットテストを書いて実行することでデータ・モデルをテストする。
File > New > Fileを選んで、左側のペインからiOSのSourceを選び、右側のペインからSwift Fileを選びます。これはRatingControlクラスを用意したときと時と異なります。
データ・モデルは、継承するクラスのいらないクリーンなBase Classになるからです。名前を「Meal」にしてFoodTrackerプロジェクトに保存します。このとき注意すべきことは、Targetsのセクションの「FoodTracker」と「FoodTrackerTests」の両方にチェックマークを入れます。これは今回の課題になるユニットテストを有効にするステップです。
このデータ・モデルとなるMeal.swiftに保存するデータはString型の食事名「name」、そしてUIImage型の写真「photo」、写真は無いかもしれないのでオプショナルにします。そしてInt型の評価を「rating」にします。
import UIKit class Meal { // MARK: Properties var name: String var photo: UIImage? var rating: Int }
カスタムクラスのこーディンで重要なのは必ず必要な指定イニシャライザです。インスタンス生成時に初期値となる値を作る基本的なイニシャライザです。
init(name: String, photo: UIImage?, rating: Int) { self.name = name self.photo = photo self.rating = rating }
これで十分ですか? ちょっと考えてみるとインスタンスを作るのに初期値のnameが抜けていたり、ratingがマイナス値だったらどうなるでしょう? ランタイムエラーが生じます。これを回避するためにnil値を扱えるイニシャライザを用意するんです。nil値を返すイニシャライザを「失敗許容イニシャライザ(failable initializer)」といって、init()に「?」を付けます。
init?(name: String, photo: UIImage?, rating: Int) { if name.isEmpty || rating < 0 { return nil // これで「nil」を返す可能性ができました。 // イニシャライザに「?」は必要です。 } self.name = name self.photo = photo self.rating = rating }
自分で作ったデータ・モデルをユニット・テストでテストしてみます。
XcodeでSingle View Applicationを作ったらテンプレートとして、ユニット・テストファイルを自動的に作っています。プロジェクトナビゲータを選択します。
既にテンプレートで色々用意されています。「import XCTest」が示すように、XCTestフレームワークが呼び込まれていることを考えると、このユニット・テストというのも奥深いものが予想されます。
stUp()やtearDown()とったメソッドも今のところどう使ったらいいのか分かりません。
基本的にファンクショナル・テストとパフォーマンス・テストがあり、ファンクショナルテストは何か「値」を作り出すコーディングをテストするもので、パフォーマンス・テストは処理に時間がかかる重いコードのテストですが、今回は両方とも必要ないみたいです。
テンプレートで用意されているメソッドをすべて消去します。
import XCTest @testable import FoodTracker class FoodTrackerTests: XCTestCase { }
今回テストするのは、Meal.swiftのイニシャライザです。これはインスタンスという「値」が生成されるファンクショナル・テストになります。
自分で分かりやすいネーミングで作ります、決まり事として、「test」を加える必要はあります。そして、テストするクラスの名前「Meal」を加えた方が分かりやすいでしょう。
testMealInitializationSucceeds()にします。
そして、テストメソッドというべきなのか「XCTAssertNotNil」と「XCTAssertNil」があり、このテストに「値」を与えることで確認していくんです。
まず、イニシャライザが上手く成功する例を想定してインスタンス化させてみます。
func testMealInitializationSucceeds() { // Zero rating let zeroRatingMeal = Meal.init(name: "Zero", photo: nil, rating: 0) XCTAssertNotNil(zeroRatingMeal) // Highest positive rating let positiveRatingMeal = Meal.init(name: "Positive", photo: nil, rating: 5) XCTAssertNotNil(positiveRatingMeal) } // Mealの初期化に与える引数に成功しそうな例を加えてpotentialItemを生成してみます。 // そしてこれをXCTAssertNotNilに投げてみるんです。
次に、nil値が戻される例をテストします。
func testMealInitializationFails() { // Negative rating let negativeRatingMeal = Meal.init(name: "Negative", photo: nil, rating: -1) XCTAssertNil(negativeRatingMeal) // Empty String let emptyStringMeal = Meal.init(name: "", photo: nil, rating: 0) XCTAssertNil(emptyStringMeal) // 引数に名無しを与えるとnilになるはずなので、XCTAssertNilに投げます。
次はあえて、テストがエラーになるであろう値を与えてみます。
// Rating exceeds maximum let largeRatingMeal = Meal.init(name: "Large", photo: nil, rating: 6) XCTAssertNil(largeRatingMeal) // ratingに5以上の数が与えられると、nilが返されると仮定しています。
ではユニット・テストをします。「Cmd + U」を押します。
まあ当然と言えば当然ですね。じゃあテストが通るようにMealクラスのイニシャライザを変更します。
init?(name: String, photo: UIImage?, rating: Int) { guard !(name.isEmpty) else { return nil } guard (rating >= 0) && (rating <= 5) else { return nil } self.name = name self.photo = photo self.rating = rating }
こうしてからテストします。
上手くいきました。こういう風にテストするのですが、これが使えるかな?
次に進む→Swiftで遊ぼう! - 381 - Create a Table View - Swiftで遊ぼう! on Hatena
*1:2015年10月24日改訂、2015年10月19日改訂:環境環境:Xcode7.1、Swift2.1