2012-03-06 17 views
29

मैं System.Data.Objects.EntityFunctions.TruncateTime विधि का उपयोग कर रहा हूँ मेरी क्वेरी में एक datetime की तारीख हिस्सा पाने के लिए:EntityFunctions.TruncateTime और इकाई परीक्षण

if (searchOptions.Date.HasValue) 
    query = query.Where(c => 
     EntityFunctions.TruncateTime(c.Date) == searchOptions.Date); 

इस विधि (मेरा मानना ​​है कि एक ही अन्य EntityFunctions तरीकों पर लागू होता है) के बाहर निष्पादित नहीं किया जा सकता है LINQ से इकाइयों के लिए। एक इकाई परीक्षण है, जो प्रभावी रूप से वस्तुओं के लिए LINQ है में इस कोड को निष्पादित, का कारण बनता है एक NotSupportedException फेंक दिया करने के लिए:

System.NotSupportedException: यह फ़ंक्शन केवल संस्थाओं को LINQ से लागू किया जा सकता है।

मैं अपने परीक्षणों में नकली DbSets के साथ एक भंडार के लिए एक स्टब का उपयोग कर रहा हूं।

तो मुझे अपनी क्वेरी का परीक्षण कैसे करना चाहिए?

+0

मैं अपने उत्तर हटा दिया गया है, यह आपके लिए उपयोगी नहीं था। किसी तरह मुझे संदेह था कि मैं आपको कुछ नया नहीं बता रहा था। मुझे पता नहीं है कि यूनिट टेस्ट में अपनी क्वेरी से कैसे निपटना है, जब तक कि आपके यूनिट टेस्ट में एलटीओ-फ्रेंडली ('c => c.Date.Date == ...') लागू करने वाले इंटरफ़ेस के पीछे पूरी क्वेरी डाली न जाए। । – Slauma

+0

क्या आप कृपया अपना उत्तर मिटा सकते हैं? मुझे लगता है कि यह काफी मान्य है और दूसरों की मदद कर सकता है ... –

+0

विधि केवल एक जगह धारक है। जब लिंक से एंटिटी ट्रांसलेटर अभिव्यक्ति वृक्ष को संसाधित करता है यदि यह इस विधि को देखता है तो यह जानता है कि इसे डेटाबेस विशिष्ट निर्माण के साथ कैसे बदला जाए। इसलिए विधि में कोई कार्यान्वयन नहीं है लेकिन NotSupportedException फेंकता है। – Pawel

उत्तर

18

आप नहीं कर सकते - अगर इकाई परीक्षण का मतलब है कि आप स्मृति में नकली भंडार का उपयोग कर रहे हैं और इसलिए आप LINQ से ऑब्जेक्ट्स का उपयोग कर रहे हैं। यदि आप LINQ से ऑब्जेक्ट्स के साथ अपने प्रश्नों का परीक्षण करते हैं तो आपने अपने आवेदन का परीक्षण नहीं किया बल्कि केवल आपके नकली भंडार का परीक्षण किया।

आपका अपवाद कम खतरनाक मामला है क्योंकि यह इंगित करता है कि आपके पास लाल परीक्षण है, लेकिन संभवतः वास्तव में एक कार्यरत अनुप्रयोग है।

अधिक खतरनाक मामला दूसरी तरफ है: एक हरा परीक्षण होने पर एक क्रैशिंग एप्लिकेशन या प्रश्न जो आपके परीक्षण के समान परिणाम नहीं लौटाते हैं। जैसे प्रश्नों ...

context.MyEntities.Where(e => MyBoolFunction(e)).ToList() 

या

context.MyEntities.Select(e => new MyEntity { Name = e.Name }).ToList() 

... अपने परीक्षण में ठीक काम करेगा, लेकिन नहीं LINQ के साथ अपने आवेदन में संस्थाओं के लिए।

एक क्वेरी की तरह ...

context.MyEntities.Where(e => e.Name == "abc").ToList() 

