Megosztás a következőn keresztül:


Tesztelés éles adatbázisrendszer nélkül

Ezen az oldalon olyan automatizált tesztek írásának módszereit tárgyaljuk, amelyek nem érintik az adatbázisrendszert, amelyen az alkalmazás éles környezetben fut, az adatbázis cseréjével egy teszt dublőr-re. Ehhez különböző típusú tesztpárok és megközelítések állnak rendelkezésre, és javasoljuk, hogy alaposan olvassa el Tesztelési stratégia kiválasztása a különböző lehetőségek teljes megértéséhez. Végül lehetőség van tesztelni az éles adatbázisrendszeren is, amit a Az éles adatbázisrendszer teszteléseszakasz tárgyal.

Borravaló

Ezen az oldalon xUnit technikák láthatók, de hasonló fogalmak léteznek más tesztelési keretrendszerekben is, például NUnit.

Adattárminta

Ha úgy döntött, hogy teszteket ír az éles adatbázisrendszer bevonása nélkül, akkor az ajánlott módszer az adattár mintája; további háttérért lásd ebben a szakaszban. Az adattár mintának az implementálásának első lépése, hogy az EF Core LINQ-lekérdezéseket elkülönítjük egy külön rétegbe, amelyet később csalunk vagy szimulálunk. Íme egy példa a blogírási rendszer adattár-felületére:

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

    IAsyncEnumerable<Blog> GetAllBlogsAsync();

    void AddBlog(Blog blog);

    Task SaveChangesAsync();
}

... és íme egy részleges minta implementáció éles használatra:

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...
}

Nincs sok benne: az adattár egyszerűen körbefuttat egy EF Core-környezetet, és olyan metódusokat tesz elérhetővé, amelyek végrehajtják az adatbázis-lekérdezéseket és -frissítéseket rajta. Fontos megjegyezni, hogy a GetAllBlogs metódus IAsyncEnumerable<Blog> (vagy IEnumerable<Blog>) ad vissza, és nem IQueryable<Blog>. Az utóbbi visszaadása azt jelentené, hogy a lekérdezési operátorok továbbra is összeállíthatók az eredmény alapján, ami megköveteli, hogy az EF Core továbbra is részt vegyen a lekérdezés fordításában; ez először is legyőzné azt a célt, hogy adattárat kelljen létrehoznia. IAsyncEnumerable<Blog> lehetővé teszi számunkra, hogy könnyen stubot készítsen vagy modellezze, amit az adattár szolgáltat.

Egy ASP.NET Core-alkalmazás esetében az adattárat szolgáltatásként kell regisztrálnunk a függőséginjektálásban az alkalmazás ConfigureServicesaz alábbiak hozzáadásával:

services.AddScoped<IBloggingRepository, BloggingRepository>();

Végül a vezérlőink az EF Core-környezet helyett az adattárszolgáltatással lesznek injektálva, és metódusokat hajtanak végre rajta:

private readonly IBloggingRepository _repository;

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

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

Ezen a ponton az alkalmazás az adattár mintája szerint van megformázva: az adatelérési réteggel ( EF Core) csak az adattárrétegen keresztül lehet kapcsolatba lépni, amely közvetítőként működik az alkalmazáskód és a tényleges adatbázis-lekérdezések között. A tesztek most már egyszerűen megírhatók a tároló becsonkításával, vagy a kedvenc mock-könyvtárával való tesztelésével. Íme egy példa egy modellalapú tesztre a népszerű Moq könyvtár használatával:

[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);
}

A teljes mintakód itt tekinthető meg itt.

SQLite a memóriában

Az SQLite könnyedén konfigurálható EF Core szolgáltatóként a tesztkörnyezet számára, az éles adatbázisrendszer (például SQL Server) helyett. A részletekért tekintse meg az SQLite szolgáltató dokumentációját. Azonban általában érdemes az SQLite memóriabeli adatbázis funkcióját használni teszteléskor, mivel egyszerű elkülönítést biztosít a tesztek között, és nem igényel tényleges SQLite-fájlok kezelését.

A memóriabeli SQLite használatához fontos tisztában lenni azzal, hogy egy új adatbázis akkor jön létre, amikor alacsony szintű kapcsolatot nyit meg, és hogy a kapcsolat bezárásakor törlődik. Normál használat esetén az EF Core DbContext szükség szerint megnyitja és bezárja az adatbázis-kapcsolatokat – minden lekérdezés végrehajtásakor – annak érdekében, hogy szükségtelenül hosszú ideig ne maradjon kapcsolat. A memóriabeli SQLite esetén azonban ez az adatbázis minden alkalommal alaphelyzetbe állításához vezetne; kerülő megoldásként megnyitjuk a kapcsolatot, mielőtt átadnánk azt az EF Core-nak, és gondoskodunk róla, hogy csak a teszt befejezésekor legyen lezárva:

    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();

A tesztek mostantól meghívhatják a CreateContext-t, amely a konstruktorban beállított kapcsolat használatával visszaad egy környezetet, biztosítva, hogy tiszta adatbázissal rendelkezzünk az előkészített adatokkal.

A memóriabeli SQLite tesztelésének teljes mintakódja itt tekinthető meg .

Memóriabeli szolgáltató

Amint azt a tesztelés áttekintési oldalánelmondtuk, a memóriabeli szolgáltató használatát erősen elriasztjuk; inkább az SQLite használatát javasoljuk, vagy az adattár mintaimplementálását. Ha a memóriában való használat mellett döntött, az alábbi egy tipikus tesztosztály-konstruktor, amely minden teszt előtt beállít és létrehoz egy új memórián belüli adatbázist:

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();
}

A memóriabeli tesztelés teljes mintakódja itt tekinthető meg itt.

Memóriabeli adatbázis elnevezése

A memóriában lévő adatbázisokat egy egyszerű sztringnév azonosítja, és ugyanahhoz az adatbázishoz többször is csatlakozhat ugyanazzal a névvel (ezért a fenti mintának minden teszt előtt meg kell hívnia EnsureDeleted). Vegye figyelembe azonban, hogy a memóriabeli adatbázisok a környezet belső szolgáltatójában gyökereznek; míg a legtöbb esetben a környezetek ugyanazt a szolgáltatót használják, a környezetek különböző beállításokkal való konfigurálása új belső szolgáltató használatát válthatja ki. Ebben az esetben explicit módon adja át ugyanazt a InMemoryDatabaseRoot-példányt a UseInMemoryDatabase minden olyan környezetnek, amelynek memórián belüli adatbázisokat kell megosztania (ez általában statikus InMemoryDatabaseRoot mezővel történik).

Tranzakciók

Vegye figyelembe, hogy alapértelmezés szerint, ha egy tranzakció elindul, a memóriabeli szolgáltató kivételt dob, mivel a tranzakciók nem támogatottak. Előfordulhat, hogy a tranzakciókat csendesen figyelmen kívül szeretné hagyni, ha úgy konfigurálja az EF Core-t, hogy figyelmen kívül hagyja InMemoryEventId.TransactionIgnoredWarning, mint a fenti példában. Ha azonban a kód valójában tranzakciós szemantikára támaszkodik – például a módosítások visszaállításától függ –, a teszt nem fog működni.

Nézetek

A memóriabeli szolgáltató lehetővé teszi a nézetek meghatározását LINQ-lekérdezésekkel, ToInMemoryQueryhasználatával:

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