Aracılığıyla paylaş


Kendi test çiftlerinizle test etme

Dekont

Yalnızca EF6'ya Doğru - Bu sayfada ele alınan özellikler, API'ler vb. Entity Framework 6'da sunulmuştur. Önceki bir sürümü kullanıyorsanız, bilgilerin bir kısmı veya tümü geçerli değildir.

Uygulamanız için testler yazarken veritabanına çarpmamak genellikle tercih edilir. Entity Framework, bellek içi verileri kullanan bir bağlam (testleriniz tarafından tanımlanan davranışla) oluşturarak bunu başarmanızı sağlar.

Test çiftleri oluşturma seçenekleri

Bağlamınızın bellek içi sürümünü oluşturmak için kullanılabilecek iki farklı yaklaşım vardır.

  • Kendi test çiftlerinizi oluşturma – Bu yaklaşım, bağlamınız ve DbSet'leriniz için kendi bellek içi uygulamanızı yazmayı içerir. Bu, sınıfların davranışı üzerinde çok fazla denetim sağlar, ancak makul miktarda kod yazmayı ve bu koda sahip olmayı içerebilir.
  • Test çiftleri oluşturmak için sahte çerçeve kullanın: Sahte çerçeve (Moq gibi) kullanarak bağlam ve kümelerinin bellek içi uygulamalarını çalışma zamanında sizin için dinamik olarak oluşturabilirsiniz.

Bu makalede kendi test çiftinizi oluşturma konusu ele alınacaktır. Sahte çerçeve kullanma hakkında bilgi için bkz . Sahte Çerçeve ile test etme.

EF6 öncesi sürümlerle test etme

Bu makalede gösterilen kod EF6 ile uyumludur. EF5 ve önceki sürümle test etmek için bkz . Fake Context ile test etme.

EF bellek içi test çiftlerinin sınırlamaları

Bellek içi test çiftleri, uygulamanızın EF kullanan bitlerinin birim test düzeyi kapsamını sağlamak için iyi bir yol olabilir. Ancak, bunu yaparken bellek içi verilere karşı sorgu yürütmek için LINQ to Objects kullanıyorsunuz. Bu, sorguları veritabanınızda çalıştırılacak SQL'e çevirmek için EF'in LINQ sağlayıcısını (LINQ to Entities) kullanmaktan farklı davranışlara neden olabilir.

Bu tür bir farkın bir örneği, ilgili verileri yüklemektir. Her birinin ilgili Gönderileri olan bir dizi Blog oluşturursanız, bellek içi verileri kullanırken ilgili Gönderiler her blog için her zaman yüklenir. Ancak, bir veritabanında çalışırken veriler yalnızca Include yöntemini kullanırsanız yüklenir.

Bu nedenle, uygulamanızın bir veritabanında düzgün çalıştığından emin olmak için her zaman bir düzey uçtan uca test (birim testlerinize ek olarak) eklemeniz önerilir.

Bu makaleyle birlikte takip

Bu makalede, dilerseniz takip etmek üzere Visual Studio'ya kopyalayabileceğiniz eksiksiz kod listeleri sağlanır. Birim Testi Projesi oluşturmak en kolayıdır ve zaman uyumsuz kullanan bölümleri tamamlamak için .NET Framework 4.5'i hedeflemeniz gerekir.

Bağlam arabirimi oluşturma

EF modelini kullanan bir hizmetin test edilmesine göz atacağız. EF bağlamımızı test için bellek içi bir sürümle değiştirebilmek için EF bağlamımızın (ve bellek içi çift) uygulayacağı bir arabirim tanımlayacağız.

Test edeceğimiz hizmet, bağlamımızın DbSet özelliklerini kullanarak verileri sorgular ve değiştirir ve ayrıca değişiklikleri veritabanına göndermek için SaveChanges'i çağırır. Bu nedenle bu üyeleri arabirime dahil ediyoruz.

using System.Data.Entity;

namespace TestingDemo
{
    public interface IBloggingContext
    {
        DbSet<Blog> Blogs { get; }
        DbSet<Post> Posts { get; }
        int SaveChanges();
    }
}

EF modeli

Test edeceğimiz hizmet, BloggingContext ve Blog ve Post sınıflarından oluşan bir EF modelini kullanır. Bu kod EF Tasarım Aracı tarafından oluşturulmuş olabilir veya bir Code First modeli olabilir.

using System.Collections.Generic;
using System.Data.Entity;

