主に依存性注入のために、Core Dataを使用してオブジェクトのグラフを管理しようとしています(NSManagedObjectsのサブセットは永続化する必要がありますが、それは私の質問の焦点ではありません)。単体テストを実行するときは、NSManagedObjectsの作成を引き継ぎ、モックに置き換えたいと思います。
今のところ、これを行うための候補となる手段があります。これは、ランタイムのmethod_exchangeImplementationsを使用し[NSEntityDescription insertNewObjectForEntityForName:inManagedObjectContext:]
て、自分の実装と交換することです(つまり、モックを返す)。これは、私が行った小さなテストで機能します。
これに関して2つの質問があります。
- insertNewObjectForEntityForName:inManagedObjectContextをスウィズリングするよりも、Core Dataのオブジェクト作成を置き換えるためのより良い方法はありますか?私はランタイムやコアデータに深く踏み込んでおらず、明らかな何かが欠けている可能性があります。
- 私の置換オブジェクト作成メソッドの概念は、モックされたNSManagedObjectsを返すことです。私はOCMockを使用していますが、動的な
@property
sのため、NSManagedObjectサブクラスを直接モックすることはありません。今のところ、NSManagedObjectのクライアントは具象オブジェクトではなくプロトコルと通信しているため、具象オブジェクトではなくモックされたプロトコルを返します。もっと良い方法はありますか?
これが私が得ているものを説明するためのいくつかの疑似的なコードです。これが私がテストしているかもしれないクラスです:
@interface ClassUnderTest : NSObject
- (id) initWithAnObject:(Thingy *)anObject anotherObject:(Thingo *)anotherObject;
@end
@interface ClassUnderTest()
@property (strong, nonatomic, readonly) Thingy *myThingy;
@property (strong, nonatomic, readonly) Thingo *myThingo;
@end
@implementation ClassUnderTest
@synthesize myThingy = _myThingy, myThingo = _myThingo;
- (id) initWithAnObject:(Thingy *)anObject anotherObject:(Thingo *)anotherObject {
if((self = [super init])) {
_myThingy = anObject;
_myThingo = anotherObject;
}
return self;
}
@end
おそらく永続性などのために、ThingyおよびThingo NSManagedObjectサブクラスを作成することにしましたが、initを次のようなものに置き換えることもできます。
@interface ClassUnderTest : NSObject
- (id) initWithManageObjectContext:(NSManagedObjectContext *)context;
@end
@implementation ClassUnderTest
@synthesize myThingy = managedObjectContext= _managedObjectContext, _myThingy, myThingo = _myThingo;
- (id) initWithManageObjectContext:(NSManagedObjectContext *)context {
if((self = [super init])) {
_managedObjectContext = context;
_myThingy = [NSEntityDescription insertNewObjectForEntityForName:@"Thingy" inManagedObjectContext:context];
_myThingo = [NSEntityDescription insertNewObjectForEntityForName:@"Thingo" inManagedObjectContext:context];
}
return self;
}
@end
次に、ユニットテストで次のようなことができます。
- (void)setUp {
Class entityDescrClass = [NSEntityDescription class];
Method originalMethod = class_getClassMethod(entityDescrClass, @selector(insertNewObjectForEntityForName:inManagedObjectContext:));
Method newMethod = class_getClassMethod([FakeEntityDescription class], @selector(insertNewObjectForEntityForName:inManagedObjectContext:));
method_exchangeImplementations(originalMethod, newMethod);
}
...[]FakeEntityDescription insertNewObjectForEntityForName:inManagedObjectContext]
実際のNSManagedObjects(またはそれらが実装するプロトコル)の代わりにモックを返します。これらのモックの唯一の目的は、ClassUnderTestの単体テスト中に行われた呼び出しを検証することです。すべての戻り値はスタブされます(他のNSManagedObjectsを参照するゲッターを含む)。
したがって、私のテストClassUnderTest
インスタンスは単体テスト内に作成されます。
ClassUnderTest *testObject = [ClassUnderTest initWithManagedObjectContext:mockContext];
(私のスウィズルのため、コンテキストは実際にはテストで使用されませんinsertNewObjectForEntityForName:inManagedObjectContext
)
このすべてのポイント?とにかく多くのクラスでコアデータを使用するので、コンストラクターの変更を管理する負担を軽減するために使用することもできます(コンストラクターを変更するたびに、単体テストの束を含むすべてのクライアントを編集する必要があります)。Core Dataを使用していなかった場合は、異議申し立てのようなものを検討するかもしれません。