Aracılığıyla paylaş


Üretim veritabanı sisteminiz olmadan test etme

Bu sayfada, veritabanınızı bir test dublörüile değiştirerek, uygulamanın üretimde çalıştığı veritabanı sistemini içermeyen otomatik testler yazma tekniklerini açıklıyoruz. Bunu yapmak için çeşitli test dublörleri ve yaklaşım türleri vardır; farklı seçenekleri tam olarak anlamak için Test Stratejisi Seçme bölümünü kapsamlı bir şekilde okumanız önerilir. Son olarak, üretim veritabanı sisteminize karşı test etmek de mümkündür; bu,üretim veritabanı sisteminize karşı test kapsamındadır.

Bahşiş

Bu sayfada xUnit teknikleri gösterilir, ancak NUnitgibi diğer test çerçevelerinde benzer kavramlar vardır.

Depo düzeni

Üretim veritabanı sisteminizi dahil etmeden testler yazmaya karar verdiyseniz, bunu yapmak için önerilen teknik depo düzenidir; bu konuda daha fazla arka plan için bu bölümü konusuna bakın. Depo desenini uygulamanın ilk adımı, EF Core LINQ sorgularınızı ayırıp ayrı bir katmana çıkarmaktır. Bu işlem, daha sonra güdüklenebilir veya simüle edilebilir. Bloglama sistemimiz için bir depo arabirimi örneği aşağıda verilmişti:

public interface IBloggingRepository
{
    Task<Blog> GetBlogByNameAsync(string name);

    IAsyncEnumerable<Blog> GetAllBlogsAsync();

    void AddBlog(Blog blog);

    Task SaveChangesAsync();
}

... ve aşağıda üretim kullanımı için kısmi bir örnek uygulama verilmişti:

public class BloggingRepository : IBloggingRepository
{
    private readonly BloggingContext _context;

    public BloggingRepository(BloggingContext context)
        => _context = context;

    public async Task<Blog> GetBlogByNameAsync(string name)
        => await _context.Blogs.FirstOrDefaultAsync(b => b.Name == name);

    // Other code...
}

Çok fazla bir şey yok: depo yalnızca EF Core bağlamını sarmalar ve veritabanı sorgularını yürüten ve bu bağlamı güncelleştiren yöntemleri kullanıma sunar. Önemli bir nokta, GetAllBlogs yöntemimizin IQueryable<Blog>değil IAsyncEnumerable<Blog> (veya IEnumerable<Blog>) döndürdüğüdür. İkincisini döndürmek, sorgu işleçlerinin sonuç üzerinde oluşturulabilir olması ve EF Core'un sorgunun çevirisine hala dahil olması gerektiği anlamına gelir; bu durum, bir depo bulundurmanın başlangıçtaki amacını boşa çıkarır. IAsyncEnumerable<Blog>, deponun döndürdüğü verileri kolayca saptamamıza veya taklit etmemize olanak tanır.

ASP.NET Core uygulaması için, depoyu bağımlılık ekleme sisteminde hizmet olarak kaydetmek amacıyla uygulamanın ConfigureServiceskısmına aşağıdakileri ekleyerek yapmamız gerekir:

services.AddScoped<IBloggingRepository, BloggingRepository>();

Son olarak, denetleyicilerimiz EF Core bağlamı yerine depo hizmetine enjekte edilir ve üzerinde yöntemler uygular:

private readonly IBloggingRepository _repository;

public BloggingControllerWithRepository(IBloggingRepository repository)
    => _repository = repository;

[HttpGet]
public async Task<Blog> GetBlog(string name)
    => await _repository.GetBlogByNameAsync(name);

Bu noktada, uygulamanız depo düzenine göre tasarlanır: veri erişim katmanıyla tek iletişim noktası olan EF Core, artık uygulama kodu ile gerçek veritabanı sorguları arasında bir aracı işlevi gören depo katmanı üzerinden yapılır. Testler artık depoyu sahteleyerek veya en sevdiğiniz mock kütüphanesi ile sahtesini oluşturarak basitçe yazılabilir. Popüler Moq kitaplığını kullanan sahte tabanlı bir test örneği aşağıda verilmişti:

[Fact]
public async Task GetBlog()
{
    // Arrange
    var repositoryMock = new Mock<IBloggingRepository>();
    repositoryMock
        .Setup(r => r.GetBlogByNameAsync("Blog2"))
        .Returns(Task.FromResult(new Blog { Name = "Blog2", Url = "http://blog2.com" }));

    var controller = new BloggingControllerWithRepository(repositoryMock.Object);

    // Act
    var blog = await controller.GetBlog("Blog2");

    // Assert
    repositoryMock.Verify(r => r.GetBlogByNameAsync("Blog2"));
    Assert.Equal("http://blog2.com", blog.Url);
}

Örnek kodun tamamı buradagörüntülenebilir.

Bellek içi SQLite

SQLite, üretim veritabanı sisteminiz (e.g. SQL Server) yerine test paketiniz için EF Core sağlayıcısı olarak kolayca yapılandırılabilir; ayrıntılar için SQLite sağlayıcı belgelerine başvurun. Ancak, testler arasında kolay yalıtım sağladığından ve gerçek SQLite dosyalarıyla ilgilenmeyi gerektirmediğinden, test sırasında SQLite'in bellek içi veritabanı özelliğini kullanmak genellikle iyi bir fikirdir.