namespace TestingDemo
{
    public class BloggingContext : DbContext, IBloggingContext
    {
        public DbSet<Blog> Blogs { get; set; }
        public DbSet<Post> Posts { get; set; }
    }

    public class Blog
    {
        public int BlogId { get; set; }
        public string Name { get; set; }
        public string Url { get; set; }

        public virtual List<Post> Posts { get; set; }
    }

    public class Post
    {
        public int PostId { get; set; }
        public string Title { get; set; }
        public string Content { get; set; }

        public int BlogId { get; set; }
        public virtual Blog Blog { get; set; }
    }
}

EF Tasarım Aracı ile bağlam arabirimini uygulama

Bağlamımızın IBloggingContext arabirimini uyguladığını unutmayın.

Code First kullanıyorsanız arabirimi uygulamak için bağlamınızı doğrudan düzenleyebilirsiniz. EF Tasarım Aracı kullanıyorsanız bağlamınızı oluşturan T4 şablonunu düzenlemeniz gerekir. <edmx dosyanız altında iç içe yerleştirilmiş model_name.Context.tt> dosyasını açın, aşağıdaki kod parçasını bulun ve gösterildiği gibi arabirimine ekleyin.

<#=Accessibility.ForType(container)#> partial class <#=code.Escape(container)#> : DbContext, IBloggingContext

Test edilecek hizmet

Bellek içi test çiftleri ile testi göstermek için BlogService için birkaç test yazacağız. Hizmet, yeni bloglar (AddBlog) oluşturup ada göre sıralanmış tüm Blogları döndürebilir (GetAllBlogs). GetAllBlogs'a ek olarak, tüm blogları ada göre zaman uyumsuz olarak sipariş edecek bir yöntem de sağladık (GetAllBlogsAsync).

using System.Collections.Generic;
using System.Data.Entity;
using System.Linq;
using System.Threading.Tasks;

namespace TestingDemo
{
    public class BlogService
    {
        private IBloggingContext _context;

        public BlogService(IBloggingContext context)
        {
            _context = context;
        }

        public Blog AddBlog(string name, string url)
        {
            var blog = new Blog { Name = name, Url = url };
            _context.Blogs.Add(blog);
            _context.SaveChanges();

            return blog;
        }

        public List<Blog> GetAllBlogs()
        {
            var query = from b in _context.Blogs
                        orderby b.Name
                        select b;

            return query.ToList();
        }

        public async Task<List<Blog>> GetAllBlogsAsync()
        {
            var query = from b in _context.Blogs
                        orderby b.Name
                        select b;

            return await query.ToListAsync();
        }
    }
}

Bellek içi test oluşturma çiftleri

Artık gerçek EF modeline ve bunu kullanabilecek hizmete sahip olduğumuza göre, test için kullanabileceğimiz bellek içi test çiftini oluşturmanın zamanı geldi. Bağlamımız için bir TestContext test çifti oluşturduk. Test çiftlerinde, çalıştıracağımız testleri desteklemek için istediğimiz davranışı seçebiliriz. Bu örnekte SaveChanges'in çağrılma sayısını yakalamaya çalışıyoruz, ancak test ettiğiniz senaryoyu doğrulamak için gereken mantığı ekleyebilirsiniz.

DbSet'in bellek içi uygulamasını sağlayan bir TestDbSet de oluşturduk. DbSet'te (Bul hariç) tüm yöntemler için eksiksiz bir uygulama sağladık ancak yalnızca test senaryonuzun kullanacağı üyeleri uygulamanız gerekiyor.

TestDbSet, zaman uyumsuz sorguların işlenebilmesini sağlamak için dahil ettiğimiz diğer bazı altyapı sınıflarını kullanır.

using System;
using System.Collections.Generic;
using System.Collections.ObjectModel;
using System.Data.Entity;
using System.Data.Entity.Infrastructure;
using System.Linq;
using System.Linq.Expressions;
using System.Threading;
using System.Threading.Tasks;

namespace TestingDemo
{
    public class TestContext : IBloggingContext
    {
        public TestContext()
        {
            this.Blogs = new TestDbSet<Blog>();
            this.Posts = new TestDbSet<Post>();
        }

        public DbSet<Blog> Blogs { get; set; }
        public DbSet<Post> Posts { get; set; }
        public int SaveChangesCount { get; private set; }
        public int SaveChanges()
        {
            this.SaveChangesCount++;
            return 1;
        }
    }

