2011-09-02 9 views
6

के साथ यूनिट टेस्ट ईएफ रिपोजिटरी पैटर्न मैंने अपने आवेदन में यूनिट परीक्षण लिखना शुरू करने का निर्णय लिया। यह एक भंडार पैटर्न के साथ इकाई फ्रेमवर्क का उपयोग करता है।मोक

अब मैं रिपॉजिटरीज़ का उपयोग कर रहे तर्क वर्गों का परीक्षण शुरू करना चाहता हूं। मैं यहां एक साधारण उदाहरण प्रदान करता हूं।

public class GenericRepository : IRepository 
{ 
    public IQueryable<TEntity> GetQuery<TEntity>() where TEntity : class 
    { 
     var entityName = GetEntityName<TEntity>(); 
     return Context.CreateQuery<TEntity>(entityName); 
    } 
    private string GetEntityName<TEntity>() where TEntity : class 
    { 
     return typeof(TEntity).Name; 
    } 
    public IEnumerable<TEntity> Find<TEntity>(Expression<Func<TEntity, bool>> predicate) where TEntity : class 
    { 
     return GetQuery<TEntity>().Where(predicate).AsEnumerable(); 
    } 
} 

एक साधारण तर्क अवरोही क्रम में एक कैलेंडर तालिका से अलग वर्षों के लौटने वर्ग (हाँ मैं शब्द कैलेंडर हमारे कोड में गलत लिखा गया है पता है):

वर्ग GenericRepository में मेरी विधियों में से तीन

[TestFixture] 
public class GetDistinctYearsFromCalendarTest 
{ 
    [Test] 
    public void ReturnsDistinctDatesInCorrectOrder() 
    { 
     var repositoryMock = new Mock<IRepository>(); 

     repositoryMock.Setup(r => r.Find<Calender_Tbl>(c => c.Year.HasValue)).Returns(new List<Calender_Tbl> 
     { 
      new Calender_Tbl 
       { 
        Date = 
         new DateTime(2010, 1, 1), 
        Year = 2010 
       }, 
      new Calender_Tbl 
       { 
        Date = 
         new DateTime(2010, 2, 1), 
        Year = 2010 
       }, 
      new Calender_Tbl 
       { 
        Date = 
         new DateTime(2011, 1, 1), 
        Year = 2011 
       } 
     }.AsQueryable()); 

     var getDistinct = new GetDistinctYearsFromCalendar(repositoryMock.Object).Get(); 

     Assert.AreEqual(2, getDistinct.Count(), "Returns more years than distinct."); 
     Assert.AreEqual(2011, getDistinct[0], "Incorrect order, latest years not first."); 
     Assert.AreEqual(2010, getDistinct[1], "Wrong year."); 


    } 
} 

यह:

public class GetDistinctYearsFromCalendar 
{ 
    private readonly IRepository _repository; 

    public GetDistinctYearsFromCalendar() 
    { 
     _repository = new GenericRepository(); 
    } 

    internal GetDistinctYearsFromCalendar(IRepository repository) 
    { 
     _repository = repository; 
    } 

    public int[] Get() 
    { 
     return _repository.Find<Calender_Tbl>(c => c.Year.HasValue).Select(c => c.Year.Value).Distinct().OrderBy(c => c).Reverse().ToArray(); 
    } 
} 

और यहाँ मेरा पहला परीक्षण है ठीक काम कर रहा है लेकिन यह वास्तव में मैं नहीं करना चाहता हूं। चूंकि मुझे विधि को सेटअप करना है मॉक ऑब्जेक्ट पर खोजें मुझे यह भी पता होना चाहिए कि यह मेरे तर्क वर्ग में कैसे कॉल किया जा रहा है। अगर मैं टीडीडी करना चाहता हूं तो मैं इस बारे में बात नहीं करना चाहता हूं। मैं बस इतना जानना चाहता हूं कि कौन सा कैलेंडर संस्थाएं मेरे भंडार प्रदान करनी चाहिए। मैं GetQuery विधि सेटअप करना चाहता हूँ। इस तरह:

