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

UnitTest 用のデータを共通化する【Swift】

テストをする際には複数のオブジェクトの初期化をする必要があり、この作業が面倒でテストを書くのが面倒になってしまうことがあったので、どうにかできないかと思い調べてみたところ、良さそうな方法がありました。

clean-swift.com

これを、自分が使いやすいと思った形に少しアレンジしたものを紹介してみようと思います。

Seeds Struct

以下のように Human というクラスがあったとします。

class Human {
    
    var name: String
    var age: Int
    var height: Double
    var job: String
    
    init(name: String, age: Int, height: Double, job: String) {
        self.name = name
        self.age = age
        self.height = height
        self.job = job
    }
    
}

この Human に関するテストを書くといったときに、以下のようにテストを書く度に値を指定して初期化していくかと思いますが、本当に全部の値を指定しなければならないというケースはそれほど多くはないのではないかと思います。

let takeru = Human(name: "takeru", age: 30, height: 180.0, job: "engineer")
let rika = Human(name: "rika", age: 28, height: 160.0, job: "designer")

こういった時に上記の記事に書いてあるようにして Seeds というテスト用のデータを持つ Struct を用意することで、Xcode 等の補完機能を用いながら以下のように簡単にデータを作成することができます。

struct Seeds {
    
    struct Humans {
        
        static let takeru = Human(name: "takeru", age: 30, height: 180.0, job: "engineer")
        static let rika = Human(name: "rika", age: 28, height: 160.0, job: "designer")
        
    }
    
}

Seeds.Humans.takeru
 => Human(name: "takeru", age: 30, height: 180.0, job: "engineer")
Seeds.Humans.rika
 => Human(name: "rika", age: 28, height: 160.0, job: "designer")

Problem

ですが、上記の様に Stored Property を static で宣言してしまうとシングルトンになってしまいます。
そのため、テスト間でオブジェクトのプロパティの値を変更する時に、思わぬところでテストが落ちてしまうということがあるかもしれないので、値を変更する際には気をつけなければなりません。

let takeru = Seeds.Humans.takeru
takeru.age = 10

// 一度プロパティの値を変更するとその後も維持されてしまう
Seeds.Humans.takeru.age
 => 10

Computed Property

そこで、テストデータの定義には以下のように Computed Property を用いるようにします。
こうすることで、以下のように Seeds からオブジェクトを呼ぶ出す度に異なるオブジェクトが返ってくるため、安心してプロパティの値を変更することができます。

struct Seeds {
    
    struct Humans {
        
        static var takeru: Human {
            return Human(name: "takeru", age: 30, height: 180.0, job: "engineer")
        }
        static var rika: Human {
            return Human(name: "rika", age: 28, height: 160.0, job: "designer")
        }
        
    }
    
}

let takeru = Seeds.Humans.takeru
takeru.age = 10

// 異なるオブジェクトなので 30 のまま
Seeds.Humans.takeru.age
 => 30

Unique Value

先ほどの Human に一意の値となる myNumber というプロパティを持たせる必要が生じたとします。

class Human {
    
    var name: String
    var age: Int
    var height: Double
    var job: String
    let myNumber: Int // 追加
    
    init(name: String, age: Int, height: Double, job: String, myNumber: Int) {
        self.name = name
        self.age = age
        self.height = height
        self.job = job
        self.myNumber = myNumber // 追加
    }
    
}

こういった、 オブジェクト毎に異なる一意の値を持たせたいという場合においても、Seeds に以下の uniqueInt の様なコンピューテッドプロパティを持たせることで、オブジェクトを呼び出す度に自動的に持たせることが可能となります。

struct Seeds {
    
    static var increment = 0
    static var uniqueInt: Int {
        increment += 1
        return increment
    }
    
    struct Humans {
        
        static var takeru: Human {
            return Human(name: "takeru", age: 30, height: 180.0, job: "engineer", myNumber: Seeds.uniqueInt)
        }
        static var rika: Human {
            return Human(name: "rika", age: 28, height: 160.0, job: "designer", myNumber: Seeds.uniqueInt)
        }
        
    }
    
}

Seeds.Humans.takeru.myNumber
 => 1
Seeds.Humans.takeru.myNumber
 => 2

テストは人間が見過ごしてしまうような部分や人間がすべきでないような部分をカバーすることができるので書くに越したことはないのですが、やはり辛い面も多々あるかと思うので、テストを書く度に毎回同じ様なオブジェクトを用意していて辛いなあと感じたら、こういったテストデータ作るくんを用意してみてはいかがでしょうか!