    public class TestDbSet<TEntity> : DbSet<TEntity>, IQueryable, IEnumerable<TEntity>, IDbAsyncEnumerable<TEntity>
        where TEntity : class
    {
        ObservableCollection<TEntity> _data;
        IQueryable _query;

        public TestDbSet()
        {
            _data = new ObservableCollection<TEntity>();
            _query = _data.AsQueryable();
        }

        public override TEntity Add(TEntity item)
        {
            _data.Add(item);
            return item;
        }

        public override TEntity Remove(TEntity item)
        {
            _data.Remove(item);
            return item;
        }

        public override TEntity Attach(TEntity item)
        {
            _data.Add(item);
            return item;
        }

        public override TEntity Create()
        {
            return Activator.CreateInstance<TEntity>();
        }

        public override TDerivedEntity Create<TDerivedEntity>()
        {
            return Activator.CreateInstance<TDerivedEntity>();
        }

        public override ObservableCollection<TEntity> Local
        {
            get { return _data; }
        }

        Type IQueryable.ElementType
        {
            get { return _query.ElementType; }
        }

        Expression IQueryable.Expression
        {
            get { return _query.Expression; }
        }

        IQueryProvider IQueryable.Provider
        {
            get { return new TestDbAsyncQueryProvider<TEntity>(_query.Provider); }
        }

        System.Collections.IEnumerator System.Collections.IEnumerable.GetEnumerator()
        {
            return _data.GetEnumerator();
        }

        IEnumerator<TEntity> IEnumerable<TEntity>.GetEnumerator()
        {
            return _data.GetEnumerator();
        }

        IDbAsyncEnumerator<TEntity> IDbAsyncEnumerable<TEntity>.GetAsyncEnumerator()
        {
            return new TestDbAsyncEnumerator<TEntity>(_data.GetEnumerator());
        }
    }

    internal class TestDbAsyncQueryProvider<TEntity> : IDbAsyncQueryProvider
    {
        private readonly IQueryProvider _inner;

        internal TestDbAsyncQueryProvider(IQueryProvider inner)
        {
            _inner = inner;
        }

        public IQueryable CreateQuery(Expression expression)
        {
            return new TestDbAsyncEnumerable<TEntity>(expression);
        }

        public IQueryable<TElement> CreateQuery<TElement>(Expression expression)
        {
            return new TestDbAsyncEnumerable<TElement>(expression);
        }

        public object Execute(Expression expression)
        {
            return _inner.Execute(expression);
        }

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

        public Task<object> ExecuteAsync(Expression expression, CancellationToken cancellationToken)
        {
            return Task.FromResult(Execute(expression));
        }

        public Task<TResult> ExecuteAsync<TResult>(Expression expression, CancellationToken cancellationToken)
        {
            return Task.FromResult(Execute<TResult>(expression));
        }
    }

    internal class TestDbAsyncEnumerable<T> : EnumerableQuery<T>, IDbAsyncEnumerable<T>, IQueryable<T>
    {
        public TestDbAsyncEnumerable(IEnumerable<T> enumerable)
            : base(enumerable)
        { }

        public TestDbAsyncEnumerable(Expression expression)
            : base(expression)
        { }

        public IDbAsyncEnumerator<T> GetAsyncEnumerator()
        {
            return new TestDbAsyncEnumerator<T>(this.AsEnumerable().GetEnumerator());
        }

        IDbAsyncEnumerator IDbAsyncEnumerable.GetAsyncEnumerator()
        {
            return GetAsyncEnumerator();
        }

        IQueryProvider IQueryable.Provider
        {
            get { return new TestDbAsyncQueryProvider<T>(this); }
        }
    }

    internal class TestDbAsyncEnumerator<T> : IDbAsyncEnumerator<T>
    {
        private readonly IEnumerator<T> _inner;

        public TestDbAsyncEnumerator(IEnumerator<T> inner)
        {
            _inner = inner;
        }

        public void Dispose()
        {
            _inner.Dispose();
        }

        public Task<bool> MoveNextAsync(CancellationToken cancellationToken)
        {
            return Task.FromResult(_inner.MoveNext());
        }

        public T Current
        {
            get { return _inner.Current; }
        }

        object IDbAsyncEnumerator.Current
        {
            get { return Current; }
        }
    }
}

Bul Uygulama