repositoryMock.Setup(r => r.GetQuery<Calender_Tbl>()).Returns(new List<Calender_Tbl> 
{ 
    new Calender_Tbl 
     { 
      Date = 
       new DateTime(2010, 1, 1), 
      Year = 2010 
     }, 
    new Calender_Tbl 
     { 
      Date = 
       new DateTime(2010, 2, 1), 
      Year = 2010 
     }, 
    new Calender_Tbl 
     { 
      Date = 
       new DateTime(2011, 1, 1), 
      Year = 2011 
     } 
}.AsQueryable()); 

तो जब ढूँढें GenericRepository वर्ग में आंतरिक रूप से GetQuery बुला रहा है यह सही कैलेंडर संस्थाओं है कि मैं GetQuery में सेटअप मिलना चाहिए। लेकिन यह निश्चित रूप से काम नहीं कर रहा है। चूंकि मैंने अपने नकली ऑब्जेक्ट की खोज विधि सेट नहीं की है, इसलिए मुझे कोई भी संस्था नहीं मिलती है।

तो क्या करना है? निस्संदेह मैं शायद मोल्स या कुछ अन्य ढांचे का उपयोग कर सकता हूं जो सबकुछ जोड़ता है लेकिन मैं ऐसा नहीं करना चाहता हूं। क्या इस मुद्दे को हल करने के लिए कक्षा या परीक्षण के डिजाइन में कुछ भी कर सकता हूं?

अगर मुझे अपने वर्तमान समाधान के साथ जाना है, तो यह दुनिया का अंत नहीं है, लेकिन क्या होगा यदि संपत्ति वर्ष शून्य नहीं हो पाता है? तो निश्चित रूप से मुझे तर्क वर्ग में अपना कार्यान्वयन बदलना होगा, लेकिन मुझे परीक्षा भी बदलनी होगी। मैं इससे बचने की कोशिश करना चाहता हूं।

public class MockRepository : IRepository 
{ 
    private List<object> entities; 
    public MockRepository(params object[] entitites) 
    { 
     this.entities = entities.ToList(); 
    } 

    public IQueryable<TEntity> GetQuery<TEntity>() where TEntity : class 
    { 
     return this.entities.OfType<TEntity>().AsQueryable(); 
    } 

    public IEnumerable<TEntity> Find<TEntity>(Expression<Func<TEntity, bool>> predicate) where TEntity : class 
    { 
     return GetQuery<TEntity>().Where(predicate).AsEnumerable(); 
    } 
} 

कि सबसे आसान है और मेरे पसंदीदा तरीका:

+1

मुझे लगता है कि ऐसा करने का एक तरीका है 'GetDistinctYearsFromCalendar' 'IRepository' पर निर्भर है। इस तरह आप 'जेनेरिक रिपोजिटरी' को 'GetDistinctYearsFromCalendar' को अलग-अलग परीक्षण कर सकते हैं। अधिक जानकारी के लिए http://en.wikipedia.org/wiki/Dependency_injection देखें। – Michael

+0

ठीक है, मुझे आंतरिक सीटीआर नहीं मिला। क्या आपके पास एक ही प्रोजेक्ट में परीक्षण और उत्पादन कोड है? मुझे लगता है कि ऐसा करने का सही तरीका डिफ़ॉल्ट सीटीओ के माध्यम से सभी तरह से निर्भरता इंजेक्शन कर रहा है। – Michael

+0

मुझे एहसास है कि मैं सिर्फ अपना तर्क लिख सकता हूं: वापसी _repository.GetQuery ()। जहां (c => c.Year.HasValue)। चयन करें (c => c.Year.Value)। डिस्टिंट()। ऑर्डरबी (सी => सी)। रैवरसे()। ToArray(); नहीं, मैं किसी अन्य प्रोजेक्ट में परीक्षण करता हूं लेकिन असेंबली निर्देशों के साथ परीक्षण परियोजना में आंतरिक रूप से खुलासा करता हूं। लेकिन क्या होगा यदि मैं जेनेरिक रिपोजिटरी से किसी अन्य प्रकार का कभी भी उपयोग नहीं करता? क्या निर्भरता इंजेक्शन को हर तरह से करना आवश्यक है? यह सबसे अच्छा समाधान सही लगता है? मैं अंधेरा था और भंडार की सभी कार्यक्षमताओं का उपयोग करना चाहता था। – John

