Üretim veritabanı sisteminiz olmadan test yapma

Bu sayfada, veritabanınızı bir test çifti ile değiştirerek uygulamanın üretimde çalıştığı veritabanı sistemini içermeyen otomatikleştirilmiş testler yazma tekniklerini ele aacağız. Bunu yapmak için çeşitli test çiftleri ve yaklaşım türleri vardır ve farklı seçenekleri tam olarak anlamak için test stratejisi seçme'yi kapsamlı bir şekilde okumanızı öneririz. Son olarak, üretim veritabanı sisteminize karşı test etmek de mümkündür; bu, Üretim veritabanı sisteminize karşı test etme bölümünde ele alınmıştır.

Bahşiş

Bu sayfada xUnit teknikleri gösterilmektedir, ancak NUnit de dahil olmak üzere diğer test çerçevelerinde benzer kavramlar mevcuttur.

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üme bakın. Depo düzenini uygulamanın ilk adımı, EF Core LINQ sorgularınızı ayrı bir katmana ayıklamaktır. Bu işlem daha sonra saplanır veya sahte hale getirilir. Bloglama sistemimiz için bir depo arabirimi örneği aşağıda verilmişti:

public interface IBloggingRepository
{
    Blog GetBlogByName(string name);

    IEnumerable<Blog> GetAllBlogs();

    void AddBlog(Blog blog);

    void SaveChanges();
}

... 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 Blog GetBlogByName(string name)
        => _context.Blogs.FirstOrDefault(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. Dikkate alınmaması gereken önemli nokta, yöntemimizin GetAllBlogs değil döndürdüğüdürIEnumerable<Blog>IQueryable<Blog>. İkincisini döndürmek, sorgu işleçlerinin sonuç üzerinde oluşturulabileceği ve EF Core'un sorguyu çevirmeye hala dahil olmasını gerektirdiği anlamına gelir; bu, bir depoya sahip olma amacını en başta yener. IEnumerable<Blog> deponun döndürdüğü verileri kolayca saptamamıza veya taklit etmemize olanak tanır.

ASP.NET Core uygulaması için, aşağıdakini uygulamanın ConfigureServicesöğesine ekleyerek depoyu bağımlılık eklemeye hizmet olarak kaydetmemiz gerekir:

services.AddScoped<IBloggingRepository, BloggingRepository>();

Son olarak, denetleyicilerimiz EF Core bağlamı yerine depo hizmetine eklenir ve üzerinde yöntemler yürütür:

private readonly IBloggingRepository _repository;

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

[HttpGet]
public Blog GetBlog(string name)
    => _repository.GetBlogByName(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 yalnızca depoyu saplayarak veya en sevdiğiniz sahte kitaplıkla sahtesini yaparak yazılabilir. Popüler Moq kitaplığını kullanan sahte tabanlı test örneği aşağıda verilmişti :

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

    var controller = new BloggingControllerWithRepository(repositoryMock.Object);

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

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

Örnek kodun tamamı burada görüntülenebilir.

Bellek içi SQLite

SQLite, üretim veritabanı sisteminiz (örneğin 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 bağlantıları gereksiz yere uzun süre tutmaktan kaçınmak için gerektiğinde (her sorgu yürütülürken) veritabanı bağlantılarını 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ürerek çekirdek verileriyle temiz bir veritabanımız olmasını sağlayan öğesini çağırabilir CreateContext.

Bellek içi SQLite testi için tam örnek kod burada gö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ının kullanılması kesinlikle önerilmez; bunun yerine SQLite kullanmayı veya depo düzenini 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 burada gö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 örnek her test öncesinde çağrılmalıdır EnsureDeleted ). 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ı InMemoryDatabaseRoot UseInMemoryDatabase paylaşması gereken tüm bağlamlar için açıkça aynı örneğini iletir (bu genellikle statik InMemoryDatabaseRoot bir alana sahip olarak yapılır).

Hareketler

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. Yukarıdaki örnekte olduğu gibi EF Core'un yoksayılması için yapılandırarak işlemlerin sessizce yoksayılması InMemoryEventId.TransactionIgnoredWarning isteyebilirsiniz. Ancak kodunuz aslında işlemsel semantiği kullanıyorsa (örneğin, geri alma değişikliklerinin geri alınmasına bağlıdır) testiniz çalışmaz.

Görünümler

Bellek içi sağlayıcı, kullanarak ToInMemoryQueryLINQ 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 }));