30

現在、MVC4 アプリケーションでのリポジトリ実装の単体テストを作成しています。データ コンテキストをモックするために、この投稿からいくつかのアイデアを採用することから始めましたが、適切にモックすることさえ可能かどうか疑問に思ういくつかの制限を発見しましたIQueryable

特に、テストはパスしても本番環境でコードが失敗する状況をいくつか見てきましたが、この失敗の原因となる動作を模倣する方法を見つけることができませんでした。

たとえば、次のスニペットはPost、定義済みのカテゴリ リストに含まれるエンティティを選択するために使用されます。

var posts = repository.GetEntities<Post>(); // Returns IQueryable<Post>
var categories = GetCategoriesInGroup("Post"); // Returns a fixed list of type Category
var filtered = posts.Where(p => categories.Any(c => c.Name == p.Category)).ToList();

私のテスト環境では、上記postsの偽の実装を使用してモックを作成し、インスタンスの を作成して拡張メソッドを使用するように変換することも試みました。これらのアプローチは両方ともテスト条件下で機能しますが、コードは実際には本番環境で失敗します。ただし、次の例外があります。DbSetListPostIQueryableAsQueryable()

System.NotSupportedException : Unable to create a constant value of type 'Category'. Only primitive types or enumeration types are supported in this context.

このような LINQ の問題は簡単に修正できますが、実際の課題は、テスト環境で明らかにならないことを考えると、問題を見つけることです。

Entity Frameworkの実装の動作を模倣できると期待するのは非現実的IQueryableですか?

あなたのアイデアをありがとう、

ティム。

4

2 に答える 2

67

Entity Framework の動作を模倣することは、不可能であるとしても非常に難しいと思います。何よりもまず、linq-to-entites と linq-to-objects が異なるすべての特殊性とエッジ ケースの深い知識が必要になるためです。あなたが言うように、本当の課題はそれらを見つけることです。ほぼすべてを網羅しているわけではありませんが、主な 3 つの領域を指摘しておきます。

Linq-to-Objects が成功し、Linq-to-Entities が失敗するケース:

  • .Select(x => x.Property1.ToString(). LINQ to Entities はメソッド 'System.String ToString()' メソッドを認識しません...これは、ネイティブ .Net クラスのほぼすべてのメソッドと、もちろん独自のメソッドに適用されます。少数の .Net メソッドのみが SQL に変換されます。CLR メソッドから正規関数へのマッピング を参照してください。ちなみにEF 6.1以降ToStringはサポートされています。ただし、パラメーターなしのオーバーロードのみ。
  • Skip()先立たずにOrderBy
  • ExceptおよびIntersect: をスローする巨大なクエリを生成できます SQL ステートメントの一部が深くネストされています。クエリを書き直すか、小さなクエリに分割します。
  • Select(x => x.Date1 - x.Date2): DbArithmeticExpression 引数には、共通の数値型が必要です。
  • (あなたの場合).Where(p => p.Category == category)このコンテキストでは、プリミティブ型または列挙型のみがサポートされています。
  • Nodes.Where(n => n.ParentNodes.First().Id == 1):メソッド 'First' は、最終クエリ操作としてのみ使用できます。
  • context.Nodes.Last(): LINQ to Entities はメソッド '...Last...' を認識しません。これは、他の多くのIQueryable拡張メソッドに適用されます。サポートされている LINQ メソッドとサポートされていない LINQ メソッドを参照してください。
  • (以下の Slauma のコメントを参照してください): .Select(x => new A { Property1 = (x.BoolProperty ? new B { BProp1 = x.Prop1, BProp2 = x.Prop2 } : new B { BProp1 = x.Prop1 }) }):タイプ 'B' は、単一の LINQ to Entities クエリ内の 2 つの構造的に互換性のない初期化に表示されます... from here .
  • context.Entities.Cast<IEntity>():タイプ 'Entity' をタイプ 'IEntity' にキャストできません。LINQ to Entities は、EDM プリミティブ型または列挙型のキャストのみをサポートします。
  • .Select(p => p.Category?.Name). 式で null 伝播を使用すると、CS8072 がスローされます。式ツリー ラムダには、null 伝播演算子が含まれていない可能性があります。 これはいつか修正されるかもしれません
  • この質問: Select、Where、および GroupBy のこの組み合わせで例外が発生するのはなぜですか? EF でサポートされていないクエリ構造全体さえあるという事実に気付きましたが、L2O ではそれらに問題はありません。

Linq-to-Objects が失敗し、Linq-to-Entities が成功するケース:

  • .Select(p => p.Category.Name): null の場合p.CategoryL2E は null を返しますが、L2O はObject reference not set to an object のインスタンスをスローします。これは、null 伝播を使用して修正することはできません (上記を参照)。
  • Nodes.Max(n => n.ParentId.Value)のいくつかの null 値を使用しn.ParentIdます。L2E は最大値を返し、L2O はNullable オブジェクトをスローし、値が必要です。
  • EntityFunctions( DbFunctionsEF 6 以降) またはを使用しSqlFunctionsます。

成功/失敗の両方が異なるが動作が異なるケース:

  • Nodes.Include("ParentNodes"): L2O には include の実装がありません。実行されてノードが返されますが ( の場合)、親ノードNodesはありません。IQueryable
  • Nodes.Select(n => n.ParentNodes.Max(p => p.Id))いくつかの空のParentNodesコレクション: どちらも失敗しますが、異なる例外があります。
  • Nodes.Where(n => n.Name.Contains("par")): L2O は大文字と小文字を区別します。L2E はデータベースの照合に依存します (多くの場合、大文字と小文字は区別されません)。
  • node.ParentNode = parentNode: 双方向の関係の場合、L2E では、親のノード コレクションにもノードが追加されます (関係 fixup )。L2Oにはありません。(双方向の EF 関係の単体テストを参照してください)。
  • null 伝播が失敗した場合の回避策: .Select(p => p.Category == null ? string.Empty : p.Category.Name): 結果は同じですが、生成された SQL クエリにも null チェックが含まれているため、最適化が難しくなる可能性があります。
  • Nodes.AsNoTracking().Select(n => n.ParentNode. これは非常にトリッキーです!. EF を使用すると、各 に対して新しいオブジェクトが作成 れるため、重複する可能性があります。エンティティ状態マネージャーとエンティティ キーが関与するようになったため、EF を使用しないと既存の が再利用されます。L2O で呼び出すことはできますが、何もしないので、あってもなくても違いはありません。AsNoTrackingParentNodeNode AsNoTrackingParentNodesAsNoTracking()

また、遅延/一括読み込みのモック化と、遅延読み込みの例外に対するコンテキスト ライフ サイクルの影響についてはどうでしょうか。または、一部のクエリ構造がパフォーマンスに与える影響 (N+1 SQL クエリをトリガーする構造など)。または、エンティティ キーの重複または欠落による例外はありますか? それとも関係修復?

私の意見:誰もそれを偽造するつもりはありません。最も憂慮すべき領域は、L2O が成功し、L2E が失敗する場所です。グリーンユニットテストの価値は何ですか?EF は統合テスト (例:ここ)でのみ確実にテストできると以前に言われましたが、私は同意する傾向があります。

ただし、これは、データ層として EF を使用するプロジェクトでの単体テストを忘れる必要があるという意味ではありません。それを行う方法はありますが、統合テストなしではないと思います。

于 2012-11-12T22:40:46.570 に答える