Bellek içi SQLite kullanmak için, düşük düzeyli bir bağlantı açıldığında yeni bir veritabanı oluşturulduğunu ve bu bağlantı kapatıldığında silindiğini anlamak önemlidir. Normal kullanımda EF Core'un DbContext, gereksiz yere uzun süre bağlantının tutulmasını önlemek için gerektiğinde veritabanı bağlantılarını (her sorgu yürütülürken) açar ve kapatır. Ancak, bellek içi SQLite ile bu durum veritabanını her seferinde sıfırlamaya neden olabilir; bu nedenle geçici bir çözüm olarak, bağlantıyı EF Core'a geçirmeden önce açar ve yalnızca test tamamlandığında kapatılmasını düzenleriz:

    public SqliteInMemoryBloggingControllerTest()
    {
        // Create and open a connection. This creates the SQLite in-memory database, which will persist until the connection is closed
        // at the end of the test (see Dispose below).
        _connection = new SqliteConnection("Filename=:memory:");
        _connection.Open();

        // These options will be used by the context instances in this test suite, including the connection opened above.
        _contextOptions = new DbContextOptionsBuilder<BloggingContext>()
            .UseSqlite(_connection)
            .Options;

        // Create the schema and seed some data
        using var context = new BloggingContext(_contextOptions);

        if (context.Database.EnsureCreated())
        {
            using var viewCommand = context.Database.GetDbConnection().CreateCommand();
            viewCommand.CommandText = @"
CREATE VIEW AllResources AS
SELECT Url
FROM Blogs;";
            viewCommand.ExecuteNonQuery();
        }

        context.AddRange(
            new Blog { Name = "Blog1", Url = "http://blog1.com" },
            new Blog { Name = "Blog2", Url = "http://blog2.com" });
        context.SaveChanges();
    }

    BloggingContext CreateContext() => new BloggingContext(_contextOptions);

    public void Dispose() => _connection.Dispose();

Testler artık oluşturucuda ayarladığımız bağlantıyı kullanarak bir bağlam döndüren CreateContextçağırabilir ve çekirdek verileri içeren temiz bir veritabanımız olmasını sağlar.

Bellek içi SQLite testi için tam örnek kod buradagörüntülenebilir.

Bellek içi sağlayıcı

teste genel bakış sayfasında açıklandığı gibi, test için bellek içi sağlayıcıyı kullanmak kesinlikle önerilmez; yerine SQLite kullanmayı veyadepo desenini uygulamayı göz önünde bulundurun. Bellek içi kullanmaya karar verdiyseniz, her test öncesinde yeni bir bellek içi veritabanı ayarlayıp bu veritabanının tohumlarını oluşturan tipik bir test sınıfı oluşturucu aşağıda verilmiştir:

public InMemoryBloggingControllerTest()
{
    _contextOptions = new DbContextOptionsBuilder<BloggingContext>()
        .UseInMemoryDatabase("BloggingControllerTest")
        .ConfigureWarnings(b => b.Ignore(InMemoryEventId.TransactionIgnoredWarning))
        .Options;

    using var context = new BloggingContext(_contextOptions);

    context.Database.EnsureDeleted();
    context.Database.EnsureCreated();

    context.AddRange(
        new Blog { Name = "Blog1", Url = "http://blog1.com" },
        new Blog { Name = "Blog2", Url = "http://blog2.com" });

    context.SaveChanges();
}

Bellek içi test için tam örnek kod buradagörüntülenebilir.

Bellek içi veritabanı adlandırma

Bellek içi veritabanları basit bir dize adıyla tanımlanır ve aynı adı sağlayarak aynı veritabanına birkaç kez bağlanmak mümkündür (bu nedenle yukarıdaki örnekte her test öncesinde EnsureDeleted çağrılmalıdır). Ancak, bellek içi veritabanlarının köklerinin bağlamın iç hizmet sağlayıcısında olduğuna dikkat edin; çoğu durumda bağlamlar aynı hizmet sağlayıcısını paylaşırken, bağlamları farklı seçeneklerle yapılandırmak yeni bir iç hizmet sağlayıcısının kullanımını tetikleyebilir. Bu durumda, bellek içi veritabanlarını paylaşması gereken tüm bağlamlar için UseInMemoryDatabase'e doğrudan aynı InMemoryDatabaseRoot örneğini gönderin (bu genellikle statik bir InMemoryDatabaseRoot alanına sahip olarak yapılır).

İşlemler

Varsayılan olarak, bir işlem başlatılırsa, işlemler desteklenmediğinden bellek içi sağlayıcının bir özel durum oluşturacağını unutmayın. Bunun yerine, EF Core'u yukarıdaki örnekte olduğu gibi InMemoryEventId.TransactionIgnoredWarning'ı yoksayacak şekilde yapılandırarak işlemlerin sessizce göz ardı edilmesini isteyebilirsiniz. Ancak kodunuz işlemsel semantiğe güveniyorsa (örneğin, geri alma işleminin gerçekten değişiklikleri geri almasına bağlıysa) testiniz çalışmaz.

Görüşler

Bellek içi sağlayıcı, ToInMemoryQuerykullanarak LINQ sorguları aracılığıyla görünümlerin tanımlanmasına izin verir:

modelBuilder.Entity<UrlResource>()
    .ToInMemoryQuery(() => context.Blogs.Select(b => new UrlResource { Url = b.Url }));