... अलग-अलग परिणामों LINQ के साथ वस्तुओं के लिए LINQ से संस्थाओं को वापस आ जाएगी।

आप केवल एकीकरण परीक्षण के निर्माण से अपने प्रश्न में क्वेरी का परीक्षण कर सकते हैं जो आपके आवेदन के LINQ से Entities प्रदाता और वास्तविक डेटाबेस का उपयोग कर रहे हैं।

संपादित

आप अभी भी इकाई परीक्षण लिखने के लिए चाहते हैं, तो मुझे लगता है कि आप नकली चाहिए स्वयं क्वेरी या क्वेरी में कम से कम भाव पर। मैं कल्पना कर सकता है कि निम्नलिखित कोड की तर्ज पर कुछ काम कर सकते हैं:

Where अभिव्यक्ति के लिए एक इंटरफेस बनाएँ:

public interface IEntityExpressions 
{ 
    Expression<Func<MyEntity, bool>> GetSearchByDateExpression(DateTime date); 
    // maybe more expressions which use EntityFunctions or SqlFunctions 
} 

आपके आवेदन के लिए एक कार्यान्वयन बनाएं ...

public class EntityExpressions : IEntityExpressions 
{ 
    public Expression<Func<MyEntity, bool>> 
     GetSearchByDateExpression(DateTime date) 
    { 
     return e => EntityFunctions.TruncateTime(e.Date) == date; 
     // Expression for LINQ to Entities, does not work with LINQ to Objects 
    } 
} 

...और अपने यूनिट परीक्षण परियोजना में एक दूसरे कार्यान्वयन:

public class MyClass 
{ 
    private readonly IEntityExpressions _entityExpressions; 

    public MyClass() 
    { 
     _entityExpressions = new EntityExpressions(); // "poor man's IOC" 
    } 

    public MyClass(IEntityExpressions entityExpressions) 
    { 
     _entityExpressions = entityExpressions; 
    } 

    // just an example, I don't know how exactly the context of your query is 
    public IQueryable<MyEntity> BuildQuery(IQueryable<MyEntity> query, 
     SearchOptions searchOptions) 
    { 
     if (searchOptions.Date.HasValue) 
      query = query.Where(_entityExpressions.GetSearchByDateExpression(
       searchOptions.Date)); 
     return query; 
    } 
} 

उपयोग पहले (डिफ़ॉल्ट):

public class FakeEntityExpressions : IEntityExpressions 
{ 
    public Expression<Func<MyEntity, bool>> 
     GetSearchByDateExpression(DateTime date) 
    { 
     return e => e.Date.Date == date; 
     // Expression for LINQ to Objects, does not work with LINQ to Entities 
    } 
} 

अपने वर्ग में जहां क्वेरी का उपयोग कर रहे हैं इस इंटरफेस के लिए एक निजी सदस्य और दो कंस्ट्रक्टर्स बनाने अपने आवेदन में निर्माता:

var myClass = new MyClass(); 
var searchOptions = new SearchOptions { Date = DateTime.Now.Date }; 

var query = myClass.BuildQuery(context.MyEntities, searchOptions); 

var result = query.ToList(); // this is LINQ to Entities, queries database 

अपने इकाई परीक्षण में FakeEntityExpressions के साथ दूसरे निर्माता का उपयोग करें:

IEntityExpressions entityExpressions = new FakeEntityExpressions(); 
var myClass = new MyClass(entityExpressions); 
var searchOptions = new SearchOptions { Date = DateTime.Now.Date }; 
var fakeList = new List<MyEntity> { new MyEntity { ... }, ... }; 

var query = myClass.BuildQuery(fakeList.AsQueryable(), searchOptions); 

var result = query.ToList(); // this is LINQ to Objects, queries in memory 

यदि आप निर्भरता इंजेक्शन कंटेनर का उपयोग कर रहे हैं तो आप IEntityExpressions को कन्स्ट्रक्टर में उपयुक्त कार्यान्वयन इंजेक्शन करके इसका लाभ उठा सकते हैं और डिफ़ॉल्ट कन्स्ट्रक्टर की आवश्यकता नहीं है।

