Swiftで遊ぼう! on Hatena

あしたさぬきblogでやってた初心者オヤジのiOSプログラミング奮闘記がHatenaに来ました

Swiftで遊ぼう! - 379 - Define Your Data Model

2017年3月28日:Swift 3に対応*1

チュートリアル索引に戻る→

developer.apple.com

入力したデータを保持するデータ・モデルの実装に取りかかる。

  • データ・モデルの作成
  • カスタム・クラスをコードするときに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を作ったらテンプレートとして、ユニット・テストファイルを自動的に作っています。プロジェクトナビゲータを選択します。

f:id:yataiblue:20150723183719j:plain

既にテンプレートで色々用意されています。「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」を押します。

f:id:yataiblue:20170328182837j:plain

まあ当然と言えば当然ですね。じゃあテストが通るように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
        
}

こうしてからテストします。

f:id:yataiblue:20170328183331j:plain

上手くいきました。こういう風にテストするのですが、これが使えるかな?

次に進む→Swiftで遊ぼう! - 381 - Create a Table View - Swiftで遊ぼう! on Hatena

*1:2015年10月24日改訂、2015年10月19日改訂:環境環境:Xcode7.1Swift2.1