उत्तर

12

मैं दो तरह से देख सकते हैं। Moq सब कुछ के लिए हथौड़ा नहीं है;)

वैकल्पिक रूप से, यदि आप वास्तव में मोक का उपयोग करने पर जोर देते हैं (मैं flattered हूँ, लेकिन यह इस मामले में बहुत अनावश्यक है, क्योंकि आप लौटाई गई संस्थाओं पर राज्य आधारित परीक्षण कर सकते हैं) , आप कर सकते हैं:

public class GenericRepository : IRepository 
{ 
    public virtual IQueryable<TEntity> GetQuery<TEntity>() where TEntity : class 
    { 
     var entityName = GetEntityName<TEntity>(); 
     return Context.CreateQuery<TEntity>(entityName); 
    } 
    private string GetEntityName<TEntity>() where TEntity : class 
    { 
     return typeof(TEntity).Name; 
    } 
    public IEnumerable<TEntity> Find<TEntity>(Expression<Func<TEntity, bool>> predicate) where TEntity : class 
    { 
     return GetQuery<TEntity>().Where(predicate).AsEnumerable(); 
    } 
} 

और फिर GetQuery के व्यवहार को ओवरराइड करने के लिए Moq का उपयोग करें:

var repository = new Mock<GenericRepository> { CallBase = true }; 

repository.Setup(x => x.GetQuery<Foo>()).Returns(theFoos.AsQueryable()); 

क्या होगा कि ढूँढें विधि, GenericRepository वर्ग पर निष्पादित किया जाएगा जो बारी में होगा है GetQuery, जो Moq द्वारा provi करने के लिए अधिलेखित किया गया है संस्थाओं के निश्चित सेट डी।

मैंने कॉलबेज = सच स्पष्ट रूप से सेट किया है यदि आप वर्चुअल भी खोजते हैं, ताकि हम सुनिश्चित कर सकें कि इसे हमेशा कॉल किया जाता है। यदि खोज आभासी नहीं है, तो तकनीकी रूप से आवश्यक नहीं है, क्योंकि यह हमेशा वास्तविक वर्ग पर लागू किया जाएगा, नकली विरासत/मजाक कर रहा है।

मैं पहले विकल्प के लिए जाना चाहता हूं, यह समझने के लिए बहुत आसान है कि क्या हो रहा है, और इसे एक विशेष परीक्षण के संदर्भ के बाहर पुन: उपयोग किया जा सकता है (बस आपको आवश्यक किसी भी संस्था को पास करें और यह सब कुछ के लिए काम करेगा)।

+0

चूंकि मुझे समझ में आया कि IQueryable लौटने की विधि को मॉक करने योग्य नहीं था क्योंकि आपको डेटाबेस के साथ एकीकरण परीक्षण चलाने की ज़रूरत है, मुझे आश्चर्य है कि विधि को नकल करना संभव है और यह मेरी संस्थाओं को वापस लौटाए, इससे कोई फर्क नहीं पड़ता कि मैं क्या तर्क देता हूं? मैंने इसके साथ प्रयास किया। यह किसी भी <> लेकिन यह काम नहीं किया। – John

0

हाल ही में, ईएफ 6+ के लिए प्रयास नामक एक नया टूल आया है जो मुझे नकली डीबी के खिलाफ यूनिट परीक्षण के लिए बेहद उपयोगी पाया गया है। http://effort.codeplex.com/wikipage?title=Tutorials&referringTitle=Home देखें।

इस पैकेज प्रबंधक कंसोल आदेश का उपयोग करके इसे जोड़ें:

PM> Install-Package Effort.EF6 

