Not
Bu sayfaya erişim yetkilendirme gerektiriyor. Oturum açmayı veya dizinleri değiştirmeyi deneyebilirsiniz.
Bu sayfaya erişim yetkilendirme gerektiriyor. Dizinleri değiştirmeyi deneyebilirsiniz.
Uyarı
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 bazıları veya tümü geçerli değildir.
Uygulamanız için testler yazarken veritabanına erişmekten kaçınmak genellikle istenir. 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ınızın ve kümelerinizin bellek içi uygulamalarını sizin için çalışma zamanında dinamik olarak oluşturabilirsiniz.
Bu makalede bir mock kütüphanesi kullanımı incelenecektir. Kendi test çiftlerinizi oluşturmak için bkz . Kendi Test Çiftlerinizle Test Etme.
EF'yi bir mock çerçevesiyle kullanmayı göstermek için Moq kullanacağız. Moq almanın en kolay yolu , NuGet'ten Moq paketini yüklemektir.
EF6 öncesi sürümlerle test etme
Bu makalede gösterilen senaryo, EF6'da DbSet'te yaptığımız bazı değişikliklere bağlıdır. EF5 ve önceki sürümlerle test etmek için Fake Context ile test etme başlığına bakın.
EF bellek içi test çiftlerinin sınırlamaları
Bellek içi test çiftleri, uygulamanızın EF kullanan parçalarının birim test düzeyi kapsamını sunmak 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 makaleyi takip ederek
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.
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 Designer tarafından oluşturulmuş veya Code First modeli olabilir.
using System.Collections.Generic;
using System.Data.Entity;
namespace TestingDemo
{
public class BloggingContext : DbContext
{
public virtual DbSet<Blog> Blogs { get; set; }
public virtual 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 Designer ile Sanal DbSet özellikleri
Bağlamdaki DbSet özelliklerinin sanal olarak işaretlendiğini unutmayın. Bu, sahteleme çerçevesinin bağlamımızdan türemesine ve bu özellikleri sahte bir uygulamayla geçersiz kılmasına olanak sağlar.
Code First kullanıyorsanız sınıflarınızı doğrudan düzenleyebilirsiniz. EF Designer 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 sanal anahtar sözcüğü ekleyin.
public string DbSet(EntitySet entitySet)
{
return string.Format(
CultureInfo.InvariantCulture,
"{0} virtual DbSet\<{1}> {2} {{ get; set; }}",
Accessibility.ForReadOnlyProperty(entitySet),
_typeMapper.GetTypeName(entitySet.ElementType),
_code.Escape(entitySet));
}
Test edilecek hizmet
Bellek içi test çiftlerini kullanarak testi göstermek amacıyla 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ı isimlerine göre eşzamansız olarak sıralayacak 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 BloggingContext _context;
public BlogService(BloggingContext context)
{
_context = context;
}
public Blog AddBlog(string name, string url)
{
var blog = _context.Blogs.Add(new Blog { Name = name, Url = url });
_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();
}
}
}
Sorgu dışı senaryoları test etme
Sorgu dışı yöntemleri test etmeye başlamak için yapmamız gereken tek şey bu. Aşağıdaki test, bağlam oluşturmak için Moq kullanır. Ardından bir DbSet<Blog> oluşturur ve bağlamın Bloglar özelliğinden döndürülmek üzere entegre eder. Daha sonra bağlam kullanılarak yeni bir BlogService oluşturulur, bu da AddBlog yöntemi kullanılarak yeni bir blog oluşturmak için kullanılır. Son olarak test, hizmetin bağlam üzerinde SaveChanges adlı yeni bir Blog eklediğini doğrular.
using Microsoft.VisualStudio.TestTools.UnitTesting;
using Moq;
using System.Data.Entity;
namespace TestingDemo
{
[TestClass]
public class NonQueryTests
{
[TestMethod]
public void CreateBlog_saves_a_blog_via_context()
{
var mockSet = new Mock<DbSet<Blog>>();
var mockContext = new Mock<BloggingContext>();
mockContext.Setup(m => m.Blogs).Returns(mockSet.Object);
var service = new BlogService(mockContext.Object);
service.AddBlog("ADO.NET Blog", "http://blogs.msdn.com/adonet");
mockSet.Verify(m => m.Add(It.IsAny<Blog>()), Times.Once());
mockContext.Verify(m => m.SaveChanges(), Times.Once());
}
}
}
Sorgu senaryolarını test etme
DbSet test çiftimizde sorgu yürütebilmek için IQueryable uygulamasını ayarlamamız gerekir. İlk adım bazı bellek içi veriler oluşturmaktır. Liste<Blogu> kullanıyoruz. Ardından, bir bağlam ve bir DbSet<Blog> oluşturacağız ve DbSet için IQueryable implementasyonu bağlayacağız – bunlar yalnızca List<T> ile çalışan LINQ to Objects sağlayıcısına yetki devrediyor.
Ardından test çiftlerini 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;
using Moq;
using System.Collections.Generic;
using System.Data.Entity;
using System.Linq;
namespace TestingDemo
{
[TestClass]
public class QueryTests
{
[TestMethod]
public void GetAllBlogs_orders_by_name()
{
var data = new List<Blog>
{
new Blog { Name = "BBB" },
new Blog { Name = "ZZZ" },
new Blog { Name = "AAA" },
}.AsQueryable();
var mockSet = new Mock<DbSet<Blog>>();
mockSet.As<IQueryable<Blog>>().Setup(m => m.Provider).Returns(data.Provider);
mockSet.As<IQueryable<Blog>>().Setup(m => m.Expression).Returns(data.Expression);
mockSet.As<IQueryable<Blog>>().Setup(m => m.ElementType).Returns(data.ElementType);
mockSet.As<IQueryable<Blog>>().Setup(m => m.GetEnumerator()).Returns(() => data.GetEnumerator());
var mockContext = new Mock<BloggingContext>();
mockContext.Setup(c => c.Blogs).Returns(mockSet.Object);
var service = new BlogService(mockContext.Object);
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);
}
}
}
Zaman uyumsuz sorgularla deneme
Entity Framework 6, bir sorguyu zaman uyumsuz olarak yürütmek için kullanılabilecek bir uzantı yöntemleri kümesi kullanıma sunulmuştur. Bu yöntemlere örnek olarak ToListAsync, FirstAsync, ForEachAsync vb. verilebilir.
Entity Framework sorguları LINQ kullandığından, uzantı yöntemleri IQueryable ve IEnumerable üzerinde tanımlanır. Ancak, bunlar yalnızca Entity Framework ile kullanılacak şekilde tasarlandığından, bunları Entity Framework sorgusu olmayan bir LINQ sorgusunda kullanmayı denerseniz aşağıdaki hatayı alabilirsiniz:
Kaynak IQueryable, IDbAsyncEnumerable{0} uygulamaz. Entity Framework zaman uyumsuz işlemleri için yalnızca IDbAsyncEnumerable uygulayan kaynaklar kullanılabilir. Diğer ayrıntılar için bkz: http://go.microsoft.com/fwlink/?LinkId=287068.
Zaman uyumsuz yöntemler yalnızca EF sorgusunda çalıştırılırken destekleniyor olsa da, dbSet'in bellek içi test çiftinde çalışırken bunları birim testinizde kullanmak isteyebilirsiniz.
Zaman uyumsuz yöntemleri kullanabilmek için zaman uyumsuz sorguyu işlemek amacıyla bellekte bir DbAsyncQueryProvider oluşturmamız gerekir. Moq kullanarak bir sorgu sağlayıcısı ayarlamak mümkün olsa da kodda test çift uygulaması oluşturmak çok daha kolaydır. Bu uygulamanın kodu aşağıdaki gibidir:
using System.Collections.Generic;
using System.Data.Entity.Infrastructure;
using System.Linq;
using System.Linq.Expressions;
using System.Threading;
using System.Threading.Tasks;
namespace TestingDemo
{
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; }
}
}
}
Artık asenkron bir sorgu sağlayıcısına sahip olduğumuza göre yeni GetAllBlogsAsync yöntemimiz için birim testi yazabiliriz.
using Microsoft.VisualStudio.TestTools.UnitTesting;
using Moq;
using System.Collections.Generic;
using System.Data.Entity;
using System.Data.Entity.Infrastructure;
using System.Linq;
using System.Threading.Tasks;
namespace TestingDemo
{
[TestClass]
public class AsyncQueryTests
{
[TestMethod]
public async Task GetAllBlogsAsync_orders_by_name()
{
var data = new List<Blog>
{
new Blog { Name = "BBB" },
new Blog { Name = "ZZZ" },
new Blog { Name = "AAA" },
}.AsQueryable();
var mockSet = new Mock<DbSet<Blog>>();
mockSet.As<IDbAsyncEnumerable<Blog>>()
.Setup(m => m.GetAsyncEnumerator())
.Returns(new TestDbAsyncEnumerator<Blog>(data.GetEnumerator()));
mockSet.As<IQueryable<Blog>>()
.Setup(m => m.Provider)
.Returns(new TestDbAsyncQueryProvider<Blog>(data.Provider));
mockSet.As<IQueryable<Blog>>().Setup(m => m.Expression).Returns(data.Expression);
mockSet.As<IQueryable<Blog>>().Setup(m => m.ElementType).Returns(data.ElementType);
mockSet.As<IQueryable<Blog>>().Setup(m => m.GetEnumerator()).Returns(() => data.GetEnumerator());
var mockContext = new Mock<BloggingContext>();
mockContext.Setup(c => c.Blogs).Returns(mockSet.Object);
var service = new BlogService(mockContext.Object);
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);
}
}
}