मैंने ऊपर दिए गए उदाहरण कोड का परीक्षण किया है और यह काम करता है।

+3

मैं एल 2 ओ और एल 2 ई के बीच अंतर को समझता हूं - मुझे पता है कि मेरे परीक्षण SQL सर्वर के व्यवहार को पूरी तरह से दोहराने नहीं कर रहे हैं, लेकिन मैं अभी भी कई सेवाओं का परीक्षण कर सकता हूं। मैं झूठी सकारात्मक नतीजे की संभावना से काफी खुश हूं - अगर ऐसा होता है तो मैं परीक्षण को सुदृढ़ कर सकता हूं। उनमें से 99% काम करने का लाभ जोखिम से बाहर निकलता है। –

+1

संपादन के लिए धन्यवाद। मुझे चिंता थी कि किसी को ऐसे हैक की जरूरत है ;-) यह एक दयालु ईएफ आधा बेक्ड है ... –

15

आप एक नया स्थिर समारोह को परिभाषित कर सकते हैं (आप इसे एक विस्तार पद्धति के रूप में हो सकता है अगर आप चाहते हैं):

[EdmFunction("Edm", "TruncateTime")] 
    public static DateTime? TruncateTime(DateTime? date) 
    { 
     return date.HasValue ? date.Value.Date : (DateTime?)null; 
    } 

तो फिर तुम तो उस समारोह LINQ में संस्थाओं और LINQ करने के लिए वस्तुओं के लिए उपयोग कर सकते हैं और कहीं भी होगी काम। हालांकि, उस विधि का अर्थ है कि आपको अपनी नई कक्षा में कॉल के साथ EntityFunctions पर कॉल को प्रतिस्थापित करना होगा।

एक और, बेहतर (लेकिन अधिक शामिल) विकल्प एक अभिव्यक्ति विज़िटर का उपयोग करना होगा, और इन-मेमोरी कार्यान्वयन के लिए कॉल के साथ EntityFunctions पर कॉल को प्रतिस्थापित करने के लिए अपनी इन-मेमोरी डीबीसेट्स के लिए कस्टम प्रदाता लिखना होगा।

+0

यह मेरे मामले के लिए काम करता था और अब तक का सबसे आसान समाधान था! धन्यवाद। –

+0

प्रारंभिक जटिल समस्या का सबसे अच्छा समाधान। – Anish

+0

एक विस्तार विधि के रूप में भी बेहतर है। सार्वजनिक स्थिर दिनांक समय? TruncateTime (यह डेटटाइम? दिनांक) फिर उपयोग करें; myDate.TruncateTime() – tkerwood

3

How to Unit Test GetNewValues() which contains EntityFunctions.AddDays function को my answer में दिखाया गया है, आप अपने खुद के साथ EntityFunctions कार्यों के लिए कॉल को बदलने के लिए एक प्रश्न अभिव्यक्ति आगंतुक उपयोग कर सकते हैं, LINQ करने के लिए संगत कार्यान्वयन ऑब्जेक्ट्स।

कार्यान्वयन दिखाई देगा:

using System; 
using System.Data.Objects; 
using System.Linq; 
using System.Linq.Expressions; 

