5

findByAttribute追加したインスタンスメソッドをNSManagedObjectどのようにテストする必要がありますか?

最初は、XcodeのCore Data Utilityチュートリアルで示されているように、プログラムで独立したCoreDataスタックを作成することを考えました。そして、そのドキュメントを検索したところ、 Core Data Fetch Request Templatesに出くわし、作成したメソッドを作成する代わりに、fetch request templateを作成する必要があると思いましたがentityName、フェッチを使用して変数を変更できるようには見えません。テンプレートをリクエストできますか?NSManagedObjectすべてのサブクラスがそれを使用できるように、フェッチ要求テンプレートを作成できますか?うーん、でもそれでも必要entityNameで、メソッドを呼び出したサブクラスの名前を動的に取得する方法はないと思います。

とにかく、良い解決策は、本番のCore Dataスタックから独立して、テスト用のメモリ内CoreDataスタックを作成することであるように見えます。@Jeff Schillingは、メモリ内の永続ストアを作成することもお勧めします。Chris Hansonは、CoreDataを単体テストするための永続的なストアコーディネーターも作成します。これは、Railsがテスト用に別個のデータベースを持っている方法と似ているようです。ただし、@ iamleegは、コアデータの依存関係を削除することをお勧めします。

どちらがより良いアプローチだと思いますか?私は個人的に後者を好みます。

更新:私はOCHamcrestとPivotalLabのCedarを使用してコアデータを単体テストしています。以下のコードを書くことに加えて、私はターゲットにNSManagedObject+Additions.mとを追加しました。User.mSpec

#define HC_SHORTHAND
#import <Cedar-iPhone/SpecHelper.h>
#import <OCHamcrestIOS/OCHamcrestIOS.h>

#import "NSManagedObject+Additions.h"
#import "User.h"

SPEC_BEGIN(NSManagedObjectAdditionsSpec)

describe(@"NSManagedObject+Additions", ^{
    __block NSManagedObjectContext *managedObjectContext;   

    beforeEach(^{
        NSManagedObjectModel *managedObjectModel =
                [NSManagedObjectModel mergedModelFromBundles:nil];

        NSPersistentStoreCoordinator *persistentStoreCoordinator =
                [[NSPersistentStoreCoordinator alloc]
                 initWithManagedObjectModel:managedObjectModel];

        [persistentStoreCoordinator addPersistentStoreWithType:NSInMemoryStoreType
                                                 configuration:nil URL:nil options:nil error:NULL];

        managedObjectContext = [[NSManagedObjectContext alloc] init];
        managedObjectContext.persistentStoreCoordinator = persistentStoreCoordinator;

        [persistentStoreCoordinator release];
    });

    it(@"finds first object by attribute value", ^{

        // Create a user with an arbitrary Facebook user ID.
        NSNumber *fbId = [[NSNumber alloc] initWithInteger:514417];
        [[NSEntityDescription insertNewObjectForEntityForName:@"User"
                                      inManagedObjectContext:managedObjectContext] setFbId:fbId];
        [managedObjectContext save:nil];

        NSNumber *fbIdFound = [(User *)[User findByAttribute:@"fbId" value:(id)fbId
                                                  entityName:@"User"
                                      inManagedObjectContext:managedObjectContext] fbId];

        assertThatInteger([fbId integerValue], equalToInteger([fbIdFound integerValue]));

        [fbId release];
    });

    afterEach(^{
        [managedObjectContext release];
    }); 
});

SPEC_END

あなたが私に渡された議論にキャストし(id)fbIdfindByAttributeない場合、私が得る理由を教えてくれれば

warning: incompatible Objective-C types 'struct NSNumber *',
expected 'struct NSString *' when passing argument 2 of
'findByAttribute:value:entityName:inManagedObjectContext:' from
distinct Objective-C type

その後、ボーナスポイントを獲得できます!:)引数がであると想定されている場合、私NSNumberはにをキャストする必要はないようです、そうですか?ididNSNumberid

4

1 に答える 1

4

私の個人的な哲学は、本物をテストしないテストはテストではないということです。多くの場合、特に手続き型コードでは機能しますが、Core Data オブジェクト グラフに見られるような複雑なコードでは失敗する可能性があります。

Core Data の障害点のほとんどは、不適切なデータ モデルに起因しています。たとえば、相互関係が失われているため、グラフのバランスが崩れ、オブジェクトが孤立しています。悪いグラフをテストする唯一の方法は、既知のグラフを作成し、コードをストレス テストして、グラフ内のオブジェクトを見つけて操作できるかどうかを確認することです。

このタイプのテストを実装するには、次のことを行います。

  1. ストアが常に既知の状態で開始されるように、既存の Core Data ストア ファイルを削除して、各テストの実行を開始します。
  2. できれば毎回コードで生成して、実行ごとに新しいストアを提供しますが、各実行の前にストア ファイルのコピーを交換することもできます。長期的には実際に簡単なので、前者の方法を好みます。
  3. テスト データに比較的極端な例が含まれていることを確認してください。たとえば、長い名前、文字化け文字を含む文字列、非常に大きな数字と非常に小さな数字などです。