फिर अपने DbContext के लिए एक इंटरफेस जोड़ने के लिए, मान लीजिए, यदि आप AdventureWorks डेटाबेस का उपयोग कर रहे हैं (https://sql2012kitdb.codeplex.com/ देखें):

तब अद्यतन अपने DbContext दो नए पैरामिट्रीकृत कंस्ट्रक्टर्स जोड़ने के लिए:

/// 
    /// Create a new context based on database name or connection string. 
    /// 
    /// Database name or connection string 
    public AdventureWorksEntities(string nameOrConnectionString) 
     : base(nameOrConnectionString) 
    { 
     this.Configuration.LazyLoadingEnabled = false; 
    } 

    public AdventureWorksEntities(DbConnection connection) 
     : base(connection, true) 
    { 
     this.Configuration.LazyLoadingEnabled = false; 
    } 

एक निर्माता है कि अपने भंडार के लिए इंटरफ़ेस लेता जोड़ें:

private IAdventureWorksDbContext _dbContext; 

    public ProductRepository(IAdventureWorksDbContext dbContext) 
    { 
     dbContext.Configuration.AutoDetectChangesEnabled = false; 
     this._dbContext = dbContext; 
    } 

फिर अपने इकाई परीक्षण परियोजना के लिए एक इंटरफेस जोड़ सकते हैं और जुड़े वर्ग:

public class ProductsTestData 
{ 
    public static void AddTestData(IAdventureWorksDbContext dbContext) 
    { 
     dbContext.Products.Add(new Product() { Id = new Guid("23ab9e4e-138a-4223-bb42-1dd176d8583cB"), Name = "Product A", CreatedDate = DateTime.Now, Description = "Product description..." }); 
     dbContext.Products.Add(new Product() { Id = new Guid("97e1835f-4c1b-4b87-a514-4a17c019df00"), Name = "Product B", CreatedDate = DateTime.Now }); 
     dbContext.SaveChanges(); 
    } 
} 

अब सेटअप अपने इकाई परीक्षण वर्ग:

public interface ITestDatabase : IDisposable 
{ 
    IAdventureWorksDbContext CreateContext(); 

    void Dispose(IAdventureWorksDbContext context); 
} 

अपने इकाई परीक्षण परियोजना के लिए कुछ नकली डेटा जोड़ें :

[TestClass] 
public class ProductsTest 
{ 
    private ITestDatabase _testDatabaseStrategy; 
    private ProductRepository _productRepository; 
    private IAdventureWorksDbContext _context; 

    [TestInitialize] 
    public void SetupTest() 
    { 
     // create the test strategy. This will initialise a new database 
     _testDatabaseStrategy = CreateTestStrategy(); 

     // add test data to the database instance 
     using (_context = _testDatabaseStrategy.CreateContext()) 
     { 
      ProductsTestData.AddTestData(_context); 
      _context.SaveChanges(); 
     } 

     // initialise the repository we are testing 
     _context = _testDatabaseStrategy.CreateContext(); 
     _productRepository = new ProductRepository(_context); 
    } 

    protected ITestDatabase CreateTestStrategy() 
    { 
     return new EffortDatabaseStrategy(); 
    } 

    [TestCleanup] 
    public void CleanupTest() 
    { 
     // dispose of the database and connection 
     _testDatabaseStrategy.Dispose(_context); 
     _context = null; 
    } 

    [TestMethod] 
    public void GetProductsByTagName() 
    { 
     IEnumerable<Product> products = _productRepository.GetProductsByTagName("Tag 1", false); 
     Assert.AreEqual(1, products.Count()); 
    } 

Wh पहले EffortDatabaseStrategy है:

public class EffortDatabaseStrategy : ITestDatabase 
{ 
    public EffortDatabaseStrategy() 
    { 
    } 

    private DbConnection _connection; 

    public IAdventureWorksDbContext CreateContext() 
    { 
     if (_connection == null) 
     { 
      _connection = Effort.DbConnectionFactory.CreateTransient(); 
     } 
     var context = new AdventureWorksDbContext(_connection); 

     return context; 
    } 

    public void Dispose(IAdventureWorksDbContext context) 
    { 
     if (context != null) 
     { 
      context.Dispose(); 
     } 
    } 

    public void Dispose() 
    { 
    } 
} 

पूरी जानकारी के लिए, कृपया http://www.codeproject.com/Articles/460175/Two-strategies-for-testing-Entity-Framework-Effort?msg=5122027#xx5122027xx देखते हैं।