static class EntityFunctionsFake 
{ 
    public static DateTime? TruncateTime(DateTime? original) 
    { 
     if (!original.HasValue) return null; 
     return original.Value.Date; 
    } 
} 
public class EntityFunctionsFakerVisitor : ExpressionVisitor 
{ 
    protected override Expression VisitMethodCall(MethodCallExpression node) 
    { 
     if (node.Method.DeclaringType == typeof(EntityFunctions)) 
     { 
      var visitedArguments = Visit(node.Arguments).ToArray(); 
      return Expression.Call(typeof(EntityFunctionsFake), node.Method.Name, node.Method.GetGenericArguments(), visitedArguments); 
     } 

     return base.VisitMethodCall(node); 
    } 
} 
class VisitedQueryProvider<TVisitor> : IQueryProvider 
    where TVisitor : ExpressionVisitor, new() 
{ 
    private readonly IQueryProvider _underlyingQueryProvider; 
    public VisitedQueryProvider(IQueryProvider underlyingQueryProvider) 
    { 
     if (underlyingQueryProvider == null) throw new ArgumentNullException(); 
     _underlyingQueryProvider = underlyingQueryProvider; 
    } 

    private static Expression Visit(Expression expression) 
    { 
     return new TVisitor().Visit(expression); 
    } 

    public IQueryable<TElement> CreateQuery<TElement>(Expression expression) 
    { 
     return new VisitedQueryable<TElement, TVisitor>(_underlyingQueryProvider.CreateQuery<TElement>(Visit(expression))); 
    } 

    public IQueryable CreateQuery(Expression expression) 
    { 
     var sourceQueryable = _underlyingQueryProvider.CreateQuery(Visit(expression)); 
     var visitedQueryableType = typeof(VisitedQueryable<,>).MakeGenericType(
      sourceQueryable.ElementType, 
      typeof(TVisitor) 
      ); 

     return (IQueryable)Activator.CreateInstance(visitedQueryableType, sourceQueryable); 
    } 

    public TResult Execute<TResult>(Expression expression) 
    { 
     return _underlyingQueryProvider.Execute<TResult>(Visit(expression)); 
    } 

    public object Execute(Expression expression) 
    { 
     return _underlyingQueryProvider.Execute(Visit(expression)); 
    } 
} 
public class VisitedQueryable<T, TExpressionVisitor> : IQueryable<T> 
    where TExpressionVisitor : ExpressionVisitor, new() 
{ 
    private readonly IQueryable<T> _underlyingQuery; 
    private readonly VisitedQueryProvider<TExpressionVisitor> _queryProviderWrapper; 
    public VisitedQueryable(IQueryable<T> underlyingQuery) 
    { 
     _underlyingQuery = underlyingQuery; 
     _queryProviderWrapper = new VisitedQueryProvider<TExpressionVisitor>(underlyingQuery.Provider); 
    } 

    public IEnumerator<T> GetEnumerator() 
    { 
     return _underlyingQuery.GetEnumerator(); 
    } 

    IEnumerator IEnumerable.GetEnumerator() 
    { 
     return GetEnumerator(); 
    } 

    public Expression Expression 
    { 
     get { return _underlyingQuery.Expression; } 
    } 

    public Type ElementType 
    { 
     get { return _underlyingQuery.ElementType; } 
    } 

    public IQueryProvider Provider 
    { 
     get { return _queryProviderWrapper; } 
    } 
} 

और यहाँ TruncateTime के साथ एक उपयोग नमूना है:

var linq2ObjectsSource = new List<DateTime?>() { null }.AsQueryable(); 
var visitedSource = new VisitedQueryable<DateTime?, EntityFunctionsFakerVisitor>(linq2ObjectsSource); 
// If you do not use a lambda expression on the following line, 
// The LINQ To Objects implementation is used. I have not found a way around it. 
var visitedQuery = visitedSource.Select(dt => EntityFunctions.TruncateTime(dt)); 
var results = visitedQuery.ToList(); 
Assert.AreEqual(1, results.Count); 
Assert.AreEqual(null, results[0]); 
2

हालांकि मैं इस सवाल का जवाब EntityExpressions वर्ग का उपयोग कर Smaula द्वारा दिए गए पसंद है, मैं यह करता है लगता है बहुत ज्यादा। असल में, यह विधि पर पूरी इकाई फेंकता है, तुलना करता है, और एक बूल देता है।

मेरे मामले में, मुझे इस इकाई की आवश्यकता है। एक समूह करने के लिए ट्रंककेटटाइम(), इसलिए मेरे पास तुलना करने की कोई तिथि नहीं थी, या वापस लौटने के लिए, मैं बस तारीख भाग प्राप्त करने के लिए सही कार्यान्वयन प्राप्त करना चाहता था। तो मैं ने लिखा है:

private static Expression<Func<DateTime?>> GetSupportedDatepartMethod(DateTime date, bool isLinqToEntities) 
    { 
     if (isLinqToEntities) 
     { 
      // Normal context 
      return() => EntityFunctions.TruncateTime(date); 
     } 
     else 
     { 
      // Test context 
      return() => date.Date; 
     } 
    } 

मेरे मामले में, मैं दो अलग कार्यान्वयन के साथ इंटरफेस जरूरत नहीं थी, लेकिन वह सिर्फ एक ही काम करना चाहिए।

मैं इसे साझा करना चाहता था, क्योंकि यह सबसे छोटी चीज संभव है। यह केवल तारीख भाग प्राप्त करने के लिए सही विधि का चयन करता है।

1

मुझे एहसास है कि यह एक पुराना धागा है लेकिन वैसे भी जवाब पोस्ट करना चाहता था।

निम्नलिखित समाधान Shims

का उपयोग कर मुझे यकीन है कि नहीं कर रहा हूँ किया जाता है क्या संस्करणों (2013, 2012, 2010) और भी जायके (व्यक्त करते हैं, समर्थक, प्रीमियम, परम) दृश्य स्टूडियो के संयोजन आप shims, तो उपयोग करने की अनुमति यह हो सकता है कि यह हर किसी के लिए उपलब्ध नहीं है।

यहाँ कोड ओपी तैनात

// some method that returns some testable result 
public object ExecuteSomething(SearchOptions searchOptions) 
{ 
    // some other preceding code 

    if (searchOptions.Date.HasValue) 
     query = query.Where(c => 
      EntityFunctions.TruncateTime(c.Date) == searchOptions.Date); 

    // some other stuff and then return some result 
} 

है निम्नलिखित कुछ इकाई परीक्षण परियोजना और कुछ इकाई परीक्षण फ़ाइल में स्थित होगा। यहां यूनिट टेस्ट है जो शिम्स का उपयोग करेगा।

// Here is the test method 
public void ExecuteSomethingTest() 
{ 
    // arrange 
    var myClassInstance = new SomeClass(); 
    var searchOptions = new SearchOptions(); 

    using (ShimsContext.Create()) 
    { 
     System.Data.Objects.Fakes.ShimEntityFunctions.TruncateTimeNullableOfDateTime = (dtToTruncate) 
      => dtToTruncate.HasValue ? (DateTime?)dtToTruncate.Value.Date : null; 

     // act 
     var result = myClassInstance.ExecuteSomething(searchOptions); 
     // assert 
     Assert.AreEqual(something,result); 
    } 
} 

मेरा मानना ​​है कि यह शायद कोड है कि NotSupportedException पैदा करने के बिना EntityFunctions का उपयोग करता है परीक्षण करने के लिए साफ और सबसे गैर दखल तरीका है।

+0

शिम्स को विजुअल स्टूडियो अल्टीमेट संस्करण रेफरी की आवश्यकता है: https: //msdn.microsoft.com/en-us/library/hh549176.aspx – Rama

+1

@DRAM शिम जांचने के बाद वीएस 2013 के प्रीमियम संस्करण में उपलब्ध है (https://msdn.microsoft.com/en-us/library/hh549175.aspx), मैं यही उपयोग करता हूं और मैंने शिम्स का उपयोग किया है।विजुअल स्टूडियो 2015 में उनका उपयोग एंटरप्राइज़ संस्करण (प्रीमियम और परम के लिए किया जाता था) में किया जा सकता है, मुझे संदेह है कि वे समर्थक या सामुदायिक संस्करणों में उपलब्ध हैं लेकिन सुनिश्चित नहीं हैं। – Igor

0

आप निम्न तरीके से जांच कर सकते हैं:

var dayStart = searchOptions.Date.Date; 
var dayEnd = searchOptions.Date.Date.AddDays(1); 

if (searchOptions.Date.HasValue) 
    query = query.Where(c => 
     c.Date >= dayStart && 
     c.Date < dayEnd); 
संबंधित मुद्दे