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 ConfigureServices
az 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 }));