テスト オブジェクト グラフの状態は、各テストの時点で完全に把握されている必要があります。私は定期的にテストでグラフ全体をダンプし、エンティティとライブ オブジェクトの両方を詳細にダンプする方法を用意しています。

私は通常、アプリのデータ モデル全体を別のアプリ プロジェクト セットアップで開発およびテストし、データ モデルを開発するだけです。アプリでデータ モデルが必要に応じて正確に機能するようになったら、それを完全なプロジェクトに移動し、コントローラーとインターフェイスの追加を開始します。

データ モデルは適切に実装された Model-View-Controller デザイン アプリの実際のコアであるため、データ モデルを正しく取得することで開発の %50-%75 がカバーされます。残りはケーキウォークです。

この特定のケースでは、フェッチ リクエストの述語が適切なオブジェクトを返すことだけをテストする必要があります。それをテストする唯一の方法は、完全なテスト グラフを提供することです。

(このメソッドは実際にはまったく役に立たないことに注意してください。属性によって特定のオブジェクトを返すのではなく、その値の属性を持つ任意の数のオブジェクトのいずれかを返すだけです。たとえば、次のオブジェクトグラフがある場合の属性値を持つ23,462Person個のオブジェクト、このメソッドは 23,462 個の任意の Person エンティティを正確に 1 つ返します。これの要点がわかりません。手続き型の SQL 用語で考えていると思います。オブジェクトを扱うときに混乱を招くでしょう。 - Core Data のようなグラフ マネージャー。)firstNameJohn

アップデート:

あなたのエラーは、コンパイラvalueが述語での使用を見て、それが NSString オブジェクトでなければならないと仮定したことが原因であると推測します。で使用されるような文字列形式でオブジェクトをドロップすると、返される実際の値は、オブジェクトのメソッドのpredicateWithFormat:結果を含む NSString オブジェクトです。descriptionしたがって、コンパイラにとって、述語は実際には次のようになります。

[NSPredicate predicateWithFormat:@"%K == %@", (NSString *)attribute, (NSString *)value]

value...そのため、逆方向に動作する場合、技術的にはそうすべきではありませんが、パラメーターで NSString を探します。この id の使用は、実際にはベスト プラクティスではありません。これは、任意のクラスを受け入れるためですが、インスタンスの-descriptionメソッドによって返される説明文字列が実際には常にわかっているわけではありません。

上で述べたように、ここにはいくつかの概念上の問題があります。以下のコメントであなたが言うとき:

私の意図は、ActiveRecord の find_by_ 動的ファインダーに類似したメソッドを作成することでした。

... 間違った視点から Core Data にアプローチしています。Active Record は主に SQL のオブジェクト ラッパーであり、既存の SQL サーバーと Ruby on Rails との統合を容易にします。そのため、手続き型 SQL の概念に支配されています。

これは、Core Data が使用する正反対のアプローチです。Core Data は、Model-View-Controller アプリ デザインのモデル レイヤーを作成するためのオブジェクト グラフ管理システムです。そのため、オブジェクトがすべてです。たとえば、属性を持たず、関係だけのオブジェクトを持つことさえ可能です。このようなオブジェクトは、非常に複雑な動作をすることもあります。これは、SQL やアクティブ レコードには存在しないものです。

まったく同じ属性を持つ任意の数のオブジェクトを持つことはかなり可能です。これにより、作成しようとしているメソッドが無意味で危険になります。どのオブジェクトが返されるかわからないためです。それはそれを「混沌とした」方法にします。同じ属性を持つオブジェクトが複数ある場合、このメソッドは、属性値が提供するものと一致する単一のオブジェクトを任意に返します。

特定のオブジェクトを識別したい場合は、オブジェクトをキャプチャしてからManagedObjectID、 を使用-[NSManagedObjectContext objectForID:]して取得する必要があります。オブジェクトが保存されると、そのオブジェクトManagedObjectIDは一意になります。

ただし、この機能は通常、別のストアや別のアプリのオブジェクトを参照する必要がある場合にのみ使用されます。通常、それ以外の意味はありません。Core Data を使用すると、属性だけでなく位置、つまりオブジェクト グラフ内の他のオブジェクトとの関係に基づいてオブジェクトを探します。

非常に重要なアドバイスをコピー アンド ペーストさせてください。コア データは SQL ではありません。エンティティはテーブルではありません。オブジェクトは行ではありません。列は属性ではありません。Core Data はオブジェクト グラフ管理システムであり、オブジェクト グラフを保持する場合と保持しない場合があり、バックグラウンドで SQL を使用する場合としない場合があります。Core Data を SQL 用語で考えようとすると、Core Data を完全に誤解してしまい、多くの悲しみと時間の浪費につながります。

使い慣れた API の設計を使用して新しい API をプログラミングしようとするのは自然なことですが、新しい API が古い API とは大幅に異なる設計哲学を持っている場合、それは危険な罠です。

古い API の基本的かつ基本的な機能を新しい API で記述しようとしていることに気付いた場合、それだけで、新しい API の哲学と同期していないことに注意してください。この場合、ジェネリックfindByAttributeメソッドが Core Data で有用であるのに、なぜ Apple はジェネリック メソッドを提供しなかったのかを尋ねる必要があります。Core Data の重要な概念を見落としている可能性が高くありませんか?

于 2011-03-18T15:29:10.593 に答える