Swift + XCTest ~ Equatableプロトコルについて ~

始めに実行環境は
OS X Yosemite 10.10
Xcode6.1.1
で検証しています。

SwiftでXCTestを利用したユニットテストの実装についてです。
XCTAssertEqualで独自クラスのインスタンス同士をチェックしようとした時に、以下のようなエラーが出力されました。

スクリーンショット 2015-02-26 13.35.42

「〜 does not conform to protocol ‘Equatable’」と書かれてます。
その時の、CarEntityクラスは以下のような実装でした。

import Foundation

class CarEntity {

    private let kResponseKeyCreateDate          = "create_date"
    private let kResponseKeyName                = "name"
    private let kResponseKeyOwnerName           = "owner_name"

    //==========================================================================

    private(set) var create_date: String? = ""
    private(set) var name: String? = ""
    private(set) var owner_name: String? = ""

    required init( parameter: AnyObject? ){
        if let data = parameter as? [String: AnyObject] {
            self.create_date = data[kResponseKeyCreateDate] as? String
            self.name = data[kResponseKeyName] as? String
            self.owner_name = data[kResponseKeyOwnerName] as? String
        }
    }

}

XCTAssertEqualは「Equatable」プロトコルに対応したクラスのみ利用出来る事を知りませんでしたので、当然怒られますよね。。
■「Equatable」について:https://developer.apple.com/library/ios/documentation/General/Reference/SwiftStandardLibraryReference/Equatable.html#//apple_ref/doc/uid/TP40014608-CH17-SW1

怒られたのでじゃあどう実装しようかなと考え、最初は以下のように記述しました。

import Foundation

class CarEntity : Equatable {

    private let kResponseKeyCreateDate          = "create_date"
    private let kResponseKeyName                = "name"
    private let kResponseKeyOwnerName           = "owner_name"

    //==========================================================================

    private(set) var create_date: String? = ""
    private(set) var name: String? = ""
    private(set) var owner_name: String? = ""

    required init( parameter: AnyObject? ){
        if let data = parameter as? [String: AnyObject] {
            self.create_date = data[kResponseKeyCreateDate] as? String
            self.name = data[kResponseKeyName] as? String
            self.owner_name = data[kResponseKeyOwnerName] as? String
        }
    }

}

func ==(lhs: CarEntity, rhs: CarEntity) -> Bool {
    return  (lhs.create_date == rhs.create_date) &&
            (lhs.name == rhs.name) &&
            (lhs.owner_name == rhs.owner_name)
}

これで、一旦は簡単な例ですが以下のようにテストをパスできるようになりました。

UTResult

ただ、この方法ですと本プロジェクトで実際にインスタンス同士の「==」が行われているのであれば、問題無いのかなと思うのですがユニットテストで利用する為だけに本ソースを修正するのはなんだかなあと思いました。
そこで、以下のようにユニットテストのソース内で拡張対応するほうがベターかなと考えました。

例)テストソース内で拡張

import UIKit
import XCTest

extension CarEntity: Equatable {

}

func ==(lhs: CarEntity, rhs: CarEntity) -> Bool {
    return  (lhs.create_date == rhs.create_date) &&
            (lhs.name == rhs.name) &&
            (lhs.owner_name == rhs.owner_name)
}

class UnitTestTests: XCTestCase {

    func testExample() {
        var entity = CarEntity( parameter: ["create_date":"1996/01/01 15:00:00",
                                            "name" : "vivio",
                                            "owner_name" : "dorapro" ] )

        XCTAssertEqual(entity, entity, "失敗:異なるentity" )
    }

}

上記でテストはパスしますし、以下のようにテストが失敗する事も確認できたため、テスト環境での拡張もありかなと思うのですが、上記例のCarEntityに新しいメンバーが追加された場合、ユニットテストの継続性、信頼性にすぐ問題が出てくるなとも感じ、ジレンマに陥ることに、、

UTResultFaile

しかし、ユニットテストのソース側に以下のようなコードを記述した場合、ビルドエラーとなるため厳密な等価を求める方には合わないかと思いますし、やはり同一ソースファイル内でEquatableプロトコルの対応を行うに1票を入れたいと思います。

import UIKit
import XCTest

extension CarEntity: Equatable {

}

func ==(lhs: CarEntity, rhs: CarEntity) -> Bool {
    return  (lhs.create_date == rhs.create_date) &&
            (lhs.name == rhs.name) &&
            (lhs.owner_name == rhs.owner_name) &&
            (lhs.kResponseKeyName == rhs.kResponseKeyName) // ← スコープ外でビルドエラーとなる

}

class UnitTestTests: XCTestCase {
 // 省略

この記事を書いた人

いもし
いもし
いもしです。 少し前までbitcoinFXを研究していましたが、育児奮闘の為停止中。 好きな言葉は「日々是好日」です。
Pocket