Find yönteminin genel bir şekilde uygulanması zordur. Bul yöntemini kullanan kodu test etmeniz gerekiyorsa, bulmayı desteklemesi gereken varlık türlerinin her biri için bir test DbSet oluşturmak en kolay yöntemdir. Daha sonra, aşağıda gösterildiği gibi belirli bir varlık türünü bulmak için mantık yazabilirsiniz.

using System.Linq;

namespace TestingDemo
{
    class TestBlogDbSet : TestDbSet<Blog>
    {
        public override Blog Find(params object[] keyValues)
        {
            var id = (int)keyValues.Single();
            return this.SingleOrDefault(b => b.BlogId == id);
        }
    }
}

Bazı testler yazma

Teste başlamak için yapmamız gereken tek şey bu. Aşağıdaki test bir TestContext ve ardından bu bağlamı temel alan bir hizmet oluşturur. Hizmet daha sonra AddBlog yöntemi kullanılarak yeni bir blog oluşturmak için kullanılır. Son olarak test, hizmetin bağlamın Bloglar özelliğine yeni bir Blog eklediğini ve bağlam üzerinde SaveChanges olarak adlandırıldığını doğrular.

Bu yalnızca bellek içi test çifti ile test yapabileceğiniz öğelerin türlerine bir örnektir ve test çiftlerinin mantığını ve doğrulamayı gereksinimlerinizi karşılayacak şekilde ayarlayabilirsiniz.

using Microsoft.VisualStudio.TestTools.UnitTesting;
using System.Linq;

namespace TestingDemo
{
    [TestClass]
    public class NonQueryTests
    {
        [TestMethod]
        public void CreateBlog_saves_a_blog_via_context()
        {
            var context = new TestContext();

            var service = new BlogService(context);
            service.AddBlog("ADO.NET Blog", "http://blogs.msdn.com/adonet");

            Assert.AreEqual(1, context.Blogs.Count());
            Assert.AreEqual("ADO.NET Blog", context.Blogs.Single().Name);
            Assert.AreEqual("http://blogs.msdn.com/adonet", context.Blogs.Single().Url);
            Assert.AreEqual(1, context.SaveChangesCount);
        }
    }
}

Burada başka bir test örneği verilmiştir. Bu kez sorgu gerçekleştiren bir testtir. Test, Blog özelliğinde bazı verilerle bir test bağlamı oluşturarak başlar. Verilerin alfabetik sırada olmadığını unutmayın. Ardından test bağlamımızı temel alan bir BlogService oluşturabilir ve GetAllBlogs'tan aldığımız verilerin ada göre sıralandığından emin olabilirsiniz.

using Microsoft.VisualStudio.TestTools.UnitTesting;

namespace TestingDemo
{
    [TestClass]
    public class QueryTests
    {
        [TestMethod]
        public void GetAllBlogs_orders_by_name()
        {
            var context = new TestContext();
            context.Blogs.Add(new Blog { Name = "BBB" });
            context.Blogs.Add(new Blog { Name = "ZZZ" });
            context.Blogs.Add(new Blog { Name = "AAA" });

            var service = new BlogService(context);
            var blogs = service.GetAllBlogs();

            Assert.AreEqual(3, blogs.Count);
            Assert.AreEqual("AAA", blogs[0].Name);
            Assert.AreEqual("BBB", blogs[1].Name);
            Assert.AreEqual("ZZZ", blogs[2].Name);
        }
    }
}

Son olarak, TestDbSet'e dahil ettiğimiz zaman uyumsuz altyapının çalıştığından emin olmak için zaman uyumsuz yöntemimizi kullanan bir test daha yazacağız.

using Microsoft.VisualStudio.TestTools.UnitTesting;
using System.Collections.Generic;
using System.Linq;
using System.Threading.Tasks;

namespace TestingDemo
{
    [TestClass]
    public class AsyncQueryTests
    {
        [TestMethod]
        public async Task GetAllBlogsAsync_orders_by_name()
        {
            var context = new TestContext();
            context.Blogs.Add(new Blog { Name = "BBB" });
            context.Blogs.Add(new Blog { Name = "ZZZ" });
            context.Blogs.Add(new Blog { Name = "AAA" });

            var service = new BlogService(context);
            var blogs = await service.GetAllBlogsAsync();

            Assert.AreEqual(3, blogs.Count);
            Assert.AreEqual("AAA", blogs[0].Name);
            Assert.AreEqual("BBB", blogs[1].Name);
            Assert.AreEqual("ZZZ", blogs[2].Name);
        }
    }
}