始めに実行環境は
OS X Yosemite 10.10
Xcode6.1.1
で検証しています。
SwiftでXCTestを利用したユニットテストの実装についてです。
XCTAssertEqualで独自クラスのインスタンス同士をチェックしようとした時に、以下のようなエラーが出力されました。
「〜 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) }
これで、一旦は簡単な例ですが以下のようにテストをパスできるようになりました。
ただ、この方法ですと本プロジェクトで実際にインスタンス同士の「==」が行われているのであれば、問題無いのかなと思うのですがユニットテストで利用する為だけに本ソースを修正するのはなんだかなあと思いました。
そこで、以下のようにユニットテストのソース内で拡張対応するほうがベターかなと考えました。
例)テストソース内で拡張
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に新しいメンバーが追加された場合、ユニットテストの継続性、信頼性にすぐ問題が出てくるなとも感じ、ジレンマに陥ることに、、
しかし、ユニットテストのソース側に以下のようなコードを記述した場合、ビルドエラーとなるため厳密な等価を求める方には合わないかと思いますし、やはり同一ソースファイル内で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を研究していましたが、育児奮闘の為停止中。 好きな言葉は「日々是好日」です。
この投稿者の最近の記事
- ReMINE開発秘話2016.08.24Maker Faire Tokyo 2016に出展してきました!
- iOS2015.02.27Swift + XCTest ~ Equatableプロトコルについて ~
- iOS2014.08.25実行速度計ってみましたその1_Objective-C,Swiftでのfor文