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


Integrációs tesztek a ASP.NET Core-ban

Készítette: Jos van der Til, Martin Costello, és Javier Calvarro Nelson.

Az integrációs tesztek biztosítják, hogy az alkalmazás összetevői megfelelően működjenek olyan szinten, amely magában foglalja az alkalmazás támogató infrastruktúráját, például az adatbázist, a fájlrendszert és a hálózatot. ASP.NET Core támogatja az integrációs teszteket egy tesztwebhelyen és egy memóriabeli tesztkiszolgálón futó egységteszt-keretrendszer használatával.

Ez a cikk feltételezi az egységtesztek alapszintű megértését. Ha nem ismeri a tesztelési fogalmakat, tekintse meg a tesztelés a .NET-ben című cikket és annak csatolt tartalmát.

Mintakód megtekintése vagy letöltése (hogyan kell letölteni)

A mintaalkalmazás egy Razor Pages-alkalmazás, és feltételezi, hogy alapszintű ismeretekkel rendelkezik a Razor Pages-ről. Ha nem ismeri a Razor oldalak tartalmát, olvassa el a következő cikkeket.

Az SLA-kteszteléséhez olyan eszközt ajánlunk, mint például a .NET-Playwright, amely automatizálhatja a böngészőt.

Bevezetés az integrációs tesztekbe

Az integrációs tesztek az alkalmazás összetevőit szélesebb szinten értékelik ki, mint egységtesztek. Az egységtesztek izolált szoftverösszetevők, például az egyes osztálymódszerek tesztelésére szolgálnak. Az integrációs tesztek megerősítik, hogy két vagy több alkalmazásösszetevő együttműködik a várt eredmény érdekében, beleértve a kérések teljes feldolgozásához szükséges összes összetevőt is.

Ezek a szélesebb körű tesztek az alkalmazás infrastruktúrájának és teljes keretrendszerének tesztelésére szolgálnak, gyakran a következő összetevőket is beleértve:

  • Adatbázis
  • Fájlrendszer
  • Hálózati berendezések
  • Kérelem-válasz folyamat

Az egységtesztek az infrastruktúra-összetevők helyett létrehozott összetevőket használnak, más néven hamis vagy szimulált objektumokat.

Az egységtesztekkel ellentétben az integrációs tesztek:

  • Használja azokat a tényleges összetevőket, amelyeket az alkalmazás éles környezetben használ.
  • További kód- és adatfeldolgozást igényel.
  • A futtatás hosszabb időt vesz igénybe.

Ezért az integrációs tesztek használatának korlátozása a legfontosabb infrastruktúra-forgatókönyvekre. Ha egy viselkedést egységteszt vagy integrációs teszt használatával lehet tesztelni, válassza ki az egységtesztet.

Az integrációs tesztek megvitatása során a tesztelt projektet gyakran nevezik System Under Test, vagy röviden "SUT" néven. A cikk során a "SUT" a tesztelt ASP.NET Core-alkalmazásra hivatkozik.

Ne írjon integrációs teszteket az adatbázisok és fájlrendszerek minden permutációs adat- és fájlhozzáféréshez. Függetlenül attól, hogy az alkalmazások hány helyen használják az adatbázisokat és a fájlrendszereket, az olvasási, írási, frissítési és törlési integrációs tesztek koncentrált készlete általában képes megfelelően tesztelni az adatbázis- és fájlrendszer-összetevőket. Használjon egységteszteket az ezen összetevőkkel interakcióba lépő metóduslogika rutintesztjeihez. Az egységtesztekben az infrastruktúra hamis vagy hamisított használata gyorsabb tesztvégrehajtást eredményez.

ASP.NET Core integrációs tesztek

A ASP.NET Core integrációs tesztjeihez a következők szükségesek:

  • A tesztprojektek a teszteket tartalmazzák és hajtják végre. A tesztprojekt hivatkozással rendelkezik a SUT-ra.
  • A tesztprojekt létrehoz egy tesztwebhely-gazdagépet a SUT számára, és egy tesztkiszolgáló-ügyféllel kezeli a kérelmeket és a válaszokat a SUT-val.
  • A tesztfuttató a tesztek végrehajtására és a teszteredmények jelentésére szolgál.

Az integrációs tesztek olyan események sorozatát követik, amelyek magukban foglalják a szokásos Előkészület, Végrehajtásés Ellenőrzés tesztelési lépéseket.

  1. Az SUT webhoszt konfigurálva van.
  2. Létrejön egy tesztkiszolgáló-ügyfél, amely kéréseket küld az alkalmazásnak.
  3. A Arrange tesztelési lépés végrehajtása: A tesztalkalmazás előkészít egy kérést.
  4. A törvény tesztlépés végrehajtása: Az ügyfél elküldi a kérést, és megkapja a választ.
  5. A ellenőrzési tesztlépést végrehajtják: A tényleges választ a rendszer a várt válasz alapján érvényesíti, és érvényesnek nyilvánítja (), vagy sikertelennek ().
  6. A folyamat az összes teszt végrehajtásáig folytatódik.
  7. A teszteredmények jelentésre kerültek.

A teszt webgazda általában eltérő konfigurációval van beállítva, mint az alkalmazás normál webgazdája a tesztek futtatásához. Előfordulhat például, hogy a tesztekhez egy másik adatbázist vagy különböző alkalmazásbeállításokat használnak.

Az infrastruktúra-összetevőket, például a tesztweb-gazdagépet és a memóriabeli tesztkiszolgálót (TestServer) a Microsoft.AspNetCore.Mvc.Testing csomag biztosítja vagy felügyeli. A csomag használata leegyszerűsíti a tesztek létrehozását és végrehajtását.

A Microsoft.AspNetCore.Mvc.Testing csomag a következő feladatokat kezeli:

  • Másolja a függőségi fájlt (.deps) a SUT-ból a tesztprojekt bin könyvtárába.
  • A tartalomgyökerét-re a SUT projektgyökeréhez állítja, hogy a statikus fájlok és a lapok/nézetek megtalálhatók legyenek a tesztek végrehajtásakor.
  • A WebApplicationFactory osztály gördülékenyebbé teszi a SUT TestServer használatával történő rendszerindítását.

Az egységtesztek dokumentációja ismerteti a tesztprojektek és tesztfuttatók beállításának módját, valamint a tesztek futtatásának részletes utasításait, valamint a tesztek és tesztosztályok elnevezésére vonatkozó javaslatokat.

Egységtesztek elkülönítése az integrációs tesztektől különböző projektekre. A tesztek elkülönítése:

  • Segít biztosítani, hogy az infrastruktúra tesztelési összetevői véletlenül ne legyenek belefoglalva az egységtesztekbe.
  • Lehetővé teszi a tesztkészlet futtatásának szabályozását.

Gyakorlatilag nincs különbség a Razor Pages-alkalmazások és az MVC-alkalmazások tesztjeinek konfigurációja között. Az egyetlen különbség a tesztek elnevezésében van. Egy Razor Pages-alkalmazásban az oldalvégpontok tesztjeit általában az oldalmodell-osztályról nevezik el (például IndexPageTests az indexlap összetevőintegrációjának teszteléséhez). Az MVC-alkalmazásokban a tesztek általában vezérlőosztályok szerint vannak rendszerezve, és az általuk tesztelt vezérlőkről nevezik el őket (például HomeControllerTests a Home vezérlő összetevőintegrációjának teszteléséhez).

Alkalmazás előfeltételeinek tesztelése

A tesztprojektnek a következőnek kell lennie:

Ezek az előfeltételek láthatók a mintaalkalmazás-ban. Vizsgálja meg a tests/RazorPagesProject.Tests/RazorPagesProject.Tests.csproj fájlt. A mintaalkalmazás az xUnit tesztelési keretrendszert és a AngleSharp elemzőtárat használja, így a mintaalkalmazás a következőre is hivatkozik:

A xunit.runner.visualstudio 2.4.2-es vagy újabb verzióját használó alkalmazásokban a tesztprojektnek hivatkoznia kell a Microsoft.NET.Test.Sdk csomagra.

Az Entity Framework Core a tesztekben is használatos. Tekintse meg a projektfájlt a GitHub.

SUT-környezet

Ha a SUT környezet nincs beállítva, a környezet alapértelmezés szerint a Fejlesztési környezet.

Alapszintű tesztek az alapértelmezett WebApplicationFactory használatával

A WebApplicationFactory<TEntryPoint>-t használjuk egy TestServer létrehozásához az integrációs tesztekhez. TEntryPoint a SUT belépési pontosztálya, általában Program.cs.

A tesztosztályok implementálnak egy osztály-fix technikai felületet (IClassFixture), amely jelzi, hogy az osztály teszteket tartalmaz, és megosztott objektumpéldányokat biztosít az osztály tesztjei során.

Az alábbi tesztosztály BasicTestsa WebApplicationFactory használatával végzi a SUT inicializálását, és biztosít egy HttpClient-t a Get_EndpointsReturnSuccessAndCorrectContentTypetesztmetódus számára. A metódus ellenőrzi, hogy a válasz állapotkódja sikeres-e (200-299), és a Content-Type fejléc több alkalmazásoldalon text/html; charset=utf-8.

CreateClient() létrehoz egy HttpClient-példányt, amely automatikusan követi az átirányításokat és kezeli a cookie-kat.

public class BasicTests 
    : IClassFixture<WebApplicationFactory<Program>>
{
    private readonly WebApplicationFactory<Program> _factory;

    public BasicTests(WebApplicationFactory<Program> factory)
    {
        _factory = factory;
    }

    [Theory]
    [InlineData("/")]
    [InlineData("/Index")]
    [InlineData("/About")]
    [InlineData("/Privacy")]
    [InlineData("/Contact")]
    public async Task Get_EndpointsReturnSuccessAndCorrectContentType(string url)
    {
        // Arrange
        var client = _factory.CreateClient();

        // Act
        var response = await client.GetAsync(url);

        // Assert
        response.EnsureSuccessStatusCode(); // Status Code 200-299
        Assert.Equal("text/html; charset=utf-8", 
            response.Content.Headers.ContentType.ToString());
    }
}
[TestClass]
public class BasicTests
{
    private static CustomWebApplicationFactory<Program> _factory;

    [ClassInitialize]
    public static void AssemblyInitialize(TestContext _)
    {
        _factory = new CustomWebApplicationFactory<Program>();
    }

    [ClassCleanup(ClassCleanupBehavior.EndOfClass)]
    public static void AssemblyCleanup(TestContext _)
    {
        _factory.Dispose();
    }

    [TestMethod]
    [DataRow("/")]
    [DataRow("/Index")]
    [DataRow("/About")]
    [DataRow("/Privacy")]
    [DataRow("/Contact")]
    public async Task Get_EndpointsReturnSuccessAndCorrectContentType(string url)
    {
        // Arrange
        var client = _factory.CreateClient();

        // Act
        var response = await client.GetAsync(url);

        // Assert
        response.EnsureSuccessStatusCode(); // Status Code 200-299
        Assert.AreEqual("text/html; charset=utf-8",
            response.Content.Headers.ContentType.ToString());
    }
}
public class BasicTests
{
    private CustomWebApplicationFactory<Program>
        _factory;

    [SetUp]
    public void SetUp()
    {
        _factory = new CustomWebApplicationFactory<Program>();
    }

    [TearDown]
    public void TearDown()
    {
        _factory.Dispose();
    }

    [DatapointSource]
    public string[] values = ["/", "/Index", "/About", "/Privacy", "/Contact"];

    [Theory]
    public async Task Get_EndpointsReturnSuccessAndCorrectContentType(string url)
    {
        // Arrange
        var client = _factory.CreateClient();

        // Act
        var response = await client.GetAsync(url);

        // Assert
        response.EnsureSuccessStatusCode(); // Status Code 200-299
        Assert.That(response.Content.Headers.ContentType.ToString(), Is.EqualTo("text/html; charset=utf-8"));
    }
}

Alapértelmezés szerint a nem alapvető cookie-k nem maradnak meg a kérések között, ha engedélyezve van az általános adatvédelmi rendelet hozzájárulási szabályzata. A nem alapvető cookie-k( például a TempData-szolgáltató által használt) megőrzése érdekében jelölje meg őket alapvetőként a tesztek során. A cookie alapvetőként való megjelölésére vonatkozó utasításokért lásd: Alapvető cookie-k.

AngleSharp és Application Parts az antiforgery-ellenőrzésekhez

Ez a cikk a AngleSharp elemzőt használja az antiforgery-ellenőrzések kezelésére oldalak betöltése és a HTML elemzése révén. A vezérlő végpontjainak és a Razor oldalak nézeteinek részletesebb teszteléséhez, anélkül, hogy törődne azzal, hogyan jelennek meg a böngészőben, fontolja meg a Application Partshasználatát. Az Alkalmazásrészek megközelítés egy vezérlőt vagy Razor lapot injektál az alkalmazásba, amellyel JSON-kéréseket lehet küldeni a szükséges értékek lekéréséhez. További információért lásd a blogot: Integrációs tesztelés ASP.NET Core erőforrások Antiforgery-vel történő védelme az Alkalmazás Részei segítségével és a kapcsolódó GitHub-adattár szerzője: Martin Costello.

A WebApplicationFactory testreszabása

A webtárhely konfigurációja a tesztosztályoktól függetlenül hozható létre, úgy, hogy a WebApplicationFactory<TEntryPoint>-ból örökölt egy vagy több egyéni gyárat készítünk.

  1. Örököljön WebApplicationFactory és bírálja felül ConfigureWebHost. A IWebHostBuilder lehetővé teszi a szolgáltatásgyűjtemény konfigurálását IWebHostBuilder.ConfigureServices

    public class CustomWebApplicationFactory<TProgram>
        : WebApplicationFactory<TProgram> where TProgram : class
    {
        protected override void ConfigureWebHost(IWebHostBuilder builder)
        {
            builder.ConfigureServices(services =>
            {
                var dbContextDescriptor = services.SingleOrDefault(
                    d => d.ServiceType == 
                        typeof(IDbContextOptionsConfiguration<ApplicationDbContext>));
    
                services.Remove(dbContextDescriptor);
    
                var dbConnectionDescriptor = services.SingleOrDefault(
                    d => d.ServiceType ==
                        typeof(DbConnection));
    
                services.Remove(dbConnectionDescriptor);
    
                // Create open SqliteConnection so EF won't automatically close it.
                services.AddSingleton<DbConnection>(container =>
                {
                    var connection = new SqliteConnection("DataSource=:memory:");
                    connection.Open();
    
                    return connection;
                });
    
                services.AddDbContext<ApplicationDbContext>((container, options) =>
                {
                    var connection = container.GetRequiredService<DbConnection>();
                    options.UseSqlite(connection);
                });
            });
    
            builder.UseEnvironment("Development");
        }
    }
    
    public class CustomWebApplicationFactory<TProgram>
        : WebApplicationFactory<TProgram> where TProgram : class
    {
        protected override void ConfigureWebHost(IWebHostBuilder builder)
        {
            builder.ConfigureServices(services =>
            {
                var dbContextDescriptor = services.SingleOrDefault(
                    d => d.ServiceType ==
                        typeof(IDbContextOptionsConfiguration<ApplicationDbContext>));
    
                services.Remove(dbContextDescriptor);
    
                var dbConnectionDescriptor = services.SingleOrDefault(
                    d => d.ServiceType ==
                        typeof(DbConnection));
    
                services.Remove(dbConnectionDescriptor);
    
                // Create open SqliteConnection so EF won't automatically close it.
                services.AddSingleton<DbConnection>(container =>
                {
                    var connection = new SqliteConnection("DataSource=:memory:");
                    connection.Open();
    
                    return connection;
                });
    
                services.AddDbContext<ApplicationDbContext>((container, options) =>
                {
                    var connection = container.GetRequiredService<DbConnection>();
                    options.UseSqlite(connection);
                });
            });
    
            builder.UseEnvironment("Development");
        }
    }
    
    public class CustomWebApplicationFactory<TProgram>
        : WebApplicationFactory<TProgram> where TProgram : class
    {
        protected override void ConfigureWebHost(IWebHostBuilder builder)
        {
            builder.ConfigureServices(services =>
            {
                var dbContextDescriptor = services.SingleOrDefault(
                    d => d.ServiceType ==
                        typeof(IDbContextOptionsConfiguration<ApplicationDbContext>));
    
                services.Remove(dbContextDescriptor);
    
                var dbConnectionDescriptor = services.SingleOrDefault(
                    d => d.ServiceType ==
                        typeof(DbConnection));
    
                services.Remove(dbConnectionDescriptor);
    
                // Create open SqliteConnection so EF won't automatically close it.
                services.AddSingleton<DbConnection>(container =>
                {
                    var connection = new SqliteConnection("DataSource=:memory:");
                    connection.Open();
    
                    return connection;
                });
    
                services.AddDbContext<ApplicationDbContext>((container, options) =>
                {
                    var connection = container.GetRequiredService<DbConnection>();
                    options.UseSqlite(connection);
                });
            });
    
            builder.UseEnvironment("Development");
        }
    }
    

    Az adatbázis-vetést a mintaalkalmazásban a InitializeDbForTests metódus végzi. A módszert az integrációs tesztek mintájában ismertetjük: Az alkalmazás szervezetének tesztelése szakasz.

    A SUT adatbázis-környezetét regisztrálták Program.cs-ben. A tesztalkalmazás builder.ConfigureServices visszahívása az alkalmazás lesz végrehajtva. Ha más adatbázist szeretne használni a tesztekhez, mint az alkalmazás adatbázisa, az alkalmazás adatbázis-környezetét a builder.ConfigureServicesadatbázis-környezetre kell cserélni.

    A mintaalkalmazás megkeresi az adatbázis-környezet szolgáltatásleíróját, és a leíró használatával eltávolítja a szolgáltatásregisztrációt. A gyár ezután hozzáad egy új ApplicationDbContext, amely memórián belüli adatbázist használ a tesztekhez.

    Ha másik adatbázishoz szeretne csatlakozni, módosítsa a DbConnection. SQL Server tesztadatbázis használata:

  1. Az egyéni CustomWebApplicationFactory-t használja a tesztosztályokban. Az alábbi példa a gyárat használja a IndexPageTests osztályban:

    public class IndexPageTests :
        IClassFixture<CustomWebApplicationFactory<Program>>
    {
        private readonly HttpClient _client;
        private readonly CustomWebApplicationFactory<Program>
            _factory;
    
        public IndexPageTests(
            CustomWebApplicationFactory<Program> factory)
        {
            _factory = factory;
            _client = factory.CreateClient(new WebApplicationFactoryClientOptions
            {
                AllowAutoRedirect = false
            });
        }
    
    [TestClass]
    public class IndexPageTests
    {
        private static HttpClient _client;
        private static CustomWebApplicationFactory<Program>
            _factory;
    
        [ClassInitialize]
        public static void AssemblyInitialize(TestContext _)
        {
            _factory = new CustomWebApplicationFactory<Program>();
            _client = _factory.CreateClient(new WebApplicationFactoryClientOptions
            {
                AllowAutoRedirect = false
            });
        }
    
        [ClassCleanup(ClassCleanupBehavior.EndOfClass)]
        public static void AssemblyCleanup(TestContext _)
        {
            _factory.Dispose();
        }
    
    public class IndexPageTests
    {
    
        private HttpClient _client;
        private CustomWebApplicationFactory<Program>
            _factory;
    
        [SetUp]
        public void SetUp()
        {
            _factory = new CustomWebApplicationFactory<Program>();
            _client = _factory.CreateClient(new WebApplicationFactoryClientOptions
            {
                AllowAutoRedirect = false
            });
        }
    
        [TearDown]
        public void TearDown()
        {
            _factory.Dispose();
            _client.Dispose();
        }
    

    A mintaalkalmazás ügyfele úgy van konfigurálva, hogy megakadályozza a HttpClient átirányítások követését. Amint azt a Mock authentication szakasz későbbi szakasza ismerteti, ez lehetővé teszi a tesztek számára az alkalmazás első válaszának eredményének ellenőrzését. Az első válasz ezek közül sok tesztben egy átirányítás egy "Location" fejléccel.

  2. Egy tipikus teszt a HttpClient és segédmetenek használatával dolgozza fel a kérést és a választ:

    [Fact]
    public async Task Post_DeleteAllMessagesHandler_ReturnsRedirectToRoot()
    {
        // Arrange
        var defaultPage = await _client.GetAsync("/");
        var content = await HtmlHelpers.GetDocumentAsync(defaultPage);
    
        //Act
        var response = await _client.SendAsync(
            (IHtmlFormElement)content.QuerySelector("form[id='messages']"),
            (IHtmlButtonElement)content.QuerySelector("button[id='deleteAllBtn']"));
    
        // Assert
        Assert.Equal(HttpStatusCode.OK, defaultPage.StatusCode);
        Assert.Equal(HttpStatusCode.Redirect, response.StatusCode);
        Assert.Equal("/", response.Headers.Location.OriginalString);
    }
    
    [TestMethod]
    public async Task Post_DeleteAllMessagesHandler_ReturnsRedirectToRoot()
    {
        // Arrange
        var defaultPage = await _client.GetAsync("/");
        var content = await HtmlHelpers.GetDocumentAsync(defaultPage);
    
        //Act
        var response = await _client.SendAsync(
            (IHtmlFormElement)content.QuerySelector("form[id='messages']"),
            (IHtmlButtonElement)content.QuerySelector("button[id='deleteAllBtn']"));
    
        // Assert
        Assert.AreEqual(HttpStatusCode.OK, defaultPage.StatusCode);
        Assert.AreEqual(HttpStatusCode.Redirect, response.StatusCode);
        Assert.AreEqual("/", response.Headers.Location.OriginalString);
    }
    
    [Test]
    public async Task Post_DeleteAllMessagesHandler_ReturnsRedirectToRoot()
    {
        // Arrange
        var defaultPage = await _client.GetAsync("/");
        var content = await HtmlHelpers.GetDocumentAsync(defaultPage);
    
        //Act
        var response = await _client.SendAsync(
            (IHtmlFormElement)content.QuerySelector("form[id='messages']"),
            (IHtmlButtonElement)content.QuerySelector("button[id='deleteAllBtn']"));
    
        // Assert
        Assert.That(defaultPage.StatusCode, Is.EqualTo(HttpStatusCode.OK));
        Assert.That(response.StatusCode, Is.EqualTo(HttpStatusCode.Redirect));
        Assert.That(response.Headers.Location.OriginalString, Is.EqualTo("/"));
    }
    

A SUT-nak küldött POST-kéréseknek meg kell felelniük az alkalmazás adatvédelmi rendszerének automatikusan végrehajtott hamisítás elleni ellenőrzési folyamatának. A teszt POST-kérésének rendezéséhez a tesztalkalmazásnak a következőnek kell lennie:

  1. Kérjen egy kérelmet az oldalhoz.
  2. Elemezze az azonosítóhamisítás elleni cookie-t, és kérje le az érvényesítési jogkivonatot a választól.
  3. Végezze el a POST kérést az antiforgery cookie és az érvényesítési jogkivonattal.

A mintaalkalmazás SendAsync segédmetódusai (Helpers/HttpClientExtensions.cs) és a GetDocumentAsync segédmetódus (Helpers/HtmlHelpers.cs) az AngleSharp elemzővel kezelik az antiforgery-ellenőrzést az alábbi módszerekkel:

  • GetDocumentAsync: Megkapja a HttpResponseMessage, és visszaad egy IHtmlDocument. GetDocumentAsync egy olyan gyárat használ, amely az eredeti alapján készít elő egy HttpResponseMessage-t. További információért tekintse meg az AngleSharp dokumentációt.
  • SendAsync kiterjesztési metódusai a HttpClient összállítanak egy HttpRequestMessage-t, és meghívják a SendAsync(HttpRequestMessage)-t, hogy kérelmeket küldjenek a SUT-nak. A SendAsync túlterhelései elfogadják a HTML-űrlapot (IHtmlFormElement) és a következőket:
    • Az űrlap Küldés gombja (IHtmlElement)
    • Űrlapértékek gyűjtése (IEnumerable<KeyValuePair<string, string>>)
    • Küldés gomb (IHtmlElement) és űrlapértékek (IEnumerable<KeyValuePair<string, string>>)

AngleSharp egy harmadik féltől származó elemzési kódtár, amelyet bemutató célokra használnak, ebben a cikkben és a mintaalkalmazásban. Az AngleSharp nem támogatott vagy szükséges ASP.NET Core-alkalmazások integrációs teszteléséhez. Más elemzők is használhatók, például a Html Agility Pack (HAP). Egy másik módszer, ha olyan kódot írunk, amely közvetlenül kezeli az antiforgery rendszer kérés-ellenőrzési tokenjét és az antiforgery cookie-t. További információkért tekintse meg az antiforgery-ellenőrzésekről szóló cikket itt: AngleSharp vs Application Parts,.

A EF-Core memóriabeli adatbázis-szolgáltató korlátozott és alapszintű teszteléshez használható, de a SQLite-szolgáltató a memóriabeli teszteléshez ajánlott.

Lásd: Indítás kiterjesztése indítási szűrőkkel, amely bemutatja, hogyan konfigurálhat köztes szoftvereket IStartupFilterhasználatával, ami akkor hasznos, ha egy teszthez egyéni szolgáltatásra vagy köztes szoftverre van szükség.

Az ügyfél testreszabása a WithWebHostBuilder használatával

Ha egy tesztmetóduson belül további konfigurációra van szükség, WithWebHostBuilder létrehoz egy új WebApplicationFactory egy olyan IWebHostBuilder-vel, amelyet a konfiguráció további testreszab.

A mintakód meghívja WithWebHostBuilder, hogy a konfigurált szolgáltatásokat tesztcsomókkal cserélje le. További információért és a példák használatáért lásd a Modellszolgáltatások injektálása rész ebben a cikkben.

A mintaalkalmazás Post_DeleteMessageHandler_ReturnsRedirectToRoot tesztmetódusa a WithWebHostBuilderhasználatát mutatja be. Ez a teszt egy rekord törlését hajtja végre az adatbázisban egy űrlapbeküldés SUT-ban való aktiválásával.

Mivel a IndexPageTests osztály egy másik tesztje olyan műveletet hajt végre, amely törli az adatbázis összes rekordját, és a Post_DeleteMessageHandler_ReturnsRedirectToRoot metódus előtt futhat, az adatbázist ebben a tesztmetódusban újból el kell végezni, hogy a rendszer egy rekordot adjon meg a SUT számára a törléshez. A SUT-ban a messages űrlap első törlési gombjának kiválasztását a rendszer szimulálja a SUT-nak küldött kérésben:

[Fact]
public async Task Post_DeleteMessageHandler_ReturnsRedirectToRoot()
{
    // Arrange
    using (var scope = _factory.Services.CreateScope())
    {
        var scopedServices = scope.ServiceProvider;
        var db = scopedServices.GetRequiredService<ApplicationDbContext>();

        Utilities.ReinitializeDbForTests(db);
    }

    var defaultPage = await _client.GetAsync("/");
    var content = await HtmlHelpers.GetDocumentAsync(defaultPage);

    //Act
    var response = await _client.SendAsync(
        (IHtmlFormElement)content.QuerySelector("form[id='messages']"),
        (IHtmlButtonElement)content.QuerySelector("form[id='messages']")
            .QuerySelector("div[class='panel-body']")
            .QuerySelector("button"));

    // Assert
    Assert.Equal(HttpStatusCode.OK, defaultPage.StatusCode);
    Assert.Equal(HttpStatusCode.Redirect, response.StatusCode);
    Assert.Equal("/", response.Headers.Location.OriginalString);
}
[TestMethod]
public async Task Post_DeleteMessageHandler_ReturnsRedirectToRoot()
{
    // Arrange
    using (var scope = _factory.Services.CreateScope())
    {
        var scopedServices = scope.ServiceProvider;
        var db = scopedServices.GetRequiredService<ApplicationDbContext>();

        Utilities.ReinitializeDbForTests(db);
    }

    var defaultPage = await _client.GetAsync("/");
    var content = await HtmlHelpers.GetDocumentAsync(defaultPage);

    //Act
    var response = await _client.SendAsync(
        (IHtmlFormElement)content.QuerySelector("form[id='messages']"),
        (IHtmlButtonElement)content.QuerySelector("form[id='messages']")
            .QuerySelector("div[class='panel-body']")
            .QuerySelector("button"));

    // Assert
    Assert.AreEqual(HttpStatusCode.OK, defaultPage.StatusCode);
    Assert.AreEqual(HttpStatusCode.Redirect, response.StatusCode);
    Assert.AreEqual("/", response.Headers.Location.OriginalString);
}
[Test]
public async Task Post_DeleteMessageHandler_ReturnsRedirectToRoot()
{
    // Arrange
    using (var scope = _factory.Services.CreateScope())
    {
        var scopedServices = scope.ServiceProvider;
        var db = scopedServices.GetRequiredService<ApplicationDbContext>();

        Utilities.ReinitializeDbForTests(db);
    }

    var defaultPage = await _client.GetAsync("/");
    var content = await HtmlHelpers.GetDocumentAsync(defaultPage);

    //Act
    var response = await _client.SendAsync(
        (IHtmlFormElement)content.QuerySelector("form[id='messages']"),
        (IHtmlButtonElement)content.QuerySelector("form[id='messages']")
            .QuerySelector("div[class='panel-body']")
            .QuerySelector("button"));

    // Assert
    Assert.That(defaultPage.StatusCode, Is.EqualTo(HttpStatusCode.OK));
    Assert.That(response.StatusCode, Is.EqualTo(HttpStatusCode.Redirect));
    Assert.That(response.Headers.Location.OriginalString, Is.EqualTo("/"));
}

Ügyfélbeállítások

A WebApplicationFactoryClientOptions példányok létrehozásakor az alapértelmezett beállításokat és az elérhető beállításokat a HttpClient lapon tekinti meg.

Hozza létre a WebApplicationFactoryClientOptions osztályt, és adja át a CreateClient() metódusnak:

public class IndexPageTests :
    IClassFixture<CustomWebApplicationFactory<Program>>
{
    private readonly HttpClient _client;
    private readonly CustomWebApplicationFactory<Program>
        _factory;

    public IndexPageTests(
        CustomWebApplicationFactory<Program> factory)
    {
        _factory = factory;
        _client = factory.CreateClient(new WebApplicationFactoryClientOptions
        {
            AllowAutoRedirect = false
        });
    }
[TestClass]
public class IndexPageTests
{
    private static HttpClient _client;
    private static CustomWebApplicationFactory<Program>
        _factory;

    [ClassInitialize]
    public static void AssemblyInitialize(TestContext _)
    {
        _factory = new CustomWebApplicationFactory<Program>();
        _client = _factory.CreateClient(new WebApplicationFactoryClientOptions
        {
            AllowAutoRedirect = false
        });
    }

    [ClassCleanup(ClassCleanupBehavior.EndOfClass)]
    public static void AssemblyCleanup(TestContext _)
    {
        _factory.Dispose();
    }
public class IndexPageTests
{

    private HttpClient _client;
    private CustomWebApplicationFactory<Program>
        _factory;

    [SetUp]
    public void SetUp()
    {
        _factory = new CustomWebApplicationFactory<Program>();
        _client = _factory.CreateClient(new WebApplicationFactoryClientOptions
        {
            AllowAutoRedirect = false
        });
    }

    [TearDown]
    public void TearDown()
    {
        _factory.Dispose();
        _client.Dispose();
    }

MEGJEGYZÉS: A HTTPS-átirányítási figyelmeztetések elkerülése érdekében, amikor a HTTPS-átirányító köztes réteget használja, állítsa be a BaseAddress = new Uri("https://localhost")

Makettszolgáltatások injektálása

A szolgáltatások felülírhatók egy tesztben a ConfigureTestServices hívásával a gazdagép építőn. A felülbírált szolgáltatások konkrét teszt hatóköréhez a WithWebHostBuilder metódus használható egy hostkészítő lekérésére. Ez a következő tesztekben látható:

A minta SUT tartalmaz egy hatókörű szolgáltatást, amely egy idézetet ad vissza. Az indexlap kérésekor az idézet az Index lap rejtett mezőjébe van beágyazva.

Services/IQuoteService.cs:

public interface IQuoteService
{
    Task<string> GenerateQuote();
}

Services/QuoteService.cs:

// Quote ©1975 BBC: The Doctor (Tom Baker); Dr. Who: Planet of Evil
// https://www.bbc.co.uk/programmes/p00pyrx6
public class QuoteService : IQuoteService
{
    public Task<string> GenerateQuote()
    {
        return Task.FromResult<string>(
            "Come on, Sarah. We've an appointment in London, " +
            "and we're already 30,000 years late.");
    }
}

Program.cs:

services.AddScoped<IQuoteService, QuoteService>();

Pages/Index.cshtml.cs:

public class IndexModel : PageModel
{
    private readonly ApplicationDbContext _db;
    private readonly IQuoteService _quoteService;

    public IndexModel(ApplicationDbContext db, IQuoteService quoteService)
    {
        _db = db;
        _quoteService = quoteService;
    }

    [BindProperty]
    public Message Message { get; set; }

    public IList<Message> Messages { get; private set; }

    [TempData]
    public string MessageAnalysisResult { get; set; }

    public string Quote { get; private set; }

    public async Task OnGetAsync()
    {
        Messages = await _db.GetMessagesAsync();

        Quote = await _quoteService.GenerateQuote();
    }

Pages/Index.cs:

<input id="quote" type="hidden" value="@Model.Quote">

A SUT-alkalmazás futtatásakor a következő korrektúra jön létre:

<input id="quote" type="hidden" value="Come on, Sarah. We&#x27;ve an appointment in 
    London, and we&#x27;re already 30,000 years late.">

A szolgáltatás és az injektálás teszteléséhez integrációs tesztben a teszt egy ál-szolgáltatást injektál a SUT-ba. A modellszolgáltatás lecseréli az alkalmazás QuoteService-t a tesztalkalmazás által biztosított, TestQuoteServicenevű szolgáltatásra.

IntegrationTests.IndexPageTests.cs:

// Quote ©1975 BBC: The Doctor (Tom Baker); Pyramids of Mars
// https://www.bbc.co.uk/programmes/p00pys55
public class TestQuoteService : IQuoteService
{
    public Task<string> GenerateQuote()
    {
        return Task.FromResult(
            "Something's interfering with time, Mr. Scarman, " +
            "and time is my business.");
    }
}
// Quote ©1975 BBC: The Doctor (Tom Baker); Pyramids of Mars
// https://www.bbc.co.uk/programmes/p00pys55
public class TestQuoteService : IQuoteService
{
    public Task<string> GenerateQuote()
    {
        return Task.FromResult(
            "Something's interfering with time, Mr. Scarman, " +
            "and time is my business.");
    }
}
// Quote ©1975 BBC: The Doctor (Tom Baker); Pyramids of Mars
// https://www.bbc.co.uk/programmes/p00pys55
public class TestQuoteService : IQuoteService
{
    public Task<string> GenerateQuote()
    {
        return Task.FromResult(
            "Something's interfering with time, Mr. Scarman, " +
            "and time is my business.");
    }
}

ConfigureTestServices van meghívva, és a hatókörön belüli szolgáltatás regisztrálva van:

[Fact]
public async Task Get_QuoteService_ProvidesQuoteInPage()
{
    // Arrange
    var client = _factory.WithWebHostBuilder(builder =>
        {
            builder.ConfigureTestServices(services =>
            {
                services.AddScoped<IQuoteService, TestQuoteService>();
            });
        })
        .CreateClient();

    //Act
    var defaultPage = await client.GetAsync("/");
    var content = await HtmlHelpers.GetDocumentAsync(defaultPage);
    var quoteElement = content.QuerySelector("#quote");

    // Assert
    Assert.Equal("Something's interfering with time, Mr. Scarman, " +
        "and time is my business.", quoteElement.Attributes["value"].Value);
}
[TestMethod]
public async Task Get_QuoteService_ProvidesQuoteInPage()
{
    // Arrange
    var client = _factory.WithWebHostBuilder(builder =>
        {
            builder.ConfigureTestServices(services =>
            {
                services.AddScoped<IQuoteService, TestQuoteService>();
            });
        })
        .CreateClient();

    //Act
    var defaultPage = await client.GetAsync("/");
    var content = await HtmlHelpers.GetDocumentAsync(defaultPage);
    var quoteElement = content.QuerySelector("#quote");

    // Assert
    Assert.AreEqual("Something's interfering with time, Mr. Scarman, " +
        "and time is my business.", quoteElement.Attributes["value"].Value);
}
[Test]
public async Task Get_QuoteService_ProvidesQuoteInPage()
{
    // Arrange
    var client = _factory.WithWebHostBuilder(builder =>
        {
            builder.ConfigureTestServices(services =>
            {
                services.AddScoped<IQuoteService, TestQuoteService>();
            });
        })
        .CreateClient();

    //Act
    var defaultPage = await client.GetAsync("/");
    var content = await HtmlHelpers.GetDocumentAsync(defaultPage);
    var quoteElement = content.QuerySelector("#quote");

    // Assert
    Assert.That(quoteElement.Attributes["value"].Value, Is.EqualTo(
        "Something's interfering with time, Mr. Scarman, " +
        "and time is my business."));
}

A teszt végrehajtása során létrehozott jelölések a TestQuoteServiceáltal megadott idézőjelszöveget tükrözik, ezért az állítás sikeres.

<input id="quote" type="hidden" value="Something&#x27;s interfering with time, 
    Mr. Scarman, and time is my business.">

Szimulált hitelesítés

Az AuthTests osztály tesztjei ellenőrzik, hogy egy biztonságos végpont:

  • Átirányít egy nem hitelesített felhasználót az alkalmazás bejelentkezési oldalára.
  • Egy hitelesített felhasználó tartalmát adja vissza.

A SUT-ban a /SecurePage lap a AuthorizePage konvenciót használja, hogy egy AuthorizeFilter-t alkalmazzon a lapra. További információkért lásd a Razor oldalak engedélyezési konvencióit.

services.AddRazorPages(options =>
{
    options.Conventions.AuthorizePage("/SecurePage");
});

A Get_SecurePageRedirectsAnUnauthenticatedUser tesztben egy WebApplicationFactoryClientOptions úgy van beállítva, hogy letiltsa az átirányításokat azáltal, hogy AllowAutoRedirect-t false-ra állítja.

[Fact]
public async Task Get_SecurePageRedirectsAnUnauthenticatedUser()
{
    // Arrange
    var client = _factory.CreateClient(
        new WebApplicationFactoryClientOptions
        {
            AllowAutoRedirect = false
        });

    // Act
    var response = await client.GetAsync("/SecurePage");

    // Assert
    Assert.Equal(HttpStatusCode.Redirect, response.StatusCode);
    Assert.StartsWith("http://localhost/Identity/Account/Login",
        response.Headers.Location.OriginalString);
}
[TestMethod]
public async Task Get_SecurePageRedirectsAnUnauthenticatedUser()
{
    // Arrange
    var client = _factory.CreateClient(
        new WebApplicationFactoryClientOptions
        {
            AllowAutoRedirect = false
        });

    // Act
    var response = await client.GetAsync("/SecurePage");

    // Assert
    Assert.AreEqual(HttpStatusCode.Redirect, response.StatusCode);
    StringAssert.StartsWith(response.Headers.Location.OriginalString, "http://localhost/Identity/Account/Login");
}
[Test]
public async Task Get_SecurePageRedirectsAnUnauthenticatedUser()
{
    // Arrange
    var client = _factory.CreateClient(
        new WebApplicationFactoryClientOptions
        {
            AllowAutoRedirect = false
        });

    // Act
    var response = await client.GetAsync("/SecurePage");

    // Assert
    Assert.That(response.StatusCode, Is.EqualTo(HttpStatusCode.Redirect));
    Assert.That(response.Headers.Location.OriginalString, Does.StartWith("http://localhost/Identity/Account/Login"));
}

Az ügyfél átirányításának megakadályozásával a következő ellenőrzéseket hajthatja végre:

  • A SUT által visszaadott állapotkód a várt HttpStatusCode.Redirect eredményen ellenőrizhető, nem pedig a bejelentkezési lapra való átirányítás utáni végleges állapotkóddal, amely HttpStatusCode.OK.
  • A válaszfejlécek közül a Location fejléc értékét ellenőrzik annak megállapítására, hogy http://localhost/Identity/Account/Login-el kezdődik-e, nem pedig a végleges bejelentkezési oldal válaszát, ahol a Location fejléc nem lenne jelen.

A tesztalkalmazás szimulálhat egy AuthenticationHandler<TOptions>ConfigureTestServices-ben a hitelesítés és az engedélyezés szempontjainak teszteléséhez. Egy minimális forgatókönyv AuthenticateResult.Success-t ad vissza.

public class TestAuthHandler : AuthenticationHandler<AuthenticationSchemeOptions>
{
    public TestAuthHandler(IOptionsMonitor<AuthenticationSchemeOptions> options,
        ILoggerFactory logger, UrlEncoder encoder)
        : base(options, logger, encoder)
    {
    }

    protected override Task<AuthenticateResult> HandleAuthenticateAsync()
    {
        var claims = new[] { new Claim(ClaimTypes.Name, "Test user") };
        var identity = new ClaimsIdentity(claims, "Test");
        var principal = new ClaimsPrincipal(identity);
        var ticket = new AuthenticationTicket(principal, "TestScheme");

        var result = AuthenticateResult.Success(ticket);

        return Task.FromResult(result);
    }
}
public class TestAuthHandler : AuthenticationHandler<AuthenticationSchemeOptions>
{
    public TestAuthHandler(IOptionsMonitor<AuthenticationSchemeOptions> options,
        ILoggerFactory logger, UrlEncoder encoder)
        : base(options, logger, encoder)
    {
    }

    protected override Task<AuthenticateResult> HandleAuthenticateAsync()
    {
        var claims = new[] { new Claim(ClaimTypes.Name, "Test user") };
        var identity = new ClaimsIdentity(claims, "Test");
        var principal = new ClaimsPrincipal(identity);
        var ticket = new AuthenticationTicket(principal, "TestScheme");

        var result = AuthenticateResult.Success(ticket);

        return Task.FromResult(result);
    }
}
public class TestAuthHandler : AuthenticationHandler<AuthenticationSchemeOptions>
{
    public TestAuthHandler(IOptionsMonitor<AuthenticationSchemeOptions> options,
        ILoggerFactory logger, UrlEncoder encoder)
        : base(options, logger, encoder)
    {
    }

    protected override Task<AuthenticateResult> HandleAuthenticateAsync()
    {
        var claims = new[] { new Claim(ClaimTypes.Name, "Test user") };
        var identity = new ClaimsIdentity(claims, "Test");
        var principal = new ClaimsPrincipal(identity);
        var ticket = new AuthenticationTicket(principal, "TestScheme");

        var result = AuthenticateResult.Success(ticket);

        return Task.FromResult(result);
    }
}

A TestAuthHandler egy felhasználó hitelesítésére hívják meg, amikor a hitelesítési séma TestScheme van beállítva, ahol a AddAuthentication regisztrálva van a ConfigureTestServices-ra. Fontos, hogy a TestScheme séma megfeleljen az alkalmazás által várt sémának. Ellenkező esetben a hitelesítés nem működik.

[Fact]
public async Task Get_SecurePageIsReturnedForAnAuthenticatedUser()
{
    // Arrange
    var client = _factory.WithWebHostBuilder(builder =>
        {
            builder.ConfigureTestServices(services =>
            {
                services.AddAuthentication(defaultScheme: "TestScheme")
                    .AddScheme<AuthenticationSchemeOptions, TestAuthHandler>(
                        "TestScheme", options => { });
            });
        })
        .CreateClient(new WebApplicationFactoryClientOptions
        {
            AllowAutoRedirect = false,
        });

    client.DefaultRequestHeaders.Authorization =
        new AuthenticationHeaderValue(scheme: "TestScheme");

    //Act
    var response = await client.GetAsync("/SecurePage");

    // Assert
    Assert.Equal(HttpStatusCode.OK, response.StatusCode);
}
[TestMethod]
public async Task Get_SecurePageIsReturnedForAnAuthenticatedUser()
{
    // Arrange
    var client = _factory.WithWebHostBuilder(builder =>
    {
        builder.ConfigureTestServices(services =>
        {
            services.AddAuthentication(defaultScheme: "TestScheme")
                .AddScheme<AuthenticationSchemeOptions, TestAuthHandler>(
                    "TestScheme", options => { });
        });
    })
        .CreateClient(new WebApplicationFactoryClientOptions
        {
            AllowAutoRedirect = false,
        });

    client.DefaultRequestHeaders.Authorization =
        new AuthenticationHeaderValue(scheme: "TestScheme");

    //Act
    var response = await client.GetAsync("/SecurePage");

    // Assert
    Assert.AreEqual(HttpStatusCode.OK, response.StatusCode);
}
[Test]
public async Task Get_SecurePageIsReturnedForAnAuthenticatedUser()
{
    // Arrange
    var client = _factory.WithWebHostBuilder(builder =>
    {
        builder.ConfigureTestServices(services =>
        {
            services.AddAuthentication(defaultScheme: "TestScheme")
                .AddScheme<AuthenticationSchemeOptions, TestAuthHandler>(
                    "TestScheme", options => { });
        });
    })
        .CreateClient(new WebApplicationFactoryClientOptions
        {
            AllowAutoRedirect = false,
        });

    client.DefaultRequestHeaders.Authorization =
        new AuthenticationHeaderValue(scheme: "TestScheme");

    //Act
    var response = await client.GetAsync("/SecurePage");

    // Assert
    Assert.That(response.StatusCode, Is.EqualTo(HttpStatusCode.OK));
}

A WebApplicationFactoryClientOptionskapcsolatos további információkért lásd a Ügyfélbeállítások című szakaszt.

A köztes szoftver hitelesítésének alapszintű tesztjei

A Köztes szoftver hitelesítésének alapszintű teszteléséhez tekintse meg ezt a GitHub-adattárat. A tesztforgatókönyvre jellemző tesztkiszolgálót tartalmaz.

A környezet beállítása

Állítsa be a környezetet az egyéni alkalmazás-előállítóban:

public class CustomWebApplicationFactory<TProgram>
    : WebApplicationFactory<TProgram> where TProgram : class
{
    protected override void ConfigureWebHost(IWebHostBuilder builder)
    {
        builder.ConfigureServices(services =>
        {
            var dbContextDescriptor = services.SingleOrDefault(
                d => d.ServiceType == 
                    typeof(IDbContextOptionsConfiguration<ApplicationDbContext>));

            services.Remove(dbContextDescriptor);

            var dbConnectionDescriptor = services.SingleOrDefault(
                d => d.ServiceType ==
                    typeof(DbConnection));

            services.Remove(dbConnectionDescriptor);

            // Create open SqliteConnection so EF won't automatically close it.
            services.AddSingleton<DbConnection>(container =>
            {
                var connection = new SqliteConnection("DataSource=:memory:");
                connection.Open();

                return connection;
            });

            services.AddDbContext<ApplicationDbContext>((container, options) =>
            {
                var connection = container.GetRequiredService<DbConnection>();
                options.UseSqlite(connection);
            });
        });

        builder.UseEnvironment("Development");
    }
}
public class CustomWebApplicationFactory<TProgram>
    : WebApplicationFactory<TProgram> where TProgram : class
{
    protected override void ConfigureWebHost(IWebHostBuilder builder)
    {
        builder.ConfigureServices(services =>
        {
            var dbContextDescriptor = services.SingleOrDefault(
                d => d.ServiceType ==
                    typeof(IDbContextOptionsConfiguration<ApplicationDbContext>));

            services.Remove(dbContextDescriptor);

            var dbConnectionDescriptor = services.SingleOrDefault(
                d => d.ServiceType ==
                    typeof(DbConnection));

            services.Remove(dbConnectionDescriptor);

            // Create open SqliteConnection so EF won't automatically close it.
            services.AddSingleton<DbConnection>(container =>
            {
                var connection = new SqliteConnection("DataSource=:memory:");
                connection.Open();

                return connection;
            });

            services.AddDbContext<ApplicationDbContext>((container, options) =>
            {
                var connection = container.GetRequiredService<DbConnection>();
                options.UseSqlite(connection);
            });
        });

        builder.UseEnvironment("Development");
    }
}
public class CustomWebApplicationFactory<TProgram>
    : WebApplicationFactory<TProgram> where TProgram : class
{
    protected override void ConfigureWebHost(IWebHostBuilder builder)
    {
        builder.ConfigureServices(services =>
        {
            var dbContextDescriptor = services.SingleOrDefault(
                d => d.ServiceType ==
                    typeof(IDbContextOptionsConfiguration<ApplicationDbContext>));

            services.Remove(dbContextDescriptor);

            var dbConnectionDescriptor = services.SingleOrDefault(
                d => d.ServiceType ==
                    typeof(DbConnection));

            services.Remove(dbConnectionDescriptor);

            // Create open SqliteConnection so EF won't automatically close it.
            services.AddSingleton<DbConnection>(container =>
            {
                var connection = new SqliteConnection("DataSource=:memory:");
                connection.Open();

                return connection;
            });

            services.AddDbContext<ApplicationDbContext>((container, options) =>
            {
                var connection = container.GetRequiredService<DbConnection>();
                options.UseSqlite(connection);
            });
        });

        builder.UseEnvironment("Development");
    }
}

Hogyan következtet a tesztinfrastruktúra az alkalmazás tartalomgyökerének elérési útjára?

A WebApplicationFactory konstruktor meghatározza az alkalmazás tartalom gyökér elérési útját úgy, hogy a WebApplicationFactoryContentRootAttribute szerelvény TEntryPointkulccsal a System.Reflection.Assembly.FullName-t keres az integrációs teszteket tartalmazó szerelvényen. Ha nem található a megfelelő kulccsal rendelkező attribútum, WebApplicationFactory visszatér a megoldásfájl (.sln) kereséséhez, és hozzáadja a TEntryPoint szerelvény nevét a megoldáskönyvtárhoz. Az alkalmazás gyökérkönyvtára (a tartalom gyökérútvonala) a nézetek és tartalomfájlok felderítésére szolgál.

Árnyékmásolás letiltása

Az árnyékmásolás miatt a tesztek a kimeneti könyvtártól eltérő könyvtárban futnak. Ha a tesztek a fájlok Assembly.Location-hoz viszonyított betöltésére támaszkodnak, és problémákat tapasztalunk, előfordulhat, hogy le kell tiltani az árnyékmásolást.

Ha le szeretné tiltani az árnyékmásolást az xUnit használatakor, hozzon létre egy xunit.runner.json fájlt a tesztprojekt könyvtárában a megfelelő konfigurációs beállítással:

{
  "shadowCopy": false
}

Objektumok ártalmatlanítása

A IClassFixture implementáció tesztjeinek végrehajtása után TestServer és HttpClient megsemmisülnek, amikor az xUnit megsemmisíti a WebApplicationFactory-at. Ha a fejlesztő által példányosított objektumok ártalmatlanítást igényelnek, azokat a IClassFixture implementációban kell megsemmisíteni. További információért lásd: Dispose metódus implementálása.

A TestClass tesztjeinek végrehajtása után az MSTest a TestServer metódusban megsemmisíti a HttpClient-et, ekkor pedig a WebApplicationFactory és ClassCleanup is megsemmisülnek. Ha a fejlesztő által példányosított objektumok megsemmisítést igényelnek, a ClassCleanup metódusban kell megsemmisíteni őket. További információért lásd: Dispose metódus implementálása.

A tesztosztály tesztjeinek végrehajtása után az NUnit megsemmisíti a TestServer a HttpClient metódusban. Ha a fejlesztő által példányosított objektumok megsemmisítést igényelnek, a TearDown metódusban kell megsemmisíteni őket. További információért lásd: Dispose metódus implementálása.

Integrációs tesztek mintája

A mintaalkalmazás két alkalmazásból áll:

Alkalmazás Projektmappa Leírás
Üzenetküldő alkalmazás (a SUT) src/RazorPagesProject Lehetővé teszi a felhasználó számára, hogy hozzáadjon, töröljön egyet, törölje az összeset, és elemezze az üzeneteket.
Alkalmazás tesztelése tests/RazorPagesProject.Tests A SUT integrációs tesztelésére szolgál.

A tesztek egy IDE beépített tesztfunkcióival futtathatók, például Visual Studio. Ha Visual Studio Code vagy parancssort használ, hajtsa végre a következő parancsot egy parancssorban a tests/RazorPagesProject.Tests könyvtárban:

dotnet test

Üzenetküldő alkalmazás (SUT) szervezete

A SUT egy Razor Pages üzenetrendszer, amely a következő jellemzőkkel rendelkezik:

  • Az alkalmazás indexoldala (Pages/Index.cshtml és Pages/Index.cshtml.cs) felhasználói felületi és lapmodellezési módszereket biztosít az üzenetek hozzáadásának, törlésének és elemzésének szabályozásához (átlagos szavak üzenetenként).
  • Az üzeneteket a Message osztály (Data/Message.cs) írja le két tulajdonságokkal: Id (kulcs) és Text (üzenet). A Text tulajdonság megadása kötelező, és legfeljebb 200 karakter hosszúságú lehet.
  • Az üzenetek tárolása Entity Framework memórián belüli adatbázisának† használatával történik.
  • Az alkalmazás egy adatelérési réteget (DAL) tartalmaz az adatbázis környezeti osztályában, AppDbContext (Data/AppDbContext.cs).
  • Ha az adatbázis üres az alkalmazás indításakor, az üzenettároló három üzenettel lesz inicializálva.
  • Az alkalmazás tartalmaz egy /SecurePage, amelyet csak hitelesített felhasználó érhet el.

†Az EF-cikk, Az InMemorytesztelése című cikk bemutatja, hogyan használható memóriabeli adatbázis az MSTesttel végzett tesztekhez. Ez a témakör az xUnit tesztelési keretrendszert használja. A különböző tesztelési keretrendszerek tesztelési fogalmai és tesztelési implementációi hasonlóak, de nem azonosak.

Bár az alkalmazás nem használja az adattár mintát, és nem hatékony példa a Munkaegység (UoW) mintára, a Razor Pages támogatja ezeket a fejlesztési mintákat. További információ: Az infrastruktúra perzisztenciaréteg tervezése és Tesztvezérlő logika (a minta implementálja az adattár mintát).

Tesztalkalmazás szervezése

A tesztalkalmazás egy konzolalkalmazás a tests/RazorPagesProject.Tests könyvtárban.

Alkalmazáskönyvtár tesztelése Leírás
AuthTests A következő tesztelési módszereket tartalmazza:
  • Biztonságos lap elérése hitelesítés nélküli felhasználó által.
  • Biztonságos lap elérése egy hitelesített felhasználó által egy ál-AuthenticationHandler<TOptions>.
  • GitHub-felhasználói profil beszerzése és a profil felhasználói bejelentkezésének ellenőrzése.
BasicTests Az útválasztás és a tartalomtípus tesztelési módszerét tartalmazza.
IntegrationTests Az Index lap integrációs tesztjeit tartalmazza egyéni WebApplicationFactory osztály használatával.
Helpers/Utilities
  • Utilities.cs tartalmazza az adatbázis tesztelési adatokkal való üzembe helyezésekor használt InitializeDbForTests metódust.
  • HtmlHelpers.cs egy AngleSharp IHtmlDocument visszaadására szolgáló metódust biztosít a tesztelési módszerek számára.
  • HttpClientExtensions.cs túlterhelést biztosít a SendAsync számára, hogy kéréseket küldjön a SUT-nak.

A tesztelési keretrendszer xUnit. Az integrációs teszteket a Microsoft.AspNetCore.TestHostsegítségével hajtják végre, amely magában foglalja a TestServer-et. Mivel a Microsoft.AspNetCore.Mvc.Testing csomag a tesztgazda és a tesztkiszolgáló konfigurálására szolgál, a TestHost és TestServer csomagok nem igényelnek közvetlen csomaghivatkozásokat a tesztalkalmazás projektfájljában vagy fejlesztői konfigurációjában a tesztalkalmazásban.

Az integrációs tesztek általában kis adatkészletet igényelnek az adatbázisban a teszt végrehajtása előtt. A törlési teszt például egy adatbázisrekord törlését kéri, ezért az adatbázisnak legalább egy rekorddal kell rendelkeznie ahhoz, hogy a törlési kérelem sikeres legyen.

A mintaalkalmazás három üzenettel tölti fel az adatbázist a Utilities.cs-ben, amelyeket a tesztek futtatásakor használhatnak.

public static void InitializeDbForTests(ApplicationDbContext db)
{
    db.Messages.AddRange(GetSeedingMessages());
    db.SaveChanges();
}

public static void ReinitializeDbForTests(ApplicationDbContext db)
{
    db.Messages.RemoveRange(db.Messages);
    InitializeDbForTests(db);
}

public static List<Message> GetSeedingMessages()
{
    return new List<Message>()
    {
        new Message(){ Text = "TEST RECORD: You're standing on my scarf." },
        new Message(){ Text = "TEST RECORD: Would you like a jelly baby?" },
        new Message(){ Text = "TEST RECORD: To the rational mind, " +
            "nothing is inexplicable; only unexplained." }
    };
}

A SUT adatbázis-környezetét regisztrálták Program.cs-ben. A tesztalkalmazás builder.ConfigureServices visszahívása az alkalmazás lesz végrehajtva. Ha másik adatbázist szeretne használni a tesztekhez, az alkalmazás adatbázis-környezetét le kell cserélni a builder.ConfigureServices-ban. A további információkért lásd a WebApplicationFactory testreszabása szakaszt.

További erőforrások

Ez a témakör feltételezi az egységtesztek alapszintű megértését. Ha nem ismeri a tesztelési fogalmakat, tekintse meg a .NET Core és a .NET Standard Egységtesztelés című témakört és annak csatolt tartalmát.

Mintakód megtekintése vagy letöltése (hogyan kell letölteni)

A mintaalkalmazás egy Razor Pages-alkalmazás, és feltételezi, hogy alapszintű ismeretekkel rendelkezik a Razor Pages-ről. Ha nem ismeri Razor oldalakat, tekintse meg az alábbi témaköröket:

Jegyzet

Az SLA-k teszteléséhez olyan eszközt ajánlunk, mint a Playwright for .NET, amely automatizálhatja a böngészőt.

Bevezetés az integrációs tesztekbe

Az integrációs tesztek az alkalmazás összetevőit szélesebb szinten értékelik ki, mint egységtesztek. Az egységtesztek izolált szoftverösszetevők, például az egyes osztálymódszerek tesztelésére szolgálnak. Az integrációs tesztek megerősítik, hogy két vagy több alkalmazásösszetevő együttműködik a várt eredmény érdekében, beleértve a kérések teljes feldolgozásához szükséges összes összetevőt is.

Ezek a szélesebb körű tesztek az alkalmazás infrastruktúrájának és teljes keretrendszerének tesztelésére szolgálnak, gyakran a következő összetevőket is beleértve:

  • Adatbázis
  • Fájlrendszer
  • Hálózati berendezések
  • Kérelem-válasz folyamat

Az egységtesztek az infrastruktúra-összetevők helyett létrehozott összetevőket használnak, más néven hamis vagy szimulált objektumokat.

Az egységtesztekkel ellentétben az integrációs tesztek:

  • Használja azokat a tényleges összetevőket, amelyeket az alkalmazás éles környezetben használ.
  • További kód- és adatfeldolgozást igényel.
  • A futtatás hosszabb időt vesz igénybe.

Ezért az integrációs tesztek használatának korlátozása a legfontosabb infrastruktúra-forgatókönyvekre. Ha egy viselkedést egységteszt vagy integrációs teszt használatával lehet tesztelni, válassza ki az egységtesztet.

Az integrációs tesztek megvitatása során a tesztelt projektet gyakran nevezik System Under Test, vagy röviden "SUT" néven. A cikk során a "SUT" a tesztelt ASP.NET Core-alkalmazásra hivatkozik.

Ne írjon integrációs teszteket az adatbázisok és fájlrendszerek minden permutációs adat- és fájlhozzáféréshez. Függetlenül attól, hogy az alkalmazások hány helyen használják az adatbázisokat és a fájlrendszereket, az olvasási, írási, frissítési és törlési integrációs tesztek koncentrált készlete általában képes megfelelően tesztelni az adatbázis- és fájlrendszer-összetevőket. Használjon egységteszteket az ezen összetevőkkel interakcióba lépő metóduslogika rutintesztjeihez. Az egységtesztekben az infrastruktúra hamis vagy hamisított használata gyorsabb tesztvégrehajtást eredményez.

ASP.NET Core integrációs tesztek

A ASP.NET Core integrációs tesztjeihez a következők szükségesek:

  • A tesztprojektek a teszteket tartalmazzák és hajtják végre. A tesztprojekt hivatkozással rendelkezik a SUT-ra.
  • A tesztprojekt létrehoz egy tesztwebhely-gazdagépet a SUT számára, és egy tesztkiszolgáló-ügyféllel kezeli a kérelmeket és a válaszokat a SUT-val.
  • A tesztfuttató a tesztek végrehajtására és a teszteredmények jelentésére szolgál.

Az integrációs tesztek olyan események sorozatát követik, amelyek magukban foglalják a szokásos Előkészület, Végrehajtásés Ellenőrzés tesztelési lépéseket.

  1. Az SUT webhoszt konfigurálva van.
  2. Létrejön egy tesztkiszolgáló-ügyfél, amely kéréseket küld az alkalmazásnak.
  3. A Arrange tesztelési lépés végrehajtása: A tesztalkalmazás előkészít egy kérést.
  4. A törvény tesztlépés végrehajtása: Az ügyfél elküldi a kérést, és megkapja a választ.
  5. A ellenőrzési tesztlépést végrehajtják: A tényleges választ a rendszer a várt válasz alapján érvényesíti, és érvényesnek nyilvánítja (), vagy sikertelennek ().
  6. A folyamat az összes teszt végrehajtásáig folytatódik.
  7. A teszteredmények jelentésre kerültek.

A teszt webgazda általában eltérő konfigurációval van beállítva, mint az alkalmazás normál webgazdája a tesztek futtatásához. Előfordulhat például, hogy a tesztekhez egy másik adatbázist vagy különböző alkalmazásbeállításokat használnak.

Az infrastruktúra-összetevőket, például a tesztweb-gazdagépet és a memóriabeli tesztkiszolgálót (TestServer) a Microsoft.AspNetCore.Mvc.Testing csomag biztosítja vagy felügyeli. A csomag használata leegyszerűsíti a tesztek létrehozását és végrehajtását.

A Microsoft.AspNetCore.Mvc.Testing csomag a következő feladatokat kezeli:

  • Másolja a függőségi fájlt (.deps) a SUT-ból a tesztprojekt bin könyvtárába.
  • A tartalomgyökerét-re a SUT projektgyökeréhez állítja, hogy a statikus fájlok és a lapok/nézetek megtalálhatók legyenek a tesztek végrehajtásakor.
  • A WebApplicationFactory osztály gördülékenyebbé teszi a SUT TestServer használatával történő rendszerindítását.

Az egységtesztek dokumentációja ismerteti a tesztprojektek és tesztfuttatók beállításának módját, valamint a tesztek futtatásának részletes utasításait, valamint a tesztek és tesztosztályok elnevezésére vonatkozó javaslatokat.

Egységtesztek elkülönítése az integrációs tesztektől különböző projektekre. A tesztek elkülönítése:

  • Segít biztosítani, hogy az infrastruktúra tesztelési összetevői véletlenül ne legyenek belefoglalva az egységtesztekbe.
  • Lehetővé teszi a tesztkészlet futtatásának szabályozását.

Gyakorlatilag nincs különbség a Razor Pages-alkalmazások és az MVC-alkalmazások tesztjeinek konfigurációja között. Az egyetlen különbség a tesztek elnevezésében van. Egy Razor Pages-alkalmazásban az oldalvégpontok tesztjeit általában az oldalmodell-osztályról nevezik el (például IndexPageTests az indexlap összetevőintegrációjának teszteléséhez). Az MVC-alkalmazásokban a tesztek általában vezérlőosztályok szerint vannak rendszerezve, és az általuk tesztelt vezérlőkről nevezik el őket (például HomeControllerTests a Home vezérlő összetevőintegrációjának teszteléséhez).

Alkalmazás előfeltételeinek tesztelése

A tesztprojektnek a következőnek kell lennie:

Ezek az előfeltételek láthatók a mintaalkalmazás-ban. Vizsgálja meg a tests/RazorPagesProject.Tests/RazorPagesProject.Tests.csproj fájlt. A mintaalkalmazás az xUnit tesztelési keretrendszert és a AngleSharp elemzőtárat használja, így a mintaalkalmazás a következőre is hivatkozik:

A xunit.runner.visualstudio 2.4.2-es vagy újabb verzióját használó alkalmazásokban a tesztprojektnek hivatkoznia kell a Microsoft.NET.Test.Sdk csomagra.

Az Entity Framework Core a tesztekben is használatos. Az alkalmazás hivatkozásai:

SUT-környezet

Ha a SUT környezet nincs beállítva, a környezet alapértelmezés szerint a Fejlesztési környezet.

Alapszintű tesztek az alapértelmezett WebApplicationFactory használatával

A WebApplicationFactory<TEntryPoint>-t használjuk egy TestServer létrehozásához az integrációs tesztekhez. TEntryPoint a SUT belépési pontosztálya, általában a Startup osztály.

A tesztosztályok implementálnak egy osztály-fix technikai felületet (IClassFixture), amely jelzi, hogy az osztály teszteket tartalmaz, és megosztott objektumpéldányokat biztosít az osztály tesztjei során.

Az alábbi tesztosztály BasicTestsa WebApplicationFactory használatával végzi a SUT inicializálását, és biztosít egy HttpClient-t a Get_EndpointsReturnSuccessAndCorrectContentTypetesztmetódus számára. A metódus ellenőrzi, hogy a válasz állapotkódja sikeres-e (az állapotkódok a 200–299 tartományba esnek), és hogy a Content-Type fejléc több alkalmazásoldal esetében text/html; charset=utf-8-e.

CreateClient() létrehoz egy HttpClient-példányt, amely automatikusan követi az átirányításokat és kezeli a cookie-kat.

public class BasicTests 
    : IClassFixture<WebApplicationFactory<RazorPagesProject.Startup>>
{
    private readonly WebApplicationFactory<RazorPagesProject.Startup> _factory;

    public BasicTests(WebApplicationFactory<RazorPagesProject.Startup> factory)
    {
        _factory = factory;
    }

    [Theory]
    [InlineData("/")]
    [InlineData("/Index")]
    [InlineData("/About")]
    [InlineData("/Privacy")]
    [InlineData("/Contact")]
    public async Task Get_EndpointsReturnSuccessAndCorrectContentType(string url)
    {
        // Arrange
        var client = _factory.CreateClient();

        // Act
        var response = await client.GetAsync(url);

        // Assert
        response.EnsureSuccessStatusCode(); // Status Code 200-299
        Assert.Equal("text/html; charset=utf-8", 
            response.Content.Headers.ContentType.ToString());
    }
}

Alapértelmezés szerint a nem alapvető cookie-k nem maradnak meg a kérések között, ha engedélyezve van a GDPR hozzájárulási szabályzata. A nem alapvető cookie-k( például a TempData-szolgáltató által használt) megőrzése érdekében jelölje meg őket alapvetőként a tesztek során. A cookie alapvetőként való megjelölésére vonatkozó utasításokért lásd: Alapvető cookie-k.

A WebApplicationFactory testreszabása

A webtárhely konfigurációja a tesztosztályoktól függetlenül hozható létre, úgy, hogy a WebApplicationFactory-ból örökölt egy vagy több egyéni gyárat készítünk.

  1. Örököljön WebApplicationFactory és bírálja felül ConfigureWebHost. A IWebHostBuilder lehetővé teszi a szolgáltatások gyűjteményének konfigurálását a ConfigureServicessegítségével.

    public class CustomWebApplicationFactory<TStartup>
        : WebApplicationFactory<TStartup> where TStartup: class
    {
        protected override void ConfigureWebHost(IWebHostBuilder builder)
        {
            builder.ConfigureServices(services =>
            {
                var descriptor = services.SingleOrDefault(
                    d => d.ServiceType ==
                        typeof(DbContextOptions<ApplicationDbContext>));
    
                services.Remove(descriptor);
    
                services.AddDbContext<ApplicationDbContext>(options =>
                {
                    options.UseInMemoryDatabase("InMemoryDbForTesting");
                });
    
                var sp = services.BuildServiceProvider();
    
                using (var scope = sp.CreateScope())
                {
                    var scopedServices = scope.ServiceProvider;
                    var db = scopedServices.GetRequiredService<ApplicationDbContext>();
                    var logger = scopedServices
                        .GetRequiredService<ILogger<CustomWebApplicationFactory<TStartup>>>();
    
                    db.Database.EnsureCreated();
    
                    try
                    {
                        Utilities.InitializeDbForTests(db);
                    }
                    catch (Exception ex)
                    {
                        logger.LogError(ex, "An error occurred seeding the " +
                            "database with test messages. Error: {Message}", ex.Message);
                    }
                }
            });
        }
    }
    

    Az adatbázis-vetést a mintaalkalmazásban a InitializeDbForTests metódus végzi. A módszert az integrációs tesztek mintájában ismertetjük: Az alkalmazás szervezetének tesztelése szakasz.

    A SUT adatbázis-környezete a Startup.ConfigureServices metódusában van regisztrálva. A tesztalkalmazás builder.ConfigureServices visszahívása az alkalmazás lesz végrehajtva. A végrehajtási sorrend a Általános gazdagép a ASP.NET Core 3.0 kiadásával történő kompatibilitástörő változás. Ha más adatbázist szeretne használni a tesztekhez, mint az alkalmazás adatbázisa, az alkalmazás adatbázis-környezetét a builder.ConfigureServicesadatbázis-környezetre kell cserélni.

    Azokban az esetekben, amikor a SUT-ok továbbra is a Web Host-ot használják, a tesztalkalmazás builder.ConfigureServices visszahívása még a SUT Startup.ConfigureServices kódja előtt kerül végrehajtásra. A tesztalkalmazás builder.ConfigureTestServices visszahívása után lesz végrehajtva.

    A mintaalkalmazás megkeresi az adatbázis-környezet szolgáltatásleíróját, és a leíró használatával eltávolítja a szolgáltatásregisztrációt. Ezután a gyár hozzáad egy új ApplicationDbContext, amely memórián belüli adatbázist használ a tesztekhez.

    Ha a memóriában lévő adatbázistól eltérő adatbázishoz szeretne csatlakozni, módosítsa a UseInMemoryDatabase hívást a környezet másik adatbázishoz való csatlakoztatásához. SQL Server tesztadatbázis használata:

    services.AddDbContext<ApplicationDbContext>((options, context) => 
    {
        context.UseSqlServer(
            Configuration.GetConnectionString("TestingDbConnectionString"));
    });
    
  2. Az egyéni CustomWebApplicationFactory-t használja a tesztosztályokban. Az alábbi példa a gyárat használja a IndexPageTests osztályban:

    public class IndexPageTests : 
        IClassFixture<CustomWebApplicationFactory<RazorPagesProject.Startup>>
    {
        private readonly HttpClient _client;
        private readonly CustomWebApplicationFactory<RazorPagesProject.Startup> 
            _factory;
    
        public IndexPageTests(
            CustomWebApplicationFactory<RazorPagesProject.Startup> factory)
        {
            _factory = factory;
            _client = factory.CreateClient(new WebApplicationFactoryClientOptions
                {
                    AllowAutoRedirect = false
                });
        }
    

    A mintaalkalmazás ügyfele úgy van konfigurálva, hogy megakadályozza a HttpClient átirányítások követését. Amint azt a Mock authentication szakasz későbbi szakasza ismerteti, ez lehetővé teszi a tesztek számára az alkalmazás első válaszának eredményének ellenőrzését. Az első válasz ezek közül sok tesztben egy átirányítás egy "Location" fejléccel.

  3. Egy tipikus teszt a HttpClient és segédmetenek használatával dolgozza fel a kérést és a választ:

    [Fact]
    public async Task Post_DeleteAllMessagesHandler_ReturnsRedirectToRoot()
    {
        // Arrange
        var defaultPage = await _client.GetAsync("/");
        var content = await HtmlHelpers.GetDocumentAsync(defaultPage);
    
        //Act
        var response = await _client.SendAsync(
            (IHtmlFormElement)content.QuerySelector("form[id='messages']"),
            (IHtmlButtonElement)content.QuerySelector("button[id='deleteAllBtn']"));
    
        // Assert
        Assert.Equal(HttpStatusCode.OK, defaultPage.StatusCode);
        Assert.Equal(HttpStatusCode.Redirect, response.StatusCode);
        Assert.Equal("/", response.Headers.Location.OriginalString);
    }
    

A SUT-nak küldött POST-kéréseknek meg kell felelniük az alkalmazás adatvédelmi rendszerének automatikusan végrehajtott hamisítás elleni ellenőrzési folyamatának. A teszt POST-kérésének rendezéséhez a tesztalkalmazásnak a következőnek kell lennie:

  1. Kérjen egy kérelmet az oldalhoz.
  2. Elemezze az azonosítóhamisítás elleni cookie-t, és kérje le az érvényesítési jogkivonatot a választól.
  3. Végezze el a POST kérést az antiforgery cookie és az érvényesítési jogkivonattal.

A mintaalkalmazás SendAsync segédmetódusai (Helpers/HttpClientExtensions.cs) és a GetDocumentAsync segédmetódus (Helpers/HtmlHelpers.cs) az AngleSharp elemzővel kezelik az antiforgery-ellenőrzést az alábbi módszerekkel:

  • GetDocumentAsync: Megkapja a HttpResponseMessage, és visszaad egy IHtmlDocument. GetDocumentAsync egy olyan gyárat használ, amely az eredeti alapján készít elő egy HttpResponseMessage-t. További információért tekintse meg az AngleSharp dokumentációt.
  • SendAsync kiterjesztési metódusai a HttpClient összállítanak egy HttpRequestMessage-t, és meghívják a SendAsync(HttpRequestMessage)-t, hogy kérelmeket küldjenek a SUT-nak. A SendAsync túlterhelései elfogadják a HTML-űrlapot (IHtmlFormElement) és a következőket:
    • Az űrlap Küldés gombja (IHtmlElement)
    • Űrlapértékek gyűjtése (IEnumerable<KeyValuePair<string, string>>)
    • Küldés gomb (IHtmlElement) és űrlapértékek (IEnumerable<KeyValuePair<string, string>>)

Jegyzet

AngleSharp egy harmadik féltől származó elemzési kódtár, amelyet bemutató célokra használnak ebben a témakörben és a mintaalkalmazásban. Az AngleSharp nem támogatott vagy szükséges ASP.NET Core-alkalmazások integrációs teszteléséhez. Más elemzők is használhatók, például a Html Agility Pack (HAP). Egy másik módszer, ha olyan kódot írunk, amely közvetlenül kezeli az antiforgery rendszer kérés-ellenőrzési tokenjét és az antiforgery cookie-t.

Jegyzet

A EF-Core memóriabeli adatbázis-szolgáltató korlátozott és alapszintű teszteléshez használható, de a memóriabeli teszteléshez ajánlott SQLite-szolgáltató.

Az ügyfél testreszabása a WithWebHostBuilder használatával

Ha egy tesztmetóduson belül további konfigurációra van szükség, WithWebHostBuilder létrehoz egy új WebApplicationFactory egy olyan IWebHostBuilder-vel, amelyet a konfiguráció további testreszab.

A mintaalkalmazás Post_DeleteMessageHandler_ReturnsRedirectToRoot tesztmetódusa a WithWebHostBuilderhasználatát mutatja be. Ez a teszt egy rekord törlését hajtja végre az adatbázisban egy űrlapbeküldés SUT-ban való aktiválásával.

Mivel a IndexPageTests osztály egy másik tesztje olyan műveletet hajt végre, amely törli az adatbázis összes rekordját, és a Post_DeleteMessageHandler_ReturnsRedirectToRoot metódus előtt futhat, az adatbázist ebben a tesztmetódusban újból el kell végezni, hogy a rendszer egy rekordot adjon meg a SUT számára a törléshez. A SUT-ban a messages űrlap első törlési gombjának kiválasztását a rendszer szimulálja a SUT-nak küldött kérésben:

[Fact]
public async Task Post_DeleteMessageHandler_ReturnsRedirectToRoot()
{
    // Arrange
    var client = _factory.WithWebHostBuilder(builder =>
        {
            builder.ConfigureServices(services =>
            {
                var serviceProvider = services.BuildServiceProvider();

                using (var scope = serviceProvider.CreateScope())
                {
                    var scopedServices = scope.ServiceProvider;
                    var db = scopedServices
                        .GetRequiredService<ApplicationDbContext>();
                    var logger = scopedServices
                        .GetRequiredService<ILogger<IndexPageTests>>();

                    try
                    {
                        Utilities.ReinitializeDbForTests(db);
                    }
                    catch (Exception ex)
                    {
                        logger.LogError(ex, "An error occurred seeding " +
                            "the database with test messages. Error: {Message}", 
                            ex.Message);
                    }
                }
            });
        })
        .CreateClient(new WebApplicationFactoryClientOptions
        {
            AllowAutoRedirect = false
        });
    var defaultPage = await client.GetAsync("/");
    var content = await HtmlHelpers.GetDocumentAsync(defaultPage);

    //Act
    var response = await client.SendAsync(
        (IHtmlFormElement)content.QuerySelector("form[id='messages']"),
        (IHtmlButtonElement)content.QuerySelector("form[id='messages']")
            .QuerySelector("div[class='panel-body']")
            .QuerySelector("button"));

    // Assert
    Assert.Equal(HttpStatusCode.OK, defaultPage.StatusCode);
    Assert.Equal(HttpStatusCode.Redirect, response.StatusCode);
    Assert.Equal("/", response.Headers.Location.OriginalString);
}

Ügyfélbeállítások

Az alábbi táblázat a WebApplicationFactoryClientOptions-példányok létrehozásakor elérhető alapértelmezett HttpClient mutatja.

Opció Leírás Alapértelmezett
AllowAutoRedirect Lekérdezi vagy beállítja, hogy HttpClient példányoknak automatikusan követnie kell-e az átirányítási válaszokat. true
BaseAddress Lekéri vagy beállítja HttpClient példányok alapcímét. http://localhost
HandleCookies Lekérdezi vagy beállítja, hogy HttpClient példányok kezeljenek-e cookie-kat. true
MaxAutomaticRedirections Lekéri vagy beállítja a maximális számát azoknak az átirányítási válaszoknak, amelyeket a HttpClient példányoknak követniük kell. 7

Hozza létre a WebApplicationFactoryClientOptions osztályt, és adja át a CreateClient() metódusnak (az alapértelmezett értékek a példakódban láthatók):

// Default client option values are shown
var clientOptions = new WebApplicationFactoryClientOptions();
clientOptions.AllowAutoRedirect = true;
clientOptions.BaseAddress = new Uri("http://localhost");
clientOptions.HandleCookies = true;
clientOptions.MaxAutomaticRedirections = 7;

_client = _factory.CreateClient(clientOptions);

Makettszolgáltatások injektálása

A szolgáltatások felülírhatók egy tesztben a ConfigureTestServices hívásával a gazdagép építőn. A modellszolgáltatások injektálásához a SUT-nak rendelkeznie kell egy Startup osztállyal, amely tartalmaz egy Startup.ConfigureServices metódust.

A minta SUT tartalmaz egy hatókörű szolgáltatást, amely egy idézetet ad vissza. Az indexlap kérésekor az idézet az Index lap rejtett mezőjébe van beágyazva.

Services/IQuoteService.cs:

public interface IQuoteService
{
    Task<string> GenerateQuote();
}

Services/QuoteService.cs:

// Quote ©1975 BBC: The Doctor (Tom Baker); Dr. Who: Planet of Evil
// https://www.bbc.co.uk/programmes/p00pyrx6
public class QuoteService : IQuoteService
{
    public Task<string> GenerateQuote()
    {
        return Task.FromResult<string>(
            "Come on, Sarah. We've an appointment in London, " +
            "and we're already 30,000 years late.");
    }
}

Startup.cs:

services.AddScoped<IQuoteService, QuoteService>();

Pages/Index.cshtml.cs:

public class IndexModel : PageModel
{
    private readonly ApplicationDbContext _db;
    private readonly IQuoteService _quoteService;

    public IndexModel(ApplicationDbContext db, IQuoteService quoteService)
    {
        _db = db;
        _quoteService = quoteService;
    }

    [BindProperty]
    public Message Message { get; set; }

    public IList<Message> Messages { get; private set; }

    [TempData]
    public string MessageAnalysisResult { get; set; }

    public string Quote { get; private set; }

    public async Task OnGetAsync()
    {
        Messages = await _db.GetMessagesAsync();

        Quote = await _quoteService.GenerateQuote();
    }

Pages/Index.cs:

<input id="quote" type="hidden" value="@Model.Quote">

A SUT-alkalmazás futtatásakor a következő korrektúra jön létre:

<input id="quote" type="hidden" value="Come on, Sarah. We&#x27;ve an appointment in 
    London, and we&#x27;re already 30,000 years late.">

A szolgáltatás és az injektálás teszteléséhez integrációs tesztben a teszt egy ál-szolgáltatást injektál a SUT-ba. A modellszolgáltatás lecseréli az alkalmazás QuoteService-t a tesztalkalmazás által biztosított, TestQuoteServicenevű szolgáltatásra.

IntegrationTests.IndexPageTests.cs:

// Quote ©1975 BBC: The Doctor (Tom Baker); Pyramids of Mars
// https://www.bbc.co.uk/programmes/p00pys55
public class TestQuoteService : IQuoteService
{
    public Task<string> GenerateQuote()
    {
        return Task.FromResult<string>(
            "Something's interfering with time, Mr. Scarman, " +
            "and time is my business.");
    }
}

ConfigureTestServices van meghívva, és a hatókörön belüli szolgáltatás regisztrálva van:

[Fact]
public async Task Get_QuoteService_ProvidesQuoteInPage()
{
    // Arrange
    var client = _factory.WithWebHostBuilder(builder =>
        {
            builder.ConfigureTestServices(services =>
            {
                services.AddScoped<IQuoteService, TestQuoteService>();
            });
        })
        .CreateClient();

    //Act
    var defaultPage = await client.GetAsync("/");
    var content = await HtmlHelpers.GetDocumentAsync(defaultPage);
    var quoteElement = content.QuerySelector("#quote");

    // Assert
    Assert.Equal("Something's interfering with time, Mr. Scarman, " +
        "and time is my business.", quoteElement.Attributes["value"].Value);
}

A teszt végrehajtása során létrehozott jelölések a TestQuoteServiceáltal megadott idézőjelszöveget tükrözik, ezért az állítás sikeres.

<input id="quote" type="hidden" value="Something&#x27;s interfering with time, 
    Mr. Scarman, and time is my business.">

Szimulált hitelesítés

Az AuthTests osztály tesztjei ellenőrzik, hogy egy biztonságos végpont:

  • Átirányít egy nem hitelesített felhasználót az alkalmazás Bejelentkezési lapjára.
  • Egy hitelesített felhasználó tartalmát adja vissza.

A SUT-ban a /SecurePage lap a AuthorizePage konvenciót használja, hogy egy AuthorizeFilter-t alkalmazzon a lapra. További információkért lásd a Razor oldalak engedélyezési konvencióit.

services.AddRazorPages(options =>
{
    options.Conventions.AuthorizePage("/SecurePage");
});

A Get_SecurePageRedirectsAnUnauthenticatedUser tesztben egy WebApplicationFactoryClientOptions úgy van beállítva, hogy letiltsa az átirányításokat azáltal, hogy AllowAutoRedirect-t false-ra állítja.

[Fact]
public async Task Get_SecurePageRedirectsAnUnauthenticatedUser()
{
    // Arrange
    var client = _factory.CreateClient(
        new WebApplicationFactoryClientOptions
        {
            AllowAutoRedirect = false
        });

    // Act
    var response = await client.GetAsync("/SecurePage");

    // Assert
    Assert.Equal(HttpStatusCode.Redirect, response.StatusCode);
    Assert.StartsWith("http://localhost/Identity/Account/Login", 
        response.Headers.Location.OriginalString);
}

Az ügyfél átirányításának megakadályozásával a következő ellenőrzéseket hajthatja végre:

  • A SUT által visszaadott állapotkódot a várt HttpStatusCode.Redirect eredmény alapján lehet ellenőrizni, nem pedig a bejelentkezési lapra való átirányítás utáni végleges állapotkódot, amely HttpStatusCode.OK.
  • A válaszfejlécek Location fejlécértékét ellenőrizni kell, hogy az http://localhost/Identity/Account/Login-gyel kezdődik-e, nem pedig a bejelentkezési oldal végső válasza helyett, ahol a Location fejléc nem lenne jelen.

A tesztalkalmazás szimulálhat egy AuthenticationHandler<TOptions>ConfigureTestServices-ben a hitelesítés és az engedélyezés szempontjainak teszteléséhez. Egy minimális forgatókönyv AuthenticateResult.Success-t ad vissza.

public class TestAuthHandler : AuthenticationHandler<AuthenticationSchemeOptions>
{
    public TestAuthHandler(IOptionsMonitor<AuthenticationSchemeOptions> options, 
        ILoggerFactory logger, UrlEncoder encoder, ISystemClock clock)
        : base(options, logger, encoder, clock)
    {
    }

    protected override Task<AuthenticateResult> HandleAuthenticateAsync()
    {
        var claims = new[] { new Claim(ClaimTypes.Name, "Test user") };
        var identity = new ClaimsIdentity(claims, "Test");
        var principal = new ClaimsPrincipal(identity);
        var ticket = new AuthenticationTicket(principal, "Test");

        var result = AuthenticateResult.Success(ticket);

        return Task.FromResult(result);
    }
}

A TestAuthHandler egy felhasználó hitelesítésére hívják meg, amikor a hitelesítési séma Test van beállítva, ahol a AddAuthentication regisztrálva van a ConfigureTestServices-ra. Fontos, hogy a Test séma megfeleljen az alkalmazás által várt sémának. Ellenkező esetben a hitelesítés nem működik.

[Fact]
public async Task Get_SecurePageIsReturnedForAnAuthenticatedUser()
{
    // Arrange
    var client = _factory.WithWebHostBuilder(builder =>
        {
            builder.ConfigureTestServices(services =>
            {
                services.AddAuthentication("Test")
                    .AddScheme<AuthenticationSchemeOptions, TestAuthHandler>(
                        "Test", options => {});
            });
        })
        .CreateClient(new WebApplicationFactoryClientOptions
        {
            AllowAutoRedirect = false,
        });

    client.DefaultRequestHeaders.Authorization = 
        new AuthenticationHeaderValue("Test");

    //Act
    var response = await client.GetAsync("/SecurePage");

    // Assert
    Assert.Equal(HttpStatusCode.OK, response.StatusCode);
}

A WebApplicationFactoryClientOptionskapcsolatos további információkért lásd a Ügyfélbeállítások című szakaszt.

A környezet beállítása

Alapértelmezés szerint az SUT gazdagép- és alkalmazáskörnyezete a fejlesztési környezet használatára van konfigurálva. A SUT környezetének felülírása a IHostBuilderhasználatakor:

  • Állítsa be a ASPNETCORE_ENVIRONMENT környezeti változót (például Staging, Productionvagy más egyéni értéket, például Testing).
  • Felülbírálja a(z) CreateHostBuilder-t a tesztalkalmazásban a ASPNETCOREelőtagú környezeti változók olvasásához.
protected override IHostBuilder CreateHostBuilder() =>
    base.CreateHostBuilder()
        .ConfigureHostConfiguration(
            config => config.AddEnvironmentVariables("ASPNETCORE"));

Ha a SUT a webszervert (IWebHostBuilder) használja, felülírja a CreateWebHostBuilder-t:

protected override IWebHostBuilder CreateWebHostBuilder() =>
    base.CreateWebHostBuilder().UseEnvironment("Testing");

Hogyan következtet a tesztinfrastruktúra az alkalmazás tartalomgyökerének elérési útjára?

A WebApplicationFactory konstruktor meghatározza az alkalmazás tartalom gyökér elérési útját úgy, hogy a WebApplicationFactoryContentRootAttribute szerelvény TEntryPointkulccsal a System.Reflection.Assembly.FullName-t keres az integrációs teszteket tartalmazó szerelvényen. Ha nem található a megfelelő kulccsal rendelkező attribútum, WebApplicationFactory visszatér a megoldásfájl (.sln) kereséséhez, és hozzáadja a TEntryPoint szerelvény nevét a megoldáskönyvtárhoz. Az alkalmazás gyökérkönyvtára (a tartalom gyökérútvonala) a nézetek és tartalomfájlok felderítésére szolgál.

Árnyékmásolás letiltása

Az árnyékmásolás miatt a tesztek a kimeneti könyvtártól eltérő könyvtárban futnak. Ha a tesztek a fájlok Assembly.Location-hoz viszonyított betöltésére támaszkodnak, és problémákat tapasztalunk, előfordulhat, hogy le kell tiltani az árnyékmásolást.

Ha le szeretné tiltani az árnyékmásolást az xUnit használatakor, hozzon létre egy xunit.runner.json fájlt a tesztprojekt könyvtárában a megfelelő konfigurációs beállítással:

{
  "shadowCopy": false
}

Objektumok ártalmatlanítása

A IClassFixture implementáció tesztjeinek végrehajtása után TestServer és HttpClient megsemmisülnek, amikor az xUnit megsemmisíti a WebApplicationFactory-at. Ha a fejlesztő által példányosított objektumok ártalmatlanítást igényelnek, azokat a IClassFixture implementációban kell megsemmisíteni. További információért lásd: Dispose metódus implementálása.

Integrációs tesztek mintája

A mintaalkalmazás két alkalmazásból áll:

Alkalmazás Projektmappa Leírás
Üzenetküldő alkalmazás (a SUT) src/RazorPagesProject Lehetővé teszi a felhasználó számára, hogy hozzáadjon, töröljön egyet, törölje az összeset, és elemezze az üzeneteket.
Alkalmazás tesztelése tests/RazorPagesProject.Tests A SUT integrációs tesztelésére szolgál.

A tesztek egy IDE beépített tesztfunkcióival futtathatók, például Visual Studio. Ha Visual Studio Code vagy parancssort használ, hajtsa végre a következő parancsot egy parancssorban a tests/RazorPagesProject.Tests könyvtárban:

dotnet test

Üzenetküldő alkalmazás (SUT) szervezete

A SUT egy Razor Pages üzenetrendszer, amely a következő jellemzőkkel rendelkezik:

  • Az alkalmazás indexoldala (Pages/Index.cshtml és Pages/Index.cshtml.cs) felhasználói felületi és lapmodellezési módszereket biztosít az üzenetek hozzáadásának, törlésének és elemzésének szabályozásához (átlagos szavak üzenetenként).
  • Az üzeneteket a Message osztály (Data/Message.cs) írja le két tulajdonságokkal: Id (kulcs) és Text (üzenet). A Text tulajdonság megadása kötelező, és legfeljebb 200 karakter hosszúságú lehet.
  • Az üzenetek tárolása Entity Framework memórián belüli adatbázisának† használatával történik.
  • Az alkalmazás egy adatelérési réteget (DAL) tartalmaz az adatbázis környezeti osztályában, AppDbContext (Data/AppDbContext.cs).
  • Ha az adatbázis üres az alkalmazás indításakor, az üzenettároló három üzenettel lesz inicializálva.
  • Az alkalmazás tartalmaz egy /SecurePage, amelyet csak hitelesített felhasználó érhet el.

†Az EF-témakör, Az InMemorytesztelése című témakör bemutatja, hogyan használható memóriabeli adatbázis az MSTesttel végzett tesztekhez. Ez a témakör az xUnit tesztelési keretrendszert használja. A különböző tesztelési keretrendszerek tesztelési fogalmai és tesztelési implementációi hasonlóak, de nem azonosak.

Bár az alkalmazás nem használja az adattár mintát, és nem hatékony példa a Munkaegység (UoW) mintára, a Razor Pages támogatja ezeket a fejlesztési mintákat. További információ: Az infrastruktúra perzisztenciaréteg tervezése és Tesztvezérlő logika (a minta implementálja az adattár mintát).

Tesztalkalmazás szervezése

A tesztalkalmazás egy konzolalkalmazás a tests/RazorPagesProject.Tests könyvtárban.

Alkalmazáskönyvtár tesztelése Leírás
AuthTests A következő tesztelési módszereket tartalmazza:
  • Biztonságos lap elérése hitelesítés nélküli felhasználó által.
  • Biztonságos lap elérése egy hitelesített felhasználó által egy ál-AuthenticationHandler<TOptions>.
  • GitHub-felhasználói profil beszerzése és a profil felhasználói bejelentkezésének ellenőrzése.
BasicTests Az útválasztás és a tartalomtípus tesztelési módszerét tartalmazza.
IntegrationTests Az Index lap integrációs tesztjeit tartalmazza egyéni WebApplicationFactory osztály használatával.
Helpers/Utilities
  • Utilities.cs tartalmazza az adatbázis tesztelési adatokkal való üzembe helyezésekor használt InitializeDbForTests metódust.
  • HtmlHelpers.cs egy AngleSharp IHtmlDocument visszaadására szolgáló metódust biztosít a tesztelési módszerek számára.
  • HttpClientExtensions.cs túlterhelést biztosít a SendAsync számára, hogy kéréseket küldjön a SUT-nak.

A tesztelési keretrendszer xUnit. Az integrációs teszteket a Microsoft.AspNetCore.TestHostsegítségével hajtják végre, amely magában foglalja a TestServer-et. Mivel a Microsoft.AspNetCore.Mvc.Testing csomag a tesztgazda és a tesztkiszolgáló konfigurálására szolgál, a TestHost és TestServer csomagok nem igényelnek közvetlen csomaghivatkozásokat a tesztalkalmazás projektfájljában vagy fejlesztői konfigurációjában a tesztalkalmazásban.

Az integrációs tesztek általában kis adatkészletet igényelnek az adatbázisban a teszt végrehajtása előtt. A törlési teszt például egy adatbázisrekord törlését kéri, ezért az adatbázisnak legalább egy rekorddal kell rendelkeznie ahhoz, hogy a törlési kérelem sikeres legyen.

A mintaalkalmazás három üzenettel tölti fel az adatbázist a Utilities.cs-ben, amelyeket a tesztek futtatásakor használhatnak.

public static void InitializeDbForTests(ApplicationDbContext db)
{
    db.Messages.AddRange(GetSeedingMessages());
    db.SaveChanges();
}

public static void ReinitializeDbForTests(ApplicationDbContext db)
{
    db.Messages.RemoveRange(db.Messages);
    InitializeDbForTests(db);
}

public static List<Message> GetSeedingMessages()
{
    return new List<Message>()
    {
        new Message(){ Text = "TEST RECORD: You're standing on my scarf." },
        new Message(){ Text = "TEST RECORD: Would you like a jelly baby?" },
        new Message(){ Text = "TEST RECORD: To the rational mind, " +
            "nothing is inexplicable; only unexplained." }
    };
}

A SUT adatbázis-környezete a Startup.ConfigureServices metódusában van regisztrálva. A tesztalkalmazás builder.ConfigureServices visszahívása az alkalmazás lesz végrehajtva. Ha másik adatbázist szeretne használni a tesztekhez, az alkalmazás adatbázis-környezetét le kell cserélni a builder.ConfigureServices-ban. A további információkért lásd a WebApplicationFactory testreszabása szakaszt.

Azokban az esetekben, amikor a SUT-ok továbbra is a Web Host-ot használják, a tesztalkalmazás builder.ConfigureServices visszahívása még a SUT Startup.ConfigureServices kódja előtt kerül végrehajtásra. A tesztalkalmazás builder.ConfigureTestServices visszahívása után lesz végrehajtva.

További erőforrások

Ez a cikk feltételezi az egységtesztek alapszintű megértését. Ha nem ismeri a tesztelési fogalmakat, tekintse meg a .NET Core és a .NET Standard Egységtesztelés című cikket és annak csatolt tartalmát.

Mintakód megtekintése vagy letöltése (hogyan kell letölteni)

A mintaalkalmazás egy Razor Pages-alkalmazás, és feltételezi, hogy alapszintű ismeretekkel rendelkezik a Razor Pages-ről. Ha nem ismeri a Razor oldalak tartalmát, olvassa el a következő cikkeket.

Az SLA-kteszteléséhez olyan eszközt ajánlunk, mint például a .NET-Playwright, amely automatizálhatja a böngészőt.

Bevezetés az integrációs tesztekbe

Az integrációs tesztek az alkalmazás összetevőit szélesebb szinten értékelik ki, mint egységtesztek. Az egységtesztek izolált szoftverösszetevők, például az egyes osztálymódszerek tesztelésére szolgálnak. Az integrációs tesztek megerősítik, hogy két vagy több alkalmazásösszetevő együttműködik a várt eredmény érdekében, beleértve a kérések teljes feldolgozásához szükséges összes összetevőt is.

Ezek a szélesebb körű tesztek az alkalmazás infrastruktúrájának és teljes keretrendszerének tesztelésére szolgálnak, gyakran a következő összetevőket is beleértve:

  • Adatbázis
  • Fájlrendszer
  • Hálózati berendezések
  • Kérelem-válasz folyamat

Az egységtesztek az infrastruktúra-összetevők helyett létrehozott összetevőket használnak, más néven hamis vagy szimulált objektumokat.

Az egységtesztekkel ellentétben az integrációs tesztek:

  • Használja azokat a tényleges összetevőket, amelyeket az alkalmazás éles környezetben használ.
  • További kód- és adatfeldolgozást igényel.
  • A futtatás hosszabb időt vesz igénybe.

Ezért az integrációs tesztek használatának korlátozása a legfontosabb infrastruktúra-forgatókönyvekre. Ha egy viselkedést egységteszt vagy integrációs teszt használatával lehet tesztelni, válassza ki az egységtesztet.

Az integrációs tesztek megvitatása során a tesztelt projektet gyakran nevezik System Under Test, vagy röviden "SUT" néven. A cikk során a "SUT" a tesztelt ASP.NET Core-alkalmazásra hivatkozik.

Ne írjon integrációs teszteket az adatbázisok és fájlrendszerek minden permutációs adat- és fájlhozzáféréshez. Függetlenül attól, hogy az alkalmazások hány helyen használják az adatbázisokat és a fájlrendszereket, az olvasási, írási, frissítési és törlési integrációs tesztek koncentrált készlete általában képes megfelelően tesztelni az adatbázis- és fájlrendszer-összetevőket. Használjon egységteszteket az ezen összetevőkkel interakcióba lépő metóduslogika rutintesztjeihez. Az egységtesztekben az infrastruktúra hamis vagy hamisított használata gyorsabb tesztvégrehajtást eredményez.

ASP.NET Core integrációs tesztek

A ASP.NET Core integrációs tesztjeihez a következők szükségesek:

  • A tesztprojektek a teszteket tartalmazzák és hajtják végre. A tesztprojekt hivatkozással rendelkezik a SUT-ra.
  • A tesztprojekt létrehoz egy tesztwebhely-gazdagépet a SUT számára, és egy tesztkiszolgáló-ügyféllel kezeli a kérelmeket és a válaszokat a SUT-val.
  • A tesztfuttató a tesztek végrehajtására és a teszteredmények jelentésére szolgál.

Az integrációs tesztek olyan események sorozatát követik, amelyek magukban foglalják a szokásos Előkészület, Végrehajtásés Ellenőrzés tesztelési lépéseket.

  1. Az SUT webhoszt konfigurálva van.
  2. Létrejön egy tesztkiszolgáló-ügyfél, amely kéréseket küld az alkalmazásnak.
  3. A Arrange tesztelési lépés végrehajtása: A tesztalkalmazás előkészít egy kérést.
  4. A törvény tesztlépés végrehajtása: Az ügyfél elküldi a kérést, és megkapja a választ.
  5. A ellenőrzési tesztlépést végrehajtják: A tényleges választ a rendszer a várt válasz alapján érvényesíti, és érvényesnek nyilvánítja (), vagy sikertelennek ().
  6. A folyamat az összes teszt végrehajtásáig folytatódik.
  7. A teszteredmények jelentésre kerültek.

A teszt webgazda általában eltérő konfigurációval van beállítva, mint az alkalmazás normál webgazdája a tesztek futtatásához. Előfordulhat például, hogy a tesztekhez egy másik adatbázist vagy különböző alkalmazásbeállításokat használnak.

Az infrastruktúra-összetevőket, például a tesztweb-gazdagépet és a memóriabeli tesztkiszolgálót (TestServer) a Microsoft.AspNetCore.Mvc.Testing csomag biztosítja vagy felügyeli. A csomag használata leegyszerűsíti a tesztek létrehozását és végrehajtását.

A Microsoft.AspNetCore.Mvc.Testing csomag a következő feladatokat kezeli:

  • Másolja a függőségi fájlt (.deps) a SUT-ból a tesztprojekt bin könyvtárába.
  • A tartalomgyökerét-re a SUT projektgyökeréhez állítja, hogy a statikus fájlok és a lapok/nézetek megtalálhatók legyenek a tesztek végrehajtásakor.
  • A WebApplicationFactory osztály gördülékenyebbé teszi a SUT TestServer használatával történő rendszerindítását.

Az egységtesztek dokumentációja ismerteti a tesztprojektek és tesztfuttatók beállításának módját, valamint a tesztek futtatásának részletes utasításait, valamint a tesztek és tesztosztályok elnevezésére vonatkozó javaslatokat.

Egységtesztek elkülönítése az integrációs tesztektől különböző projektekre. A tesztek elkülönítése:

  • Segít biztosítani, hogy az infrastruktúra tesztelési összetevői véletlenül ne legyenek belefoglalva az egységtesztekbe.
  • Lehetővé teszi a tesztkészlet futtatásának szabályozását.

Gyakorlatilag nincs különbség a Razor Pages-alkalmazások és az MVC-alkalmazások tesztjeinek konfigurációja között. Az egyetlen különbség a tesztek elnevezésében van. Egy Razor Pages-alkalmazásban az oldalvégpontok tesztjeit általában az oldalmodell-osztályról nevezik el (például IndexPageTests az indexlap összetevőintegrációjának teszteléséhez). Az MVC-alkalmazásokban a tesztek általában vezérlőosztályok szerint vannak rendszerezve, és az általuk tesztelt vezérlőkről nevezik el őket (például HomeControllerTests a Home vezérlő összetevőintegrációjának teszteléséhez).

Alkalmazás előfeltételeinek tesztelése

A tesztprojektnek a következőnek kell lennie:

Ezek az előfeltételek láthatók a mintaalkalmazás-ban. Vizsgálja meg a tests/RazorPagesProject.Tests/RazorPagesProject.Tests.csproj fájlt. A mintaalkalmazás az xUnit tesztelési keretrendszert és a AngleSharp elemzőtárat használja, így a mintaalkalmazás a következőre is hivatkozik:

A xunit.runner.visualstudio 2.4.2-es vagy újabb verzióját használó alkalmazásokban a tesztprojektnek hivatkoznia kell a Microsoft.NET.Test.Sdk csomagra.

Az Entity Framework Core a tesztekben is használatos. Tekintse meg a projektfájlt a GitHub.

SUT-környezet

Ha a SUT környezet nincs beállítva, a környezet alapértelmezés szerint a Fejlesztési környezet.

Alapszintű tesztek az alapértelmezett WebApplicationFactory használatával

Tegye közzé az implicit módon definiált Program osztályt a tesztprojektben az alábbi műveletek egyikével:

  • Belső típusok kitétele a webalkalmazásból a tesztprojekt számára. Ez a SUT-projekt fájljában (.csproj) végezhető el:

    <ItemGroup>
         <InternalsVisibleTo Include="MyTestProject" />
    </ItemGroup>
    
  • Tegye nyilvánossá a Program osztályt egy részleges osztály deklarációval:

    var builder = WebApplication.CreateBuilder(args);
    // ... Configure services, routes, etc.
    app.Run();
    + public partial class Program { }
    

    A mintaalkalmazás a Program részleges osztály megközelítést használja.

A WebApplicationFactory<TEntryPoint>-t használjuk egy TestServer létrehozásához az integrációs tesztekhez. TEntryPoint a SUT belépési pontosztálya, általában Program.cs.

A tesztosztályok implementálnak egy osztály-fix technikai felületet (IClassFixture), amely jelzi, hogy az osztály teszteket tartalmaz, és megosztott objektumpéldányokat biztosít az osztály tesztjei során.

Az alábbi tesztosztály BasicTestsa WebApplicationFactory használatával végzi a SUT inicializálását, és biztosít egy HttpClient-t a Get_EndpointsReturnSuccessAndCorrectContentTypetesztmetódus számára. A metódus ellenőrzi, hogy a válasz állapotkódja sikeres-e (200-299), és a Content-Type fejléc több alkalmazásoldalon text/html; charset=utf-8.

CreateClient() létrehoz egy HttpClient-példányt, amely automatikusan követi az átirányításokat és kezeli a cookie-kat.

public class BasicTests 
    : IClassFixture<WebApplicationFactory<Program>>
{
    private readonly WebApplicationFactory<Program> _factory;

    public BasicTests(WebApplicationFactory<Program> factory)
    {
        _factory = factory;
    }

    [Theory]
    [InlineData("/")]
    [InlineData("/Index")]
    [InlineData("/About")]
    [InlineData("/Privacy")]
    [InlineData("/Contact")]
    public async Task Get_EndpointsReturnSuccessAndCorrectContentType(string url)
    {
        // Arrange
        var client = _factory.CreateClient();

        // Act
        var response = await client.GetAsync(url);

        // Assert
        response.EnsureSuccessStatusCode(); // Status Code 200-299
        Assert.Equal("text/html; charset=utf-8", 
            response.Content.Headers.ContentType.ToString());
    }
}

Alapértelmezés szerint a nem alapvető cookie-k nem maradnak meg a kérések között, ha engedélyezve van az általános adatvédelmi rendelet hozzájárulási szabályzata. A nem alapvető cookie-k( például a TempData-szolgáltató által használt) megőrzése érdekében jelölje meg őket alapvetőként a tesztek során. A cookie alapvetőként való megjelölésére vonatkozó utasításokért lásd: Alapvető cookie-k.

AngleSharp és Application Parts az antiforgery-ellenőrzésekhez

Ez a cikk a AngleSharp elemzőt használja az antiforgery-ellenőrzések kezelésére oldalak betöltése és a HTML elemzése révén. A vezérlő végpontjainak és a Razor oldalak nézeteinek részletesebb teszteléséhez, anélkül, hogy törődne azzal, hogyan jelennek meg a böngészőben, fontolja meg a Application Partshasználatát. Az Alkalmazásrészek megközelítés egy vezérlőt vagy Razor lapot injektál az alkalmazásba, amellyel JSON-kéréseket lehet küldeni a szükséges értékek lekéréséhez. További információért lásd a blogot: Integrációs tesztelés ASP.NET Core erőforrások Antiforgery-vel történő védelme az Alkalmazás Részei segítségével és a kapcsolódó GitHub-adattár szerzője: Martin Costello.

A WebApplicationFactory testreszabása

A webtárhely konfigurációja a tesztosztályoktól függetlenül hozható létre, úgy, hogy a WebApplicationFactory<TEntryPoint>-ból örökölt egy vagy több egyéni gyárat készítünk.

  1. Örököljön WebApplicationFactory és bírálja felül ConfigureWebHost. A IWebHostBuilder lehetővé teszi a szolgáltatásgyűjtemény konfigurálását IWebHostBuilder.ConfigureServices

    public class CustomWebApplicationFactory<TProgram>
        : WebApplicationFactory<TProgram> where TProgram : class
    {
        protected override void ConfigureWebHost(IWebHostBuilder builder)
        {
            builder.ConfigureServices(services =>
            {
                var dbContextDescriptor = services.SingleOrDefault(
                    d => d.ServiceType ==
                        typeof(DbContextOptions<ApplicationDbContext>));
    
                services.Remove(dbContextDescriptor);
    
                var dbConnectionDescriptor = services.SingleOrDefault(
                    d => d.ServiceType ==
                        typeof(DbConnection));
    
                services.Remove(dbConnectionDescriptor);
    
                // Create open SqliteConnection so EF won't automatically close it.
                services.AddSingleton<DbConnection>(container =>
                {
                    var connection = new SqliteConnection("DataSource=:memory:");
                    connection.Open();
    
                    return connection;
                });
    
                services.AddDbContext<ApplicationDbContext>((container, options) =>
                {
                    var connection = container.GetRequiredService<DbConnection>();
                    options.UseSqlite(connection);
                });
            });
    
            builder.UseEnvironment("Development");
        }
    }
    

    Az adatbázis-vetést a mintaalkalmazásban a InitializeDbForTests metódus végzi. A módszert az integrációs tesztek mintájában ismertetjük: Az alkalmazás szervezetének tesztelése szakasz.

    A SUT adatbázis-környezetét regisztrálták Program.cs-ben. A tesztalkalmazás builder.ConfigureServices visszahívása az alkalmazás lesz végrehajtva. Ha más adatbázist szeretne használni a tesztekhez, mint az alkalmazás adatbázisa, az alkalmazás adatbázis-környezetét a builder.ConfigureServicesadatbázis-környezetre kell cserélni.

    A mintaalkalmazás megkeresi az adatbázis-környezet szolgáltatásleíróját, és a leíró használatával eltávolítja a szolgáltatásregisztrációt. A gyár ezután hozzáad egy új ApplicationDbContext, amely memórián belüli adatbázist használ a tesztekhez.

    Ha másik adatbázishoz szeretne csatlakozni, módosítsa a DbConnection. SQL Server tesztadatbázis használata:

  1. Az egyéni CustomWebApplicationFactory-t használja a tesztosztályokban. Az alábbi példa a gyárat használja a IndexPageTests osztályban:

    public class IndexPageTests :
        IClassFixture<CustomWebApplicationFactory<Program>>
    {
        private readonly HttpClient _client;
        private readonly CustomWebApplicationFactory<Program>
            _factory;
    
        public IndexPageTests(
            CustomWebApplicationFactory<Program> factory)
        {
            _factory = factory;
            _client = factory.CreateClient(new WebApplicationFactoryClientOptions
            {
                AllowAutoRedirect = false
            });
        }
    

    A mintaalkalmazás ügyfele úgy van konfigurálva, hogy megakadályozza a HttpClient átirányítások követését. Amint azt a Mock authentication szakasz későbbi szakasza ismerteti, ez lehetővé teszi a tesztek számára az alkalmazás első válaszának eredményének ellenőrzését. Az első válasz ezek közül sok tesztben egy átirányítás egy "Location" fejléccel.

  2. Egy tipikus teszt a HttpClient és segédmetenek használatával dolgozza fel a kérést és a választ:

    [Fact]
    public async Task Post_DeleteAllMessagesHandler_ReturnsRedirectToRoot()
    {
        // Arrange
        var defaultPage = await _client.GetAsync("/");
        var content = await HtmlHelpers.GetDocumentAsync(defaultPage);
    
        //Act
        var response = await _client.SendAsync(
            (IHtmlFormElement)content.QuerySelector("form[id='messages']"),
            (IHtmlButtonElement)content.QuerySelector("button[id='deleteAllBtn']"));
    
        // Assert
        Assert.Equal(HttpStatusCode.OK, defaultPage.StatusCode);
        Assert.Equal(HttpStatusCode.Redirect, response.StatusCode);
        Assert.Equal("/", response.Headers.Location.OriginalString);
    }
    

A SUT-nak küldött POST-kéréseknek meg kell felelniük az alkalmazás adatvédelmi rendszerének automatikusan végrehajtott hamisítás elleni ellenőrzési folyamatának. A teszt POST-kérésének rendezéséhez a tesztalkalmazásnak a következőnek kell lennie:

  1. Kérjen egy kérelmet az oldalhoz.
  2. Elemezze az azonosítóhamisítás elleni cookie-t, és kérje le az érvényesítési jogkivonatot a választól.
  3. Végezze el a POST kérést az antiforgery cookie és az érvényesítési jogkivonattal.

A mintaalkalmazás SendAsync segédmetódusai (Helpers/HttpClientExtensions.cs) és a GetDocumentAsync segédmetódus (Helpers/HtmlHelpers.cs) az AngleSharp elemzővel kezelik az antiforgery-ellenőrzést az alábbi módszerekkel:

  • GetDocumentAsync: Megkapja a HttpResponseMessage, és visszaad egy IHtmlDocument. GetDocumentAsync egy olyan gyárat használ, amely az eredeti alapján készít elő egy HttpResponseMessage-t. További információért tekintse meg az AngleSharp dokumentációt.
  • SendAsync kiterjesztési metódusai a HttpClient összállítanak egy HttpRequestMessage-t, és meghívják a SendAsync(HttpRequestMessage)-t, hogy kérelmeket küldjenek a SUT-nak. A SendAsync túlterhelései elfogadják a HTML-űrlapot (IHtmlFormElement) és a következőket:
    • Az űrlap Küldés gombja (IHtmlElement)
    • Űrlapértékek gyűjtése (IEnumerable<KeyValuePair<string, string>>)
    • Küldés gomb (IHtmlElement) és űrlapértékek (IEnumerable<KeyValuePair<string, string>>)

AngleSharp egy harmadik féltől származó elemzési kódtár, amelyet bemutató célokra használnak, ebben a cikkben és a mintaalkalmazásban. Az AngleSharp nem támogatott vagy szükséges ASP.NET Core-alkalmazások integrációs teszteléséhez. Más elemzők is használhatók, például a Html Agility Pack (HAP). Egy másik módszer, ha olyan kódot írunk, amely közvetlenül kezeli az antiforgery rendszer kérés-ellenőrzési tokenjét és az antiforgery cookie-t. További információkért tekintse meg az antiforgery-ellenőrzésekről szóló cikket itt: AngleSharp vs Application Parts,.

A EF-Core memóriabeli adatbázis-szolgáltató korlátozott és alapszintű teszteléshez használható, de a SQLite-szolgáltató a memóriabeli teszteléshez ajánlott.

Lásd: Indítás kiterjesztése indítási szűrőkkel, amely bemutatja, hogyan konfigurálhat köztes szoftvereket IStartupFilterhasználatával, ami akkor hasznos, ha egy teszthez egyéni szolgáltatásra vagy köztes szoftverre van szükség.

Az ügyfél testreszabása a WithWebHostBuilder használatával

Ha egy tesztmetóduson belül további konfigurációra van szükség, WithWebHostBuilder létrehoz egy új WebApplicationFactory egy olyan IWebHostBuilder-vel, amelyet a konfiguráció további testreszab.

A mintakód meghívja WithWebHostBuilder, hogy a konfigurált szolgáltatásokat tesztcsomókkal cserélje le. További információért és a példák használatáért lásd a Modellszolgáltatások injektálása rész ebben a cikkben.

A mintaalkalmazás Post_DeleteMessageHandler_ReturnsRedirectToRoot tesztmetódusa a WithWebHostBuilderhasználatát mutatja be. Ez a teszt egy rekord törlését hajtja végre az adatbázisban egy űrlapbeküldés SUT-ban való aktiválásával.

Mivel a IndexPageTests osztály egy másik tesztje olyan műveletet hajt végre, amely törli az adatbázis összes rekordját, és a Post_DeleteMessageHandler_ReturnsRedirectToRoot metódus előtt futhat, az adatbázist ebben a tesztmetódusban újból el kell végezni, hogy a rendszer egy rekordot adjon meg a SUT számára a törléshez. A SUT-ban a messages űrlap első törlési gombjának kiválasztását a rendszer szimulálja a SUT-nak küldött kérésben:

[Fact]
public async Task Post_DeleteMessageHandler_ReturnsRedirectToRoot()
{
    // Arrange
    using (var scope = _factory.Services.CreateScope())
    {
        var scopedServices = scope.ServiceProvider;
        var db = scopedServices.GetRequiredService<ApplicationDbContext>();

        Utilities.ReinitializeDbForTests(db);
    }

    var defaultPage = await _client.GetAsync("/");
    var content = await HtmlHelpers.GetDocumentAsync(defaultPage);

    //Act
    var response = await _client.SendAsync(
        (IHtmlFormElement)content.QuerySelector("form[id='messages']"),
        (IHtmlButtonElement)content.QuerySelector("form[id='messages']")
            .QuerySelector("div[class='panel-body']")
            .QuerySelector("button"));

    // Assert
    Assert.Equal(HttpStatusCode.OK, defaultPage.StatusCode);
    Assert.Equal(HttpStatusCode.Redirect, response.StatusCode);
    Assert.Equal("/", response.Headers.Location.OriginalString);
}

Ügyfélbeállítások

A WebApplicationFactoryClientOptions példányok létrehozásakor az alapértelmezett beállításokat és az elérhető beállításokat a HttpClient lapon tekinti meg.

Hozza létre a WebApplicationFactoryClientOptions osztályt, és adja át a CreateClient() metódusnak:

public class IndexPageTests :
    IClassFixture<CustomWebApplicationFactory<Program>>
{
    private readonly HttpClient _client;
    private readonly CustomWebApplicationFactory<Program>
        _factory;

    public IndexPageTests(
        CustomWebApplicationFactory<Program> factory)
    {
        _factory = factory;
        _client = factory.CreateClient(new WebApplicationFactoryClientOptions
        {
            AllowAutoRedirect = false
        });
    }

MEGJEGYZÉS: A HTTPS-átirányítási figyelmeztetések elkerülése érdekében, amikor a HTTPS-átirányító köztes réteget használja, állítsa be a BaseAddress = new Uri("https://localhost")

Makettszolgáltatások injektálása

A szolgáltatások felülírhatók egy tesztben a ConfigureTestServices hívásával a gazdagép építőn. A felülbírált szolgáltatások konkrét teszt hatóköréhez a WithWebHostBuilder metódus használható egy hostkészítő lekérésére. Ez a következő tesztekben látható:

A minta SUT tartalmaz egy hatókörű szolgáltatást, amely egy idézetet ad vissza. Az indexlap kérésekor az idézet az Index lap rejtett mezőjébe van beágyazva.

Services/IQuoteService.cs:

public interface IQuoteService
{
    Task<string> GenerateQuote();
}

Services/QuoteService.cs:

// Quote ©1975 BBC: The Doctor (Tom Baker); Dr. Who: Planet of Evil
// https://www.bbc.co.uk/programmes/p00pyrx6
public class QuoteService : IQuoteService
{
    public Task<string> GenerateQuote()
    {
        return Task.FromResult<string>(
            "Come on, Sarah. We've an appointment in London, " +
            "and we're already 30,000 years late.");
    }
}

Program.cs:

services.AddScoped<IQuoteService, QuoteService>();

Pages/Index.cshtml.cs:

public class IndexModel : PageModel
{
    private readonly ApplicationDbContext _db;
    private readonly IQuoteService _quoteService;

    public IndexModel(ApplicationDbContext db, IQuoteService quoteService)
    {
        _db = db;
        _quoteService = quoteService;
    }

    [BindProperty]
    public Message Message { get; set; }

    public IList<Message> Messages { get; private set; }

    [TempData]
    public string MessageAnalysisResult { get; set; }

    public string Quote { get; private set; }

    public async Task OnGetAsync()
    {
        Messages = await _db.GetMessagesAsync();

        Quote = await _quoteService.GenerateQuote();
    }

Pages/Index.cs:

<input id="quote" type="hidden" value="@Model.Quote">

A SUT-alkalmazás futtatásakor a következő korrektúra jön létre:

<input id="quote" type="hidden" value="Come on, Sarah. We&#x27;ve an appointment in 
    London, and we&#x27;re already 30,000 years late.">

A szolgáltatás és az injektálás teszteléséhez integrációs tesztben a teszt egy ál-szolgáltatást injektál a SUT-ba. A modellszolgáltatás lecseréli az alkalmazás QuoteService-t a tesztalkalmazás által biztosított, TestQuoteServicenevű szolgáltatásra.

IntegrationTests.IndexPageTests.cs:

// Quote ©1975 BBC: The Doctor (Tom Baker); Pyramids of Mars
// https://www.bbc.co.uk/programmes/p00pys55
public class TestQuoteService : IQuoteService
{
    public Task<string> GenerateQuote()
    {
        return Task.FromResult(
            "Something's interfering with time, Mr. Scarman, " +
            "and time is my business.");
    }
}

ConfigureTestServices van meghívva, és a hatókörön belüli szolgáltatás regisztrálva van:

[Fact]
public async Task Get_QuoteService_ProvidesQuoteInPage()
{
    // Arrange
    var client = _factory.WithWebHostBuilder(builder =>
        {
            builder.ConfigureTestServices(services =>
            {
                services.AddScoped<IQuoteService, TestQuoteService>();
            });
        })
        .CreateClient();

    //Act
    var defaultPage = await client.GetAsync("/");
    var content = await HtmlHelpers.GetDocumentAsync(defaultPage);
    var quoteElement = content.QuerySelector("#quote");

    // Assert
    Assert.Equal("Something's interfering with time, Mr. Scarman, " +
        "and time is my business.", quoteElement.Attributes["value"].Value);
}

A teszt végrehajtása során létrehozott jelölések a TestQuoteServiceáltal megadott idézőjelszöveget tükrözik, ezért az állítás sikeres.

<input id="quote" type="hidden" value="Something&#x27;s interfering with time, 
    Mr. Scarman, and time is my business.">

Szimulált hitelesítés

Az AuthTests osztály tesztjei ellenőrzik, hogy egy biztonságos végpont:

  • Átirányít egy nem hitelesített felhasználót az alkalmazás bejelentkezési oldalára.
  • Egy hitelesített felhasználó tartalmát adja vissza.

A SUT-ban a /SecurePage lap a AuthorizePage konvenciót használja, hogy egy AuthorizeFilter-t alkalmazzon a lapra. További információkért lásd a Razor oldalak engedélyezési konvencióit.

services.AddRazorPages(options =>
{
    options.Conventions.AuthorizePage("/SecurePage");
});

A Get_SecurePageRedirectsAnUnauthenticatedUser tesztben egy WebApplicationFactoryClientOptions úgy van beállítva, hogy letiltsa az átirányításokat azáltal, hogy AllowAutoRedirect-t false-ra állítja.

[Fact]
public async Task Get_SecurePageRedirectsAnUnauthenticatedUser()
{
    // Arrange
    var client = _factory.CreateClient(
        new WebApplicationFactoryClientOptions
        {
            AllowAutoRedirect = false
        });

    // Act
    var response = await client.GetAsync("/SecurePage");

    // Assert
    Assert.Equal(HttpStatusCode.Redirect, response.StatusCode);
    Assert.StartsWith("http://localhost/Identity/Account/Login",
        response.Headers.Location.OriginalString);
}

Az ügyfél átirányításának megakadályozásával a következő ellenőrzéseket hajthatja végre:

  • A SUT által visszaadott állapotkód a várt HttpStatusCode.Redirect eredményen ellenőrizhető, nem pedig a bejelentkezési lapra való átirányítás utáni végleges állapotkóddal, amely HttpStatusCode.OK.
  • A válaszfejlécek közül a Location fejléc értékét ellenőrzik annak megállapítására, hogy http://localhost/Identity/Account/Login-el kezdődik-e, nem pedig a végleges bejelentkezési oldal válaszát, ahol a Location fejléc nem lenne jelen.

A tesztalkalmazás szimulálhat egy AuthenticationHandler<TOptions>ConfigureTestServices-ben a hitelesítés és az engedélyezés szempontjainak teszteléséhez. Egy minimális forgatókönyv AuthenticateResult.Success-t ad vissza.

public class TestAuthHandler : AuthenticationHandler<AuthenticationSchemeOptions>
{
    public TestAuthHandler(IOptionsMonitor<AuthenticationSchemeOptions> options,
        ILoggerFactory logger, UrlEncoder encoder, ISystemClock clock)
        : base(options, logger, encoder, clock)
    {
    }

    protected override Task<AuthenticateResult> HandleAuthenticateAsync()
    {
        var claims = new[] { new Claim(ClaimTypes.Name, "Test user") };
        var identity = new ClaimsIdentity(claims, "Test");
        var principal = new ClaimsPrincipal(identity);
        var ticket = new AuthenticationTicket(principal, "TestScheme");

        var result = AuthenticateResult.Success(ticket);

        return Task.FromResult(result);
    }
}

A TestAuthHandler egy felhasználó hitelesítésére hívják meg, amikor a hitelesítési séma TestScheme van beállítva, ahol a AddAuthentication regisztrálva van a ConfigureTestServices-ra. Fontos, hogy a TestScheme séma megfeleljen az alkalmazás által várt sémának. Ellenkező esetben a hitelesítés nem működik.

[Fact]
public async Task Get_SecurePageIsReturnedForAnAuthenticatedUser()
{
    // Arrange
    var client = _factory.WithWebHostBuilder(builder =>
        {
            builder.ConfigureTestServices(services =>
            {
                services.AddAuthentication(defaultScheme: "TestScheme")
                    .AddScheme<AuthenticationSchemeOptions, TestAuthHandler>(
                        "TestScheme", options => { });
            });
        })
        .CreateClient(new WebApplicationFactoryClientOptions
        {
            AllowAutoRedirect = false,
        });

    client.DefaultRequestHeaders.Authorization =
        new AuthenticationHeaderValue(scheme: "TestScheme");

    //Act
    var response = await client.GetAsync("/SecurePage");

    // Assert
    Assert.Equal(HttpStatusCode.OK, response.StatusCode);
}

A WebApplicationFactoryClientOptionskapcsolatos további információkért lásd a Ügyfélbeállítások című szakaszt.

A köztes szoftver hitelesítésének alapszintű tesztjei

A Köztes szoftver hitelesítésének alapszintű teszteléséhez tekintse meg ezt a GitHub-adattárat. A tesztforgatókönyvre jellemző tesztkiszolgálót tartalmaz.

A környezet beállítása

Állítsa be a környezetet az egyéni alkalmazás-előállítóban:

public class CustomWebApplicationFactory<TProgram>
    : WebApplicationFactory<TProgram> where TProgram : class
{
    protected override void ConfigureWebHost(IWebHostBuilder builder)
    {
        builder.ConfigureServices(services =>
        {
            var dbContextDescriptor = services.SingleOrDefault(
                d => d.ServiceType ==
                    typeof(DbContextOptions<ApplicationDbContext>));

            services.Remove(dbContextDescriptor);

            var dbConnectionDescriptor = services.SingleOrDefault(
                d => d.ServiceType ==
                    typeof(DbConnection));

            services.Remove(dbConnectionDescriptor);

            // Create open SqliteConnection so EF won't automatically close it.
            services.AddSingleton<DbConnection>(container =>
            {
                var connection = new SqliteConnection("DataSource=:memory:");
                connection.Open();

                return connection;
            });

            services.AddDbContext<ApplicationDbContext>((container, options) =>
            {
                var connection = container.GetRequiredService<DbConnection>();
                options.UseSqlite(connection);
            });
        });

        builder.UseEnvironment("Development");
    }
}

Hogyan következtet a tesztinfrastruktúra az alkalmazás tartalomgyökerének elérési útjára?

A WebApplicationFactory konstruktor meghatározza az alkalmazás tartalom gyökér elérési útját úgy, hogy a WebApplicationFactoryContentRootAttribute szerelvény TEntryPointkulccsal a System.Reflection.Assembly.FullName-t keres az integrációs teszteket tartalmazó szerelvényen. Ha nem található a megfelelő kulccsal rendelkező attribútum, WebApplicationFactory visszatér a megoldásfájl (.sln) kereséséhez, és hozzáadja a TEntryPoint szerelvény nevét a megoldáskönyvtárhoz. Az alkalmazás gyökérkönyvtára (a tartalom gyökérútvonala) a nézetek és tartalomfájlok felderítésére szolgál.

Árnyékmásolás letiltása

Az árnyékmásolás miatt a tesztek a kimeneti könyvtártól eltérő könyvtárban futnak. Ha a tesztek a fájlok Assembly.Location-hoz viszonyított betöltésére támaszkodnak, és problémákat tapasztalunk, előfordulhat, hogy le kell tiltani az árnyékmásolást.

Ha le szeretné tiltani az árnyékmásolást az xUnit használatakor, hozzon létre egy xunit.runner.json fájlt a tesztprojekt könyvtárában a megfelelő konfigurációs beállítással:

{
  "shadowCopy": false
}

Objektumok ártalmatlanítása

A IClassFixture implementáció tesztjeinek végrehajtása után TestServer és HttpClient megsemmisülnek, amikor az xUnit megsemmisíti a WebApplicationFactory-at. Ha a fejlesztő által példányosított objektumok ártalmatlanítást igényelnek, azokat a IClassFixture implementációban kell megsemmisíteni. További információért lásd: Dispose metódus implementálása.

Integrációs tesztek mintája

A mintaalkalmazás két alkalmazásból áll:

Alkalmazás Projektmappa Leírás
Üzenetküldő alkalmazás (a SUT) src/RazorPagesProject Lehetővé teszi a felhasználó számára, hogy hozzáadjon, töröljön egyet, törölje az összeset, és elemezze az üzeneteket.
Alkalmazás tesztelése tests/RazorPagesProject.Tests A SUT integrációs tesztelésére szolgál.

A tesztek egy IDE beépített tesztfunkcióival futtathatók, például Visual Studio. Ha Visual Studio Code vagy parancssort használ, hajtsa végre a következő parancsot egy parancssorban a tests/RazorPagesProject.Tests könyvtárban:

dotnet test

Üzenetküldő alkalmazás (SUT) szervezete

A SUT egy Razor Pages üzenetrendszer, amely a következő jellemzőkkel rendelkezik:

  • Az alkalmazás indexoldala (Pages/Index.cshtml és Pages/Index.cshtml.cs) felhasználói felületi és lapmodellezési módszereket biztosít az üzenetek hozzáadásának, törlésének és elemzésének szabályozásához (átlagos szavak üzenetenként).
  • Az üzeneteket a Message osztály (Data/Message.cs) írja le két tulajdonságokkal: Id (kulcs) és Text (üzenet). A Text tulajdonság megadása kötelező, és legfeljebb 200 karakter hosszúságú lehet.
  • Az üzenetek tárolása Entity Framework memórián belüli adatbázisának† használatával történik.
  • Az alkalmazás egy adatelérési réteget (DAL) tartalmaz az adatbázis környezeti osztályában, AppDbContext (Data/AppDbContext.cs).
  • Ha az adatbázis üres az alkalmazás indításakor, az üzenettároló három üzenettel lesz inicializálva.
  • Az alkalmazás tartalmaz egy /SecurePage, amelyet csak hitelesített felhasználó érhet el.

†Az EF-cikk, Az InMemorytesztelése című cikk bemutatja, hogyan használható memóriabeli adatbázis az MSTesttel végzett tesztekhez. Ez a témakör az xUnit tesztelési keretrendszert használja. A különböző tesztelési keretrendszerek tesztelési fogalmai és tesztelési implementációi hasonlóak, de nem azonosak.

Bár az alkalmazás nem használja az adattár mintát, és nem hatékony példa a Munkaegység (UoW) mintára, a Razor Pages támogatja ezeket a fejlesztési mintákat. További információ: Az infrastruktúra perzisztenciaréteg tervezése és Tesztvezérlő logika (a minta implementálja az adattár mintát).

Tesztalkalmazás szervezése

A tesztalkalmazás egy konzolalkalmazás a tests/RazorPagesProject.Tests könyvtárban.

Alkalmazáskönyvtár tesztelése Leírás
AuthTests A következő tesztelési módszereket tartalmazza:
  • Biztonságos lap elérése hitelesítés nélküli felhasználó által.
  • Biztonságos lap elérése egy hitelesített felhasználó által egy ál-AuthenticationHandler<TOptions>.
  • GitHub-felhasználói profil beszerzése és a profil felhasználói bejelentkezésének ellenőrzése.
BasicTests Az útválasztás és a tartalomtípus tesztelési módszerét tartalmazza.
IntegrationTests Az Index lap integrációs tesztjeit tartalmazza egyéni WebApplicationFactory osztály használatával.
Helpers/Utilities
  • Utilities.cs tartalmazza az adatbázis tesztelési adatokkal való üzembe helyezésekor használt InitializeDbForTests metódust.
  • HtmlHelpers.cs egy AngleSharp IHtmlDocument visszaadására szolgáló metódust biztosít a tesztelési módszerek számára.
  • HttpClientExtensions.cs túlterhelést biztosít a SendAsync számára, hogy kéréseket küldjön a SUT-nak.

A tesztelési keretrendszer xUnit. Az integrációs teszteket a Microsoft.AspNetCore.TestHostsegítségével hajtják végre, amely magában foglalja a TestServer-et. Mivel a Microsoft.AspNetCore.Mvc.Testing csomag a tesztgazda és a tesztkiszolgáló konfigurálására szolgál, a TestHost és TestServer csomagok nem igényelnek közvetlen csomaghivatkozásokat a tesztalkalmazás projektfájljában vagy fejlesztői konfigurációjában a tesztalkalmazásban.

Az integrációs tesztek általában kis adatkészletet igényelnek az adatbázisban a teszt végrehajtása előtt. A törlési teszt például egy adatbázisrekord törlését kéri, ezért az adatbázisnak legalább egy rekorddal kell rendelkeznie ahhoz, hogy a törlési kérelem sikeres legyen.

A mintaalkalmazás három üzenettel tölti fel az adatbázist a Utilities.cs-ben, amelyeket a tesztek futtatásakor használhatnak.

public static void InitializeDbForTests(ApplicationDbContext db)
{
    db.Messages.AddRange(GetSeedingMessages());
    db.SaveChanges();
}

public static void ReinitializeDbForTests(ApplicationDbContext db)
{
    db.Messages.RemoveRange(db.Messages);
    InitializeDbForTests(db);
}

public static List<Message> GetSeedingMessages()
{
    return new List<Message>()
    {
        new Message(){ Text = "TEST RECORD: You're standing on my scarf." },
        new Message(){ Text = "TEST RECORD: Would you like a jelly baby?" },
        new Message(){ Text = "TEST RECORD: To the rational mind, " +
            "nothing is inexplicable; only unexplained." }
    };
}

A SUT adatbázis-környezetét regisztrálták Program.cs-ben. A tesztalkalmazás builder.ConfigureServices visszahívása az alkalmazás lesz végrehajtva. Ha másik adatbázist szeretne használni a tesztekhez, az alkalmazás adatbázis-környezetét le kell cserélni a builder.ConfigureServices-ban. A további információkért lásd a WebApplicationFactory testreszabása szakaszt.

További erőforrások

Ez a cikk feltételezi az egységtesztek alapszintű megértését. Ha nem ismeri a tesztelési fogalmakat, tekintse meg a .NET Core és a .NET Standard Egységtesztelés című cikket és annak csatolt tartalmát.

Mintakód megtekintése vagy letöltése (hogyan kell letölteni)

A mintaalkalmazás egy Razor Pages-alkalmazás, és feltételezi, hogy alapszintű ismeretekkel rendelkezik a Razor Pages-ről. Ha nem ismeri a Razor oldalak tartalmát, olvassa el a következő cikkeket.

Az SLA-kteszteléséhez olyan eszközt ajánlunk, mint például a .NET-Playwright, amely automatizálhatja a böngészőt.

Bevezetés az integrációs tesztekbe

Az integrációs tesztek az alkalmazás összetevőit szélesebb szinten értékelik ki, mint egységtesztek. Az egységtesztek izolált szoftverösszetevők, például az egyes osztálymódszerek tesztelésére szolgálnak. Az integrációs tesztek megerősítik, hogy két vagy több alkalmazásösszetevő együttműködik a várt eredmény érdekében, beleértve a kérések teljes feldolgozásához szükséges összes összetevőt is.

Ezek a szélesebb körű tesztek az alkalmazás infrastruktúrájának és teljes keretrendszerének tesztelésére szolgálnak, gyakran a következő összetevőket is beleértve:

  • Adatbázis
  • Fájlrendszer
  • Hálózati berendezések
  • Kérelem-válasz folyamat

Az egységtesztek az infrastruktúra-összetevők helyett létrehozott összetevőket használnak, más néven hamis vagy szimulált objektumokat.

Az egységtesztekkel ellentétben az integrációs tesztek:

  • Használja azokat a tényleges összetevőket, amelyeket az alkalmazás éles környezetben használ.
  • További kód- és adatfeldolgozást igényel.
  • A futtatás hosszabb időt vesz igénybe.

Ezért az integrációs tesztek használatának korlátozása a legfontosabb infrastruktúra-forgatókönyvekre. Ha egy viselkedést egységteszt vagy integrációs teszt használatával lehet tesztelni, válassza ki az egységtesztet.

Az integrációs tesztek megvitatása során a tesztelt projektet gyakran nevezik System Under Test, vagy röviden "SUT" néven. A cikk során a "SUT" a tesztelt ASP.NET Core-alkalmazásra hivatkozik.

Ne írjon integrációs teszteket az adatbázisok és fájlrendszerek minden permutációs adat- és fájlhozzáféréshez. Függetlenül attól, hogy az alkalmazások hány helyen használják az adatbázisokat és a fájlrendszereket, az olvasási, írási, frissítési és törlési integrációs tesztek koncentrált készlete általában képes megfelelően tesztelni az adatbázis- és fájlrendszer-összetevőket. Használjon egységteszteket az ezen összetevőkkel interakcióba lépő metóduslogika rutintesztjeihez. Az egységtesztekben az infrastruktúra hamis vagy hamisított használata gyorsabb tesztvégrehajtást eredményez.

ASP.NET Core integrációs tesztek

A ASP.NET Core integrációs tesztjeihez a következők szükségesek:

  • A tesztprojektek a teszteket tartalmazzák és hajtják végre. A tesztprojekt hivatkozással rendelkezik a SUT-ra.
  • A tesztprojekt létrehoz egy tesztwebhely-gazdagépet a SUT számára, és egy tesztkiszolgáló-ügyféllel kezeli a kérelmeket és a válaszokat a SUT-val.
  • A tesztfuttató a tesztek végrehajtására és a teszteredmények jelentésére szolgál.

Az integrációs tesztek olyan események sorozatát követik, amelyek magukban foglalják a szokásos Előkészület, Végrehajtásés Ellenőrzés tesztelési lépéseket.

  1. Az SUT webhoszt konfigurálva van.
  2. Létrejön egy tesztkiszolgáló-ügyfél, amely kéréseket küld az alkalmazásnak.
  3. A Arrange tesztelési lépés végrehajtása: A tesztalkalmazás előkészít egy kérést.
  4. A törvény tesztlépés végrehajtása: Az ügyfél elküldi a kérést, és megkapja a választ.
  5. A ellenőrzési tesztlépést végrehajtják: A tényleges választ a rendszer a várt válasz alapján érvényesíti, és érvényesnek nyilvánítja (), vagy sikertelennek ().
  6. A folyamat az összes teszt végrehajtásáig folytatódik.
  7. A teszteredmények jelentésre kerültek.

A teszt webgazda általában eltérő konfigurációval van beállítva, mint az alkalmazás normál webgazdája a tesztek futtatásához. Előfordulhat például, hogy a tesztekhez egy másik adatbázist vagy különböző alkalmazásbeállításokat használnak.

Az infrastruktúra-összetevőket, például a tesztweb-gazdagépet és a memóriabeli tesztkiszolgálót (TestServer) a Microsoft.AspNetCore.Mvc.Testing csomag biztosítja vagy felügyeli. A csomag használata leegyszerűsíti a tesztek létrehozását és végrehajtását.

A Microsoft.AspNetCore.Mvc.Testing csomag a következő feladatokat kezeli:

  • Másolja a függőségi fájlt (.deps) a SUT-ból a tesztprojekt bin könyvtárába.
  • A tartalomgyökerét-re a SUT projektgyökeréhez állítja, hogy a statikus fájlok és a lapok/nézetek megtalálhatók legyenek a tesztek végrehajtásakor.
  • A WebApplicationFactory osztály gördülékenyebbé teszi a SUT TestServer használatával történő rendszerindítását.

Az egységtesztek dokumentációja ismerteti a tesztprojektek és tesztfuttatók beállításának módját, valamint a tesztek futtatásának részletes utasításait, valamint a tesztek és tesztosztályok elnevezésére vonatkozó javaslatokat.

Egységtesztek elkülönítése az integrációs tesztektől különböző projektekre. A tesztek elkülönítése:

  • Segít biztosítani, hogy az infrastruktúra tesztelési összetevői véletlenül ne legyenek belefoglalva az egységtesztekbe.
  • Lehetővé teszi a tesztkészlet futtatásának szabályozását.

Gyakorlatilag nincs különbség a Razor Pages-alkalmazások és az MVC-alkalmazások tesztjeinek konfigurációja között. Az egyetlen különbség a tesztek elnevezésében van. Egy Razor Pages-alkalmazásban az oldalvégpontok tesztjeit általában az oldalmodell-osztályról nevezik el (például IndexPageTests az indexlap összetevőintegrációjának teszteléséhez). Az MVC-alkalmazásokban a tesztek általában vezérlőosztályok szerint vannak rendszerezve, és az általuk tesztelt vezérlőkről nevezik el őket (például HomeControllerTests a Home vezérlő összetevőintegrációjának teszteléséhez).

Alkalmazás előfeltételeinek tesztelése

A tesztprojektnek a következőnek kell lennie:

Ezek az előfeltételek láthatók a mintaalkalmazás-ban. Vizsgálja meg a tests/RazorPagesProject.Tests/RazorPagesProject.Tests.csproj fájlt. A mintaalkalmazás az xUnit tesztelési keretrendszert és a AngleSharp elemzőtárat használja, így a mintaalkalmazás a következőre is hivatkozik:

A xunit.runner.visualstudio 2.4.2-es vagy újabb verzióját használó alkalmazásokban a tesztprojektnek hivatkoznia kell a Microsoft.NET.Test.Sdk csomagra.

Az Entity Framework Core a tesztekben is használatos. Tekintse meg a projektfájlt a GitHub.

SUT-környezet

Ha a SUT környezet nincs beállítva, a környezet alapértelmezés szerint a Fejlesztési környezet.

Alapszintű tesztek az alapértelmezett WebApplicationFactory használatával

Tegye közzé az implicit módon definiált Program osztályt a tesztprojektben az alábbi műveletek egyikével:

  • Belső típusok kitétele a webalkalmazásból a tesztprojekt számára. Ez a SUT-projekt fájljában (.csproj) végezhető el:

    <ItemGroup>
         <InternalsVisibleTo Include="MyTestProject" />
    </ItemGroup>
    
  • Tegye nyilvánossá a Program osztályt egy részleges osztály deklarációval:

    var builder = WebApplication.CreateBuilder(args);
    // ... Configure services, routes, etc.
    app.Run();
    + public partial class Program { }
    

    A mintaalkalmazás a Program részleges osztály megközelítést használja.

A WebApplicationFactory<TEntryPoint>-t használjuk egy TestServer létrehozásához az integrációs tesztekhez. TEntryPoint a SUT belépési pontosztálya, általában Program.cs.

A tesztosztályok implementálnak egy osztály-fix technikai felületet (IClassFixture), amely jelzi, hogy az osztály teszteket tartalmaz, és megosztott objektumpéldányokat biztosít az osztály tesztjei során.

Az alábbi tesztosztály BasicTestsa WebApplicationFactory használatával végzi a SUT inicializálását, és biztosít egy HttpClient-t a Get_EndpointsReturnSuccessAndCorrectContentTypetesztmetódus számára. A metódus ellenőrzi, hogy a válasz állapotkódja sikeres-e (200-299), és a Content-Type fejléc több alkalmazásoldalon text/html; charset=utf-8.

CreateClient() létrehoz egy HttpClient-példányt, amely automatikusan követi az átirányításokat és kezeli a cookie-kat.

public class BasicTests 
    : IClassFixture<WebApplicationFactory<Program>>
{
    private readonly WebApplicationFactory<Program> _factory;

    public BasicTests(WebApplicationFactory<Program> factory)
    {
        _factory = factory;
    }

    [Theory]
    [InlineData("/")]
    [InlineData("/Index")]
    [InlineData("/About")]
    [InlineData("/Privacy")]
    [InlineData("/Contact")]
    public async Task Get_EndpointsReturnSuccessAndCorrectContentType(string url)
    {
        // Arrange
        var client = _factory.CreateClient();

        // Act
        var response = await client.GetAsync(url);

        // Assert
        response.EnsureSuccessStatusCode(); // Status Code 200-299
        Assert.Equal("text/html; charset=utf-8", 
            response.Content.Headers.ContentType.ToString());
    }
}

Alapértelmezés szerint a nem alapvető cookie-k nem maradnak meg a kérések között, ha engedélyezve van az általános adatvédelmi rendelet hozzájárulási szabályzata. A nem alapvető cookie-k( például a TempData-szolgáltató által használt) megőrzése érdekében jelölje meg őket alapvetőként a tesztek során. A cookie alapvetőként való megjelölésére vonatkozó utasításokért lásd: Alapvető cookie-k.

AngleSharp és Application Parts az antiforgery-ellenőrzésekhez

Ez a cikk a AngleSharp elemzőt használja az antiforgery-ellenőrzések kezelésére oldalak betöltése és a HTML elemzése révén. A vezérlő végpontjainak és a Razor oldalak nézeteinek részletesebb teszteléséhez, anélkül, hogy törődne azzal, hogyan jelennek meg a böngészőben, fontolja meg a Application Partshasználatát. Az Alkalmazásrészek megközelítés egy vezérlőt vagy Razor lapot injektál az alkalmazásba, amellyel JSON-kéréseket lehet küldeni a szükséges értékek lekéréséhez. További információért lásd a blogot: Integrációs tesztelés ASP.NET Core erőforrások Antiforgery-vel történő védelme az Alkalmazás Részei segítségével és a kapcsolódó GitHub-adattár szerzője: Martin Costello.

A WebApplicationFactory testreszabása

A webtárhely konfigurációja a tesztosztályoktól függetlenül hozható létre, úgy, hogy a WebApplicationFactory<TEntryPoint>-ból örökölt egy vagy több egyéni gyárat készítünk.

  1. Örököljön WebApplicationFactory és bírálja felül ConfigureWebHost. A IWebHostBuilder lehetővé teszi a szolgáltatásgyűjtemény konfigurálását IWebHostBuilder.ConfigureServices

    public class CustomWebApplicationFactory<TProgram>
        : WebApplicationFactory<TProgram> where TProgram : class
    {
        protected override void ConfigureWebHost(IWebHostBuilder builder)
        {
            builder.ConfigureServices(services =>
            {
                var dbContextDescriptor = services.SingleOrDefault(
                    d => d.ServiceType ==
                        typeof(DbContextOptions<ApplicationDbContext>));
    
                services.Remove(dbContextDescriptor);
    
                var dbConnectionDescriptor = services.SingleOrDefault(
                    d => d.ServiceType ==
                        typeof(DbConnection));
    
                services.Remove(dbConnectionDescriptor);
    
                // Create open SqliteConnection so EF won't automatically close it.
                services.AddSingleton<DbConnection>(container =>
                {
                    var connection = new SqliteConnection("DataSource=:memory:");
                    connection.Open();
    
                    return connection;
                });
    
                services.AddDbContext<ApplicationDbContext>((container, options) =>
                {
                    var connection = container.GetRequiredService<DbConnection>();
                    options.UseSqlite(connection);
                });
            });
    
            builder.UseEnvironment("Development");
        }
    }
    

    Az adatbázis-vetést a mintaalkalmazásban a InitializeDbForTests metódus végzi. A módszert az integrációs tesztek mintájában ismertetjük: Az alkalmazás szervezetének tesztelése szakasz.

    A SUT adatbázis-környezetét regisztrálták Program.cs-ben. A tesztalkalmazás builder.ConfigureServices visszahívása az alkalmazás lesz végrehajtva. Ha más adatbázist szeretne használni a tesztekhez, mint az alkalmazás adatbázisa, az alkalmazás adatbázis-környezetét a builder.ConfigureServicesadatbázis-környezetre kell cserélni.

    A mintaalkalmazás megkeresi az adatbázis-környezet szolgáltatásleíróját, és a leíró használatával eltávolítja a szolgáltatásregisztrációt. A gyár ezután hozzáad egy új ApplicationDbContext, amely memórián belüli adatbázist használ a tesztekhez.

    Ha másik adatbázishoz szeretne csatlakozni, módosítsa a DbConnection. SQL Server tesztadatbázis használata:

  1. Az egyéni CustomWebApplicationFactory-t használja a tesztosztályokban. Az alábbi példa a gyárat használja a IndexPageTests osztályban:

    public class IndexPageTests :
        IClassFixture<CustomWebApplicationFactory<Program>>
    {
        private readonly HttpClient _client;
        private readonly CustomWebApplicationFactory<Program>
            _factory;
    
        public IndexPageTests(
            CustomWebApplicationFactory<Program> factory)
        {
            _factory = factory;
            _client = factory.CreateClient(new WebApplicationFactoryClientOptions
            {
                AllowAutoRedirect = false
            });
        }
    }
    

    A mintaalkalmazás ügyfele úgy van konfigurálva, hogy megakadályozza a HttpClient átirányítások követését. Amint azt a Mock authentication szakasz későbbi szakasza ismerteti, ez lehetővé teszi a tesztek számára az alkalmazás első válaszának eredményének ellenőrzését. Az első válasz ezek közül sok tesztben egy átirányítás egy "Location" fejléccel.

  2. Egy tipikus teszt a HttpClient és segédmetenek használatával dolgozza fel a kérést és a választ:

    [Fact]
    public async Task Post_DeleteAllMessagesHandler_ReturnsRedirectToRoot()
    {
        // Arrange
        var defaultPage = await _client.GetAsync("/");
        var content = await HtmlHelpers.GetDocumentAsync(defaultPage);
    
        //Act
        var response = await _client.SendAsync(
            (IHtmlFormElement)content.QuerySelector("form[id='messages']"),
            (IHtmlButtonElement)content.QuerySelector("button[id='deleteAllBtn']"));
    
        // Assert
        Assert.Equal(HttpStatusCode.OK, defaultPage.StatusCode);
        Assert.Equal(HttpStatusCode.Redirect, response.StatusCode);
        Assert.Equal("/", response.Headers.Location.OriginalString);
    }
    

A SUT-nak küldött POST-kéréseknek meg kell felelniük az alkalmazás adatvédelmi rendszerének automatikusan végrehajtott hamisítás elleni ellenőrzési folyamatának. A teszt POST-kérésének rendezéséhez a tesztalkalmazásnak a következőnek kell lennie:

  1. Kérjen egy kérelmet az oldalhoz.
  2. Elemezze az azonosítóhamisítás elleni cookie-t, és kérje le az érvényesítési jogkivonatot a választól.
  3. Végezze el a POST kérést az antiforgery cookie és az érvényesítési jogkivonattal.

A mintaalkalmazás SendAsync segédmetódusai (Helpers/HttpClientExtensions.cs) és a GetDocumentAsync segédmetódus (Helpers/HtmlHelpers.cs) az AngleSharp elemzővel kezelik az antiforgery-ellenőrzést az alábbi módszerekkel:

  • GetDocumentAsync: Megkapja a HttpResponseMessage, és visszaad egy IHtmlDocument. GetDocumentAsync egy olyan gyárat használ, amely az eredeti alapján készít elő egy HttpResponseMessage-t. További információért tekintse meg az AngleSharp dokumentációt.
  • SendAsync kiterjesztési metódusai a HttpClient összállítanak egy HttpRequestMessage-t, és meghívják a SendAsync(HttpRequestMessage)-t, hogy kérelmeket küldjenek a SUT-nak. A SendAsync túlterhelései elfogadják a HTML-űrlapot (IHtmlFormElement) és a következőket:
    • Az űrlap Küldés gombja (IHtmlElement)
    • Űrlapértékek gyűjtése (IEnumerable<KeyValuePair<string, string>>)
    • Küldés gomb (IHtmlElement) és űrlapértékek (IEnumerable<KeyValuePair<string, string>>)

AngleSharp egy harmadik féltől származó elemzési kódtár, amelyet bemutató célokra használnak, ebben a cikkben és a mintaalkalmazásban. Az AngleSharp nem támogatott vagy szükséges ASP.NET Core-alkalmazások integrációs teszteléséhez. Más elemzők is használhatók, például a Html Agility Pack (HAP). Egy másik módszer, ha olyan kódot írunk, amely közvetlenül kezeli az antiforgery rendszer kérés-ellenőrzési tokenjét és az antiforgery cookie-t. További információkért tekintse meg az antiforgery-ellenőrzésekről szóló cikket itt: AngleSharp vs Application Parts,.

A EF-Core memóriabeli adatbázis-szolgáltató korlátozott és alapszintű teszteléshez használható, de a SQLite-szolgáltató a memóriabeli teszteléshez ajánlott.

Lásd: Indítás kiterjesztése indítási szűrőkkel, amely bemutatja, hogyan konfigurálhat köztes szoftvereket IStartupFilterhasználatával, ami akkor hasznos, ha egy teszthez egyéni szolgáltatásra vagy köztes szoftverre van szükség.

Az ügyfél testreszabása a WithWebHostBuilder használatával

Ha egy tesztmetóduson belül további konfigurációra van szükség, WithWebHostBuilder létrehoz egy új WebApplicationFactory egy olyan IWebHostBuilder-vel, amelyet a konfiguráció további testreszab.

A mintakód meghívja WithWebHostBuilder, hogy a konfigurált szolgáltatásokat tesztcsomókkal cserélje le. További információért és a példák használatáért lásd a Modellszolgáltatások injektálása rész ebben a cikkben.

A mintaalkalmazás Post_DeleteMessageHandler_ReturnsRedirectToRoot tesztmetódusa a WithWebHostBuilderhasználatát mutatja be. Ez a teszt egy rekord törlését hajtja végre az adatbázisban egy űrlapbeküldés SUT-ban való aktiválásával.

Mivel a IndexPageTests osztály egy másik tesztje olyan műveletet hajt végre, amely törli az adatbázis összes rekordját, és a Post_DeleteMessageHandler_ReturnsRedirectToRoot metódus előtt futhat, az adatbázist ebben a tesztmetódusban újból el kell végezni, hogy a rendszer egy rekordot adjon meg a SUT számára a törléshez. A SUT-ban a messages űrlap első törlési gombjának kiválasztását a rendszer szimulálja a SUT-nak küldött kérésben:

[Fact]
public async Task Post_DeleteMessageHandler_ReturnsRedirectToRoot()
{
    // Arrange
    using (var scope = _factory.Services.CreateScope())
    {
        var scopedServices = scope.ServiceProvider;
        var db = scopedServices.GetRequiredService<ApplicationDbContext>();

        Utilities.ReinitializeDbForTests(db);
    }

    var defaultPage = await _client.GetAsync("/");
    var content = await HtmlHelpers.GetDocumentAsync(defaultPage);

    //Act
    var response = await _client.SendAsync(
        (IHtmlFormElement)content.QuerySelector("form[id='messages']"),
        (IHtmlButtonElement)content.QuerySelector("form[id='messages']")
            .QuerySelector("div[class='panel-body']")
            .QuerySelector("button"));

    // Assert
    Assert.Equal(HttpStatusCode.OK, defaultPage.StatusCode);
    Assert.Equal(HttpStatusCode.Redirect, response.StatusCode);
    Assert.Equal("/", response.Headers.Location.OriginalString);
}

Ügyfélbeállítások

A WebApplicationFactoryClientOptions példányok létrehozásakor az alapértelmezett beállításokat és az elérhető beállításokat a HttpClient lapon tekinti meg.

Hozza létre a WebApplicationFactoryClientOptions osztályt, és adja át a CreateClient() metódusnak:

public class IndexPageTests :
    IClassFixture<CustomWebApplicationFactory<Program>>
{
    private readonly HttpClient _client;
    private readonly CustomWebApplicationFactory<Program>
        _factory;

    public IndexPageTests(
        CustomWebApplicationFactory<Program> factory)
    {
        _factory = factory;
        _client = factory.CreateClient(new WebApplicationFactoryClientOptions
        {
            AllowAutoRedirect = false
        });
    }
}

MEGJEGYZÉS: A HTTPS-átirányítási figyelmeztetések elkerülése érdekében, amikor a HTTPS-átirányító köztes réteget használja, állítsa be a BaseAddress = new Uri("https://localhost")

Makettszolgáltatások injektálása

A szolgáltatások felülírhatók egy tesztben a ConfigureTestServices hívásával a gazdagép építőn. A felülbírált szolgáltatások konkrét teszt hatóköréhez a WithWebHostBuilder metódus használható egy hostkészítő lekérésére. Ez a következő tesztekben látható:

A minta SUT tartalmaz egy hatókörű szolgáltatást, amely egy idézetet ad vissza. Az indexlap kérésekor az idézet az Index lap rejtett mezőjébe van beágyazva.

Services/IQuoteService.cs:

public interface IQuoteService
{
    Task<string> GenerateQuote();
}

Services/QuoteService.cs:

// Quote ©1975 BBC: The Doctor (Tom Baker); Dr. Who: Planet of Evil
// https://www.bbc.co.uk/programmes/p00pyrx6
public class QuoteService : IQuoteService
{
    public Task<string> GenerateQuote()
    {
        return Task.FromResult<string>(
            "Come on, Sarah. We've an appointment in London, " +
            "and we're already 30,000 years late.");
    }
}

Program.cs:

services.AddScoped<IQuoteService, QuoteService>();

Pages/Index.cshtml.cs:

public class IndexModel : PageModel
{
    private readonly ApplicationDbContext _db;
    private readonly IQuoteService _quoteService;

    public IndexModel(ApplicationDbContext db, IQuoteService quoteService)
    {
        _db = db;
        _quoteService = quoteService;
    }

    [BindProperty]
    public Message Message { get; set; }

    public IList<Message> Messages { get; private set; }

    [TempData]
    public string MessageAnalysisResult { get; set; }

    public string Quote { get; private set; }

    public async Task OnGetAsync()
    {
        Messages = await _db.GetMessagesAsync();

        Quote = await _quoteService.GenerateQuote();
    }

Pages/Index.cs:

<input id="quote" type="hidden" value="@Model.Quote">

A SUT-alkalmazás futtatásakor a következő korrektúra jön létre:

<input id="quote" type="hidden" value="Come on, Sarah. We&#x27;ve an appointment in 
    London, and we&#x27;re already 30,000 years late.">

A szolgáltatás és az injektálás teszteléséhez integrációs tesztben a teszt egy ál-szolgáltatást injektál a SUT-ba. A modellszolgáltatás lecseréli az alkalmazás QuoteService-t a tesztalkalmazás által biztosított, TestQuoteServicenevű szolgáltatásra.

IntegrationTests.IndexPageTests.cs:

// Quote ©1975 BBC: The Doctor (Tom Baker); Pyramids of Mars
// https://www.bbc.co.uk/programmes/p00pys55
public class TestQuoteService : IQuoteService
{
    public Task<string> GenerateQuote()
    {
        return Task.FromResult(
            "Something's interfering with time, Mr. Scarman, " +
            "and time is my business.");
    }
}

ConfigureTestServices van meghívva, és a hatókörön belüli szolgáltatás regisztrálva van:

[Fact]
public async Task Get_QuoteService_ProvidesQuoteInPage()
{
    // Arrange
    var client = _factory.WithWebHostBuilder(builder =>
        {
            builder.ConfigureTestServices(services =>
            {
                services.AddScoped<IQuoteService, TestQuoteService>();
            });
        })
        .CreateClient();

    //Act
    var defaultPage = await client.GetAsync("/");
    var content = await HtmlHelpers.GetDocumentAsync(defaultPage);
    var quoteElement = content.QuerySelector("#quote");

    // Assert
    Assert.Equal("Something's interfering with time, Mr. Scarman, " +
        "and time is my business.", quoteElement.Attributes["value"].Value);
}

A teszt végrehajtása során létrehozott jelölések a TestQuoteServiceáltal megadott idézőjelszöveget tükrözik, ezért az állítás sikeres.

<input id="quote" type="hidden" value="Something&#x27;s interfering with time, 
    Mr. Scarman, and time is my business.">

Szimulált hitelesítés

Az AuthTests osztály tesztjei ellenőrzik, hogy egy biztonságos végpont:

  • Átirányít egy nem hitelesített felhasználót az alkalmazás bejelentkezési oldalára.
  • Egy hitelesített felhasználó tartalmát adja vissza.

A SUT-ban a /SecurePage lap a AuthorizePage konvenciót használja, hogy egy AuthorizeFilter-t alkalmazzon a lapra. További információkért lásd a Razor oldalak engedélyezési konvencióit.

services.AddRazorPages(options =>
{
    options.Conventions.AuthorizePage("/SecurePage");
});

A Get_SecurePageRedirectsAnUnauthenticatedUser tesztben egy WebApplicationFactoryClientOptions úgy van beállítva, hogy letiltsa az átirányításokat azáltal, hogy AllowAutoRedirect-t false-ra állítja.

[Fact]
public async Task Get_SecurePageRedirectsAnUnauthenticatedUser()
{
    // Arrange
    var client = _factory.CreateClient(
        new WebApplicationFactoryClientOptions
        {
            AllowAutoRedirect = false
        });

    // Act
    var response = await client.GetAsync("/SecurePage");

    // Assert
    Assert.Equal(HttpStatusCode.Redirect, response.StatusCode);
    Assert.StartsWith("http://localhost/Identity/Account/Login",
        response.Headers.Location.OriginalString);
}

Az ügyfél átirányításának megakadályozásával a következő ellenőrzéseket hajthatja végre:

  • A SUT által visszaadott állapotkód a várt HttpStatusCode.Redirect eredményen ellenőrizhető, nem pedig a bejelentkezési lapra való átirányítás utáni végleges állapotkóddal, amely HttpStatusCode.OK.
  • A válaszfejlécek közül a Location fejléc értékét ellenőrzik annak megállapítására, hogy http://localhost/Identity/Account/Login-el kezdődik-e, nem pedig a végleges bejelentkezési oldal válaszát, ahol a Location fejléc nem lenne jelen.

A tesztalkalmazás szimulálhat egy AuthenticationHandler<TOptions>ConfigureTestServices-ben a hitelesítés és az engedélyezés szempontjainak teszteléséhez. Egy minimális forgatókönyv AuthenticateResult.Success-t ad vissza.

public class TestAuthHandler : AuthenticationHandler<AuthenticationSchemeOptions>
{
    public TestAuthHandler(IOptionsMonitor<AuthenticationSchemeOptions> options,
        ILoggerFactory logger, UrlEncoder encoder)
        : base(options, logger, encoder)
    {
    }

    protected override Task<AuthenticateResult> HandleAuthenticateAsync()
    {
        var claims = new[] { new Claim(ClaimTypes.Name, "Test user") };
        var identity = new ClaimsIdentity(claims, "Test");
        var principal = new ClaimsPrincipal(identity);
        var ticket = new AuthenticationTicket(principal, "TestScheme");

        var result = AuthenticateResult.Success(ticket);

        return Task.FromResult(result);
    }
}

A TestAuthHandler egy felhasználó hitelesítésére hívják meg, amikor a hitelesítési séma TestScheme van beállítva, ahol a AddAuthentication regisztrálva van a ConfigureTestServices-ra. Fontos, hogy a TestScheme séma megfeleljen az alkalmazás által várt sémának. Ellenkező esetben a hitelesítés nem működik.

[Fact]
public async Task Get_SecurePageIsReturnedForAnAuthenticatedUser()
{
    // Arrange
    var client = _factory.WithWebHostBuilder(builder =>
        {
            builder.ConfigureTestServices(services =>
            {
                services.AddAuthentication(defaultScheme: "TestScheme")
                    .AddScheme<AuthenticationSchemeOptions, TestAuthHandler>(
                        "TestScheme", options => { });
            });
        })
        .CreateClient(new WebApplicationFactoryClientOptions
        {
            AllowAutoRedirect = false,
        });

    client.DefaultRequestHeaders.Authorization =
        new AuthenticationHeaderValue(scheme: "TestScheme");

    //Act
    var response = await client.GetAsync("/SecurePage");

    // Assert
    Assert.Equal(HttpStatusCode.OK, response.StatusCode);
}

A WebApplicationFactoryClientOptionskapcsolatos további információkért lásd a Ügyfélbeállítások című szakaszt.

A köztes szoftver hitelesítésének alapszintű tesztjei

A Köztes szoftver hitelesítésének alapszintű teszteléséhez tekintse meg ezt a GitHub-adattárat. A tesztforgatókönyvre jellemző tesztkiszolgálót tartalmaz.

A környezet beállítása

Állítsa be a környezetet az egyéni alkalmazás-előállítóban:

public class CustomWebApplicationFactory<TProgram>
    : WebApplicationFactory<TProgram> where TProgram : class
{
    protected override void ConfigureWebHost(IWebHostBuilder builder)
    {
        builder.ConfigureServices(services =>
        {
            var dbContextDescriptor = services.SingleOrDefault(
                d => d.ServiceType ==
                    typeof(DbContextOptions<ApplicationDbContext>));

            services.Remove(dbContextDescriptor);

            var dbConnectionDescriptor = services.SingleOrDefault(
                d => d.ServiceType ==
                    typeof(DbConnection));

            services.Remove(dbConnectionDescriptor);

            // Create open SqliteConnection so EF won't automatically close it.
            services.AddSingleton<DbConnection>(container =>
            {
                var connection = new SqliteConnection("DataSource=:memory:");
                connection.Open();

                return connection;
            });

            services.AddDbContext<ApplicationDbContext>((container, options) =>
            {
                var connection = container.GetRequiredService<DbConnection>();
                options.UseSqlite(connection);
            });
        });

        builder.UseEnvironment("Development");
    }
}

Hogyan következtet a tesztinfrastruktúra az alkalmazás tartalomgyökerének elérési útjára?

A WebApplicationFactory konstruktor meghatározza az alkalmazás tartalom gyökér elérési útját úgy, hogy a WebApplicationFactoryContentRootAttribute szerelvény TEntryPointkulccsal a System.Reflection.Assembly.FullName-t keres az integrációs teszteket tartalmazó szerelvényen. Ha nem található a megfelelő kulccsal rendelkező attribútum, WebApplicationFactory visszatér a megoldásfájl (.sln) kereséséhez, és hozzáadja a TEntryPoint szerelvény nevét a megoldáskönyvtárhoz. Az alkalmazás gyökérkönyvtára (a tartalom gyökérútvonala) a nézetek és tartalomfájlok felderítésére szolgál.

Árnyékmásolás letiltása

Az árnyékmásolás miatt a tesztek a kimeneti könyvtártól eltérő könyvtárban futnak. Ha a tesztek a fájlok Assembly.Location-hoz viszonyított betöltésére támaszkodnak, és problémákat tapasztalunk, előfordulhat, hogy le kell tiltani az árnyékmásolást.

Ha le szeretné tiltani az árnyékmásolást az xUnit használatakor, hozzon létre egy xunit.runner.json fájlt a tesztprojekt könyvtárában a megfelelő konfigurációs beállítással:

{
  "shadowCopy": false
}

Objektumok ártalmatlanítása

A IClassFixture implementáció tesztjeinek végrehajtása után TestServer és HttpClient megsemmisülnek, amikor az xUnit megsemmisíti a WebApplicationFactory-at. Ha a fejlesztő által példányosított objektumok ártalmatlanítást igényelnek, azokat a IClassFixture implementációban kell megsemmisíteni. További információért lásd: Dispose metódus implementálása.

Integrációs tesztek mintája

A mintaalkalmazás két alkalmazásból áll:

Alkalmazás Projektmappa Leírás
Üzenetküldő alkalmazás (a SUT) src/RazorPagesProject Lehetővé teszi a felhasználó számára, hogy hozzáadjon, töröljön egyet, törölje az összeset, és elemezze az üzeneteket.
Alkalmazás tesztelése tests/RazorPagesProject.Tests A SUT integrációs tesztelésére szolgál.

A tesztek egy IDE beépített tesztfunkcióival futtathatók, például Visual Studio. Ha Visual Studio Code vagy parancssort használ, hajtsa végre a következő parancsot egy parancssorban a tests/RazorPagesProject.Tests könyvtárban:

dotnet test

Üzenetküldő alkalmazás (SUT) szervezete

A SUT egy Razor Pages üzenetrendszer, amely a következő jellemzőkkel rendelkezik:

  • Az alkalmazás indexoldala (Pages/Index.cshtml és Pages/Index.cshtml.cs) felhasználói felületi és lapmodellezési módszereket biztosít az üzenetek hozzáadásának, törlésének és elemzésének szabályozásához (átlagos szavak üzenetenként).
  • Az üzeneteket a Message osztály (Data/Message.cs) írja le két tulajdonságokkal: Id (kulcs) és Text (üzenet). A Text tulajdonság megadása kötelező, és legfeljebb 200 karakter hosszúságú lehet.
  • Az üzenetek tárolása Entity Framework memórián belüli adatbázisának† használatával történik.
  • Az alkalmazás egy adatelérési réteget (DAL) tartalmaz az adatbázis környezeti osztályában, AppDbContext (Data/AppDbContext.cs).
  • Ha az adatbázis üres az alkalmazás indításakor, az üzenettároló három üzenettel lesz inicializálva.
  • Az alkalmazás tartalmaz egy /SecurePage, amelyet csak hitelesített felhasználó érhet el.

†Az EF-cikk, Az InMemorytesztelése című cikk bemutatja, hogyan használható memóriabeli adatbázis az MSTesttel végzett tesztekhez. Ez a témakör az xUnit tesztelési keretrendszert használja. A különböző tesztelési keretrendszerek tesztelési fogalmai és tesztelési implementációi hasonlóak, de nem azonosak.

Bár az alkalmazás nem használja az adattár mintát, és nem hatékony példa a Munkaegység (UoW) mintára, a Razor Pages támogatja ezeket a fejlesztési mintákat. További információ: Az infrastruktúra perzisztenciaréteg tervezése és Tesztvezérlő logika (a minta implementálja az adattár mintát).

Tesztalkalmazás szervezése

A tesztalkalmazás egy konzolalkalmazás a tests/RazorPagesProject.Tests könyvtárban.

Alkalmazáskönyvtár tesztelése Leírás
AuthTests A következő tesztelési módszereket tartalmazza:
  • Biztonságos lap elérése hitelesítés nélküli felhasználó által.
  • Biztonságos lap elérése egy hitelesített felhasználó által egy ál-AuthenticationHandler<TOptions>.
  • GitHub-felhasználói profil beszerzése és a profil felhasználói bejelentkezésének ellenőrzése.
BasicTests Az útválasztás és a tartalomtípus tesztelési módszerét tartalmazza.
IntegrationTests Az Index lap integrációs tesztjeit tartalmazza egyéni WebApplicationFactory osztály használatával.
Helpers/Utilities
  • Utilities.cs tartalmazza az adatbázis tesztelési adatokkal való üzembe helyezésekor használt InitializeDbForTests metódust.
  • HtmlHelpers.cs egy AngleSharp IHtmlDocument visszaadására szolgáló metódust biztosít a tesztelési módszerek számára.
  • HttpClientExtensions.cs túlterhelést biztosít a SendAsync számára, hogy kéréseket küldjön a SUT-nak.

A tesztelési keretrendszer xUnit. Az integrációs teszteket a Microsoft.AspNetCore.TestHostsegítségével hajtják végre, amely magában foglalja a TestServer-et. Mivel a Microsoft.AspNetCore.Mvc.Testing csomag a tesztgazda és a tesztkiszolgáló konfigurálására szolgál, a TestHost és TestServer csomagok nem igényelnek közvetlen csomaghivatkozásokat a tesztalkalmazás projektfájljában vagy fejlesztői konfigurációjában a tesztalkalmazásban.

Az integrációs tesztek általában kis adatkészletet igényelnek az adatbázisban a teszt végrehajtása előtt. A törlési teszt például egy adatbázisrekord törlését kéri, ezért az adatbázisnak legalább egy rekorddal kell rendelkeznie ahhoz, hogy a törlési kérelem sikeres legyen.

A mintaalkalmazás három üzenettel tölti fel az adatbázist a Utilities.cs-ben, amelyeket a tesztek futtatásakor használhatnak.

public static void InitializeDbForTests(ApplicationDbContext db)
{
    db.Messages.AddRange(GetSeedingMessages());
    db.SaveChanges();
}

public static void ReinitializeDbForTests(ApplicationDbContext db)
{
    db.Messages.RemoveRange(db.Messages);
    InitializeDbForTests(db);
}

public static List<Message> GetSeedingMessages()
{
    return new List<Message>()
    {
        new Message(){ Text = "TEST RECORD: You're standing on my scarf." },
        new Message(){ Text = "TEST RECORD: Would you like a jelly baby?" },
        new Message(){ Text = "TEST RECORD: To the rational mind, " +
            "nothing is inexplicable; only unexplained." }
    };
}

A SUT adatbázis-környezetét regisztrálták Program.cs-ben. A tesztalkalmazás builder.ConfigureServices visszahívása az alkalmazás lesz végrehajtva. Ha másik adatbázist szeretne használni a tesztekhez, az alkalmazás adatbázis-környezetét le kell cserélni a builder.ConfigureServices-ban. A további információkért lásd a WebApplicationFactory testreszabása szakaszt.

További erőforrások

Ez a cikk feltételezi az egységtesztek alapszintű megértését. Ha nem ismeri a tesztelési fogalmakat, tekintse meg a .NET Core és a .NET Standard Egységtesztelés című cikket és annak csatolt tartalmát.

Mintakód megtekintése vagy letöltése (hogyan kell letölteni)

A mintaalkalmazás egy Razor Pages-alkalmazás, és feltételezi, hogy alapszintű ismeretekkel rendelkezik a Razor Pages-ről. Ha nem ismeri a Razor oldalak tartalmát, olvassa el a következő cikkeket.

Az SLA-kteszteléséhez olyan eszközt ajánlunk, mint például a .NET-Playwright, amely automatizálhatja a böngészőt.

Bevezetés az integrációs tesztekbe

Az integrációs tesztek az alkalmazás összetevőit szélesebb szinten értékelik ki, mint egységtesztek. Az egységtesztek izolált szoftverösszetevők, például az egyes osztálymódszerek tesztelésére szolgálnak. Az integrációs tesztek megerősítik, hogy két vagy több alkalmazásösszetevő együttműködik a várt eredmény érdekében, beleértve a kérések teljes feldolgozásához szükséges összes összetevőt is.

Ezek a szélesebb körű tesztek az alkalmazás infrastruktúrájának és teljes keretrendszerének tesztelésére szolgálnak, gyakran a következő összetevőket is beleértve:

  • Adatbázis
  • Fájlrendszer
  • Hálózati berendezések
  • Kérelem-válasz folyamat

Az egységtesztek az infrastruktúra-összetevők helyett létrehozott összetevőket használnak, más néven hamis vagy szimulált objektumokat.

Az egységtesztekkel ellentétben az integrációs tesztek:

  • Használja azokat a tényleges összetevőket, amelyeket az alkalmazás éles környezetben használ.
  • További kód- és adatfeldolgozást igényel.
  • A futtatás hosszabb időt vesz igénybe.

Ezért az integrációs tesztek használatának korlátozása a legfontosabb infrastruktúra-forgatókönyvekre. Ha egy viselkedést egységteszt vagy integrációs teszt használatával lehet tesztelni, válassza ki az egységtesztet.

Az integrációs tesztek megvitatása során a tesztelt projektet gyakran nevezik System Under Test, vagy röviden "SUT" néven. A cikk során a "SUT" a tesztelt ASP.NET Core-alkalmazásra hivatkozik.

Ne írjon integrációs teszteket az adatbázisok és fájlrendszerek minden permutációs adat- és fájlhozzáféréshez. Függetlenül attól, hogy az alkalmazások hány helyen használják az adatbázisokat és a fájlrendszereket, az olvasási, írási, frissítési és törlési integrációs tesztek koncentrált készlete általában képes megfelelően tesztelni az adatbázis- és fájlrendszer-összetevőket. Használjon egységteszteket az ezen összetevőkkel interakcióba lépő metóduslogika rutintesztjeihez. Az egységtesztekben az infrastruktúra hamis vagy hamisított használata gyorsabb tesztvégrehajtást eredményez.

ASP.NET Core integrációs tesztek

A ASP.NET Core integrációs tesztjeihez a következők szükségesek:

  • A tesztprojektek a teszteket tartalmazzák és hajtják végre. A tesztprojekt hivatkozással rendelkezik a SUT-ra.
  • A tesztprojekt létrehoz egy tesztwebhely-gazdagépet a SUT számára, és egy tesztkiszolgáló-ügyféllel kezeli a kérelmeket és a válaszokat a SUT-val.
  • A tesztfuttató a tesztek végrehajtására és a teszteredmények jelentésére szolgál.

Az integrációs tesztek olyan események sorozatát követik, amelyek magukban foglalják a szokásos Előkészület, Végrehajtásés Ellenőrzés tesztelési lépéseket.

  1. Az SUT webhoszt konfigurálva van.
  2. Létrejön egy tesztkiszolgáló-ügyfél, amely kéréseket küld az alkalmazásnak.
  3. A Arrange tesztelési lépés végrehajtása: A tesztalkalmazás előkészít egy kérést.
  4. A törvény tesztlépés végrehajtása: Az ügyfél elküldi a kérést, és megkapja a választ.
  5. A ellenőrzési tesztlépést végrehajtják: A tényleges választ a rendszer a várt válasz alapján érvényesíti, és érvényesnek nyilvánítja (), vagy sikertelennek ().
  6. A folyamat az összes teszt végrehajtásáig folytatódik.
  7. A teszteredmények jelentésre kerültek.

A teszt webgazda általában eltérő konfigurációval van beállítva, mint az alkalmazás normál webgazdája a tesztek futtatásához. Előfordulhat például, hogy a tesztekhez egy másik adatbázist vagy különböző alkalmazásbeállításokat használnak.

Az infrastruktúra-összetevőket, például a tesztweb-gazdagépet és a memóriabeli tesztkiszolgálót (TestServer) a Microsoft.AspNetCore.Mvc.Testing csomag biztosítja vagy felügyeli. A csomag használata leegyszerűsíti a tesztek létrehozását és végrehajtását.

A Microsoft.AspNetCore.Mvc.Testing csomag a következő feladatokat kezeli:

  • Másolja a függőségi fájlt (.deps) a SUT-ból a tesztprojekt bin könyvtárába.
  • A tartalomgyökerét-re a SUT projektgyökeréhez állítja, hogy a statikus fájlok és a lapok/nézetek megtalálhatók legyenek a tesztek végrehajtásakor.
  • A WebApplicationFactory osztály gördülékenyebbé teszi a SUT TestServer használatával történő rendszerindítását.

Az egységtesztek dokumentációja ismerteti a tesztprojektek és tesztfuttatók beállításának módját, valamint a tesztek futtatásának részletes utasításait, valamint a tesztek és tesztosztályok elnevezésére vonatkozó javaslatokat.

Egységtesztek elkülönítése az integrációs tesztektől különböző projektekre. A tesztek elkülönítése:

  • Segít biztosítani, hogy az infrastruktúra tesztelési összetevői véletlenül ne legyenek belefoglalva az egységtesztekbe.
  • Lehetővé teszi a tesztkészlet futtatásának szabályozását.

Gyakorlatilag nincs különbség a Razor Pages-alkalmazások és az MVC-alkalmazások tesztjeinek konfigurációja között. Az egyetlen különbség a tesztek elnevezésében van. Egy Razor Pages-alkalmazásban az oldalvégpontok tesztjeit általában az oldalmodell-osztályról nevezik el (például IndexPageTests az indexlap összetevőintegrációjának teszteléséhez). Az MVC-alkalmazásokban a tesztek általában vezérlőosztályok szerint vannak rendszerezve, és az általuk tesztelt vezérlőkről nevezik el őket (például HomeControllerTests a Home vezérlő összetevőintegrációjának teszteléséhez).

Alkalmazás előfeltételeinek tesztelése

A tesztprojektnek a következőnek kell lennie:

Ezek az előfeltételek láthatók a mintaalkalmazás-ban. Vizsgálja meg a tests/RazorPagesProject.Tests/RazorPagesProject.Tests.csproj fájlt. A mintaalkalmazás az xUnit tesztelési keretrendszert és a AngleSharp elemzőtárat használja, így a mintaalkalmazás a következőre is hivatkozik:

A xunit.runner.visualstudio 2.4.2-es vagy újabb verzióját használó alkalmazásokban a tesztprojektnek hivatkoznia kell a Microsoft.NET.Test.Sdk csomagra.

Az Entity Framework Core a tesztekben is használatos. Tekintse meg a projektfájlt a GitHub.

SUT-környezet

Ha a SUT környezet nincs beállítva, a környezet alapértelmezés szerint a Fejlesztési környezet.

Alapszintű tesztek az alapértelmezett WebApplicationFactory használatával

Tegye közzé az implicit módon definiált Program osztályt a tesztprojektben az alábbi műveletek egyikével:

  • Belső típusok kitétele a webalkalmazásból a tesztprojekt számára. Ez a SUT-projekt fájljában (.csproj) végezhető el:

    <ItemGroup>
         <InternalsVisibleTo Include="MyTestProject" />
    </ItemGroup>
    
  • Tegye nyilvánossá a Program osztályt egy részleges osztály deklarációval:

    var builder = WebApplication.CreateBuilder(args);
    // ... Configure services, routes, etc.
    app.Run();
    + public partial class Program { }
    

    A mintaalkalmazás a Program részleges osztály megközelítést használja.

A WebApplicationFactory<TEntryPoint>-t használjuk egy TestServer létrehozásához az integrációs tesztekhez. TEntryPoint a SUT belépési pontosztálya, általában Program.cs.

A tesztosztályok implementálnak egy osztály-fix technikai felületet (IClassFixture), amely jelzi, hogy az osztály teszteket tartalmaz, és megosztott objektumpéldányokat biztosít az osztály tesztjei során.

Az alábbi tesztosztály BasicTestsa WebApplicationFactory használatával végzi a SUT inicializálását, és biztosít egy HttpClient-t a Get_EndpointsReturnSuccessAndCorrectContentTypetesztmetódus számára. A metódus ellenőrzi, hogy a válasz állapotkódja sikeres-e (200-299), és a Content-Type fejléc több alkalmazásoldalon text/html; charset=utf-8.

CreateClient() létrehoz egy HttpClient-példányt, amely automatikusan követi az átirányításokat és kezeli a cookie-kat.

public class BasicTests 
    : IClassFixture<WebApplicationFactory<Program>>
{
    private readonly WebApplicationFactory<Program> _factory;

    public BasicTests(WebApplicationFactory<Program> factory)
    {
        _factory = factory;
    }

    [Theory]
    [InlineData("/")]
    [InlineData("/Index")]
    [InlineData("/About")]
    [InlineData("/Privacy")]
    [InlineData("/Contact")]
    public async Task Get_EndpointsReturnSuccessAndCorrectContentType(string url)
    {
        // Arrange
        var client = _factory.CreateClient();

        // Act
        var response = await client.GetAsync(url);

        // Assert
        response.EnsureSuccessStatusCode(); // Status Code 200-299
        Assert.Equal("text/html; charset=utf-8", 
            response.Content.Headers.ContentType.ToString());
    }
}
[TestClass]
public class BasicTests
{
    private static CustomWebApplicationFactory<Program> _factory;

    [ClassInitialize]
    public static void AssemblyInitialize(TestContext _)
    {
        _factory = new CustomWebApplicationFactory<Program>();
    }

    [ClassCleanup(ClassCleanupBehavior.EndOfClass)]
    public static void AssemblyCleanup(TestContext _)
    {
        _factory.Dispose();
    }

    [TestMethod]
    [DataRow("/")]
    [DataRow("/Index")]
    [DataRow("/About")]
    [DataRow("/Privacy")]
    [DataRow("/Contact")]
    public async Task Get_EndpointsReturnSuccessAndCorrectContentType(string url)
    {
        // Arrange
        var client = _factory.CreateClient();

        // Act
        var response = await client.GetAsync(url);

        // Assert
        response.EnsureSuccessStatusCode(); // Status Code 200-299
        Assert.AreEqual("text/html; charset=utf-8",
            response.Content.Headers.ContentType.ToString());
    }
}
public class BasicTests
{
    private CustomWebApplicationFactory<Program>
        _factory;

    [SetUp]
    public void SetUp()
    {
        _factory = new CustomWebApplicationFactory<Program>();
    }

    [TearDown]
    public void TearDown()
    {
        _factory.Dispose();
    }

    [DatapointSource]
    public string[] values = ["/", "/Index", "/About", "/Privacy", "/Contact"];

    [Theory]
    public async Task Get_EndpointsReturnSuccessAndCorrectContentType(string url)
    {
        // Arrange
        var client = _factory.CreateClient();

        // Act
        var response = await client.GetAsync(url);

        // Assert
        response.EnsureSuccessStatusCode(); // Status Code 200-299
        Assert.That(response.Content.Headers.ContentType.ToString(), Is.EqualTo("text/html; charset=utf-8"));
    }
}

Alapértelmezés szerint a nem alapvető cookie-k nem maradnak meg a kérések között, ha engedélyezve van az általános adatvédelmi rendelet hozzájárulási szabályzata. A nem alapvető cookie-k( például a TempData-szolgáltató által használt) megőrzése érdekében jelölje meg őket alapvetőként a tesztek során. A cookie alapvetőként való megjelölésére vonatkozó utasításokért lásd: Alapvető cookie-k.

AngleSharp és Application Parts az antiforgery-ellenőrzésekhez

Ez a cikk a AngleSharp elemzőt használja az antiforgery-ellenőrzések kezelésére oldalak betöltése és a HTML elemzése révén. A vezérlő végpontjainak és a Razor oldalak nézeteinek részletesebb teszteléséhez, anélkül, hogy törődne azzal, hogyan jelennek meg a böngészőben, fontolja meg a Application Partshasználatát. Az Alkalmazásrészek megközelítés egy vezérlőt vagy Razor lapot injektál az alkalmazásba, amellyel JSON-kéréseket lehet küldeni a szükséges értékek lekéréséhez. További információért lásd a blogot: Integrációs tesztelés ASP.NET Core erőforrások Antiforgery-vel történő védelme az Alkalmazás Részei segítségével és a kapcsolódó GitHub-adattár szerzője: Martin Costello.

A WebApplicationFactory testreszabása

A webtárhely konfigurációja a tesztosztályoktól függetlenül hozható létre, úgy, hogy a WebApplicationFactory<TEntryPoint>-ból örökölt egy vagy több egyéni gyárat készítünk.

  1. Örököljön WebApplicationFactory és bírálja felül ConfigureWebHost. A IWebHostBuilder lehetővé teszi a szolgáltatásgyűjtemény konfigurálását IWebHostBuilder.ConfigureServices

    public class CustomWebApplicationFactory<TProgram>
        : WebApplicationFactory<TProgram> where TProgram : class
    {
        protected override void ConfigureWebHost(IWebHostBuilder builder)
        {
            builder.ConfigureServices(services =>
            {
                var dbContextDescriptor = services.SingleOrDefault(
                    d => d.ServiceType == 
                        typeof(IDbContextOptionsConfiguration<ApplicationDbContext>));
    
                services.Remove(dbContextDescriptor);
    
                var dbConnectionDescriptor = services.SingleOrDefault(
                    d => d.ServiceType ==
                        typeof(DbConnection));
    
                services.Remove(dbConnectionDescriptor);
    
                // Create open SqliteConnection so EF won't automatically close it.
                services.AddSingleton<DbConnection>(container =>
                {
                    var connection = new SqliteConnection("DataSource=:memory:");
                    connection.Open();
    
                    return connection;
                });
    
                services.AddDbContext<ApplicationDbContext>((container, options) =>
                {
                    var connection = container.GetRequiredService<DbConnection>();
                    options.UseSqlite(connection);
                });
            });
    
            builder.UseEnvironment("Development");
        }
    }
    
    public class CustomWebApplicationFactory<TProgram>
        : WebApplicationFactory<TProgram> where TProgram : class
    {
        protected override void ConfigureWebHost(IWebHostBuilder builder)
        {
            builder.ConfigureServices(services =>
            {
                var dbContextDescriptor = services.SingleOrDefault(
                    d => d.ServiceType ==
                        typeof(IDbContextOptionsConfiguration<ApplicationDbContext>));
    
                services.Remove(dbContextDescriptor);
    
                var dbConnectionDescriptor = services.SingleOrDefault(
                    d => d.ServiceType ==
                        typeof(DbConnection));
    
                services.Remove(dbConnectionDescriptor);
    
                // Create open SqliteConnection so EF won't automatically close it.
                services.AddSingleton<DbConnection>(container =>
                {
                    var connection = new SqliteConnection("DataSource=:memory:");
                    connection.Open();
    
                    return connection;
                });
    
                services.AddDbContext<ApplicationDbContext>((container, options) =>
                {
                    var connection = container.GetRequiredService<DbConnection>();
                    options.UseSqlite(connection);
                });
            });
    
            builder.UseEnvironment("Development");
        }
    }
    
    public class CustomWebApplicationFactory<TProgram>
        : WebApplicationFactory<TProgram> where TProgram : class
    {
        protected override void ConfigureWebHost(IWebHostBuilder builder)
        {
            builder.ConfigureServices(services =>
            {
                var dbContextDescriptor = services.SingleOrDefault(
                    d => d.ServiceType ==
                        typeof(IDbContextOptionsConfiguration<ApplicationDbContext>));
    
                services.Remove(dbContextDescriptor);
    
                var dbConnectionDescriptor = services.SingleOrDefault(
                    d => d.ServiceType ==
                        typeof(DbConnection));
    
                services.Remove(dbConnectionDescriptor);
    
                // Create open SqliteConnection so EF won't automatically close it.
                services.AddSingleton<DbConnection>(container =>
                {
                    var connection = new SqliteConnection("DataSource=:memory:");
                    connection.Open();
    
                    return connection;
                });
    
                services.AddDbContext<ApplicationDbContext>((container, options) =>
                {
                    var connection = container.GetRequiredService<DbConnection>();
                    options.UseSqlite(connection);
                });
            });
    
            builder.UseEnvironment("Development");
        }
    }
    

    Az adatbázis-vetést a mintaalkalmazásban a InitializeDbForTests metódus végzi. A módszert az integrációs tesztek mintájában ismertetjük: Az alkalmazás szervezetének tesztelése szakasz.

    A SUT adatbázis-környezetét regisztrálták Program.cs-ben. A tesztalkalmazás builder.ConfigureServices visszahívása az alkalmazás lesz végrehajtva. Ha más adatbázist szeretne használni a tesztekhez, mint az alkalmazás adatbázisa, az alkalmazás adatbázis-környezetét a builder.ConfigureServicesadatbázis-környezetre kell cserélni.

    A mintaalkalmazás megkeresi az adatbázis-környezet szolgáltatásleíróját, és a leíró használatával eltávolítja a szolgáltatásregisztrációt. A gyár ezután hozzáad egy új ApplicationDbContext, amely memórián belüli adatbázist használ a tesztekhez.

    Ha másik adatbázishoz szeretne csatlakozni, módosítsa a DbConnection. SQL Server tesztadatbázis használata:

  1. Az egyéni CustomWebApplicationFactory-t használja a tesztosztályokban. Az alábbi példa a gyárat használja a IndexPageTests osztályban:

    public class IndexPageTests :
        IClassFixture<CustomWebApplicationFactory<Program>>
    {
        private readonly HttpClient _client;
        private readonly CustomWebApplicationFactory<Program>
            _factory;
    
        public IndexPageTests(
            CustomWebApplicationFactory<Program> factory)
        {
            _factory = factory;
            _client = factory.CreateClient(new WebApplicationFactoryClientOptions
            {
                AllowAutoRedirect = false
            });
        }
    
    [TestClass]
    public class IndexPageTests
    {
        private static HttpClient _client;
        private static CustomWebApplicationFactory<Program>
            _factory;
    
        [ClassInitialize]
        public static void AssemblyInitialize(TestContext _)
        {
            _factory = new CustomWebApplicationFactory<Program>();
            _client = _factory.CreateClient(new WebApplicationFactoryClientOptions
            {
                AllowAutoRedirect = false
            });
        }
    
        [ClassCleanup(ClassCleanupBehavior.EndOfClass)]
        public static void AssemblyCleanup(TestContext _)
        {
            _factory.Dispose();
        }
    
    public class IndexPageTests
    {
    
        private HttpClient _client;
        private CustomWebApplicationFactory<Program>
            _factory;
    
        [SetUp]
        public void SetUp()
        {
            _factory = new CustomWebApplicationFactory<Program>();
            _client = _factory.CreateClient(new WebApplicationFactoryClientOptions
            {
                AllowAutoRedirect = false
            });
        }
    
        [TearDown]
        public void TearDown()
        {
            _factory.Dispose();
            _client.Dispose();
        }
    

    A mintaalkalmazás ügyfele úgy van konfigurálva, hogy megakadályozza a HttpClient átirányítások követését. Amint azt a Mock authentication szakasz későbbi szakasza ismerteti, ez lehetővé teszi a tesztek számára az alkalmazás első válaszának eredményének ellenőrzését. Az első válasz ezek közül sok tesztben egy átirányítás egy "Location" fejléccel.

  2. Egy tipikus teszt a HttpClient és segédmetenek használatával dolgozza fel a kérést és a választ:

    [Fact]
    public async Task Post_DeleteAllMessagesHandler_ReturnsRedirectToRoot()
    {
        // Arrange
        var defaultPage = await _client.GetAsync("/");
        var content = await HtmlHelpers.GetDocumentAsync(defaultPage);
    
        //Act
        var response = await _client.SendAsync(
            (IHtmlFormElement)content.QuerySelector("form[id='messages']"),
            (IHtmlButtonElement)content.QuerySelector("button[id='deleteAllBtn']"));
    
        // Assert
        Assert.Equal(HttpStatusCode.OK, defaultPage.StatusCode);
        Assert.Equal(HttpStatusCode.Redirect, response.StatusCode);
        Assert.Equal("/", response.Headers.Location.OriginalString);
    }
    
    [TestMethod]
    public async Task Post_DeleteAllMessagesHandler_ReturnsRedirectToRoot()
    {
        // Arrange
        var defaultPage = await _client.GetAsync("/");
        var content = await HtmlHelpers.GetDocumentAsync(defaultPage);
    
        //Act
        var response = await _client.SendAsync(
            (IHtmlFormElement)content.QuerySelector("form[id='messages']"),
            (IHtmlButtonElement)content.QuerySelector("button[id='deleteAllBtn']"));
    
        // Assert
        Assert.AreEqual(HttpStatusCode.OK, defaultPage.StatusCode);
        Assert.AreEqual(HttpStatusCode.Redirect, response.StatusCode);
        Assert.AreEqual("/", response.Headers.Location.OriginalString);
    }
    
    [Test]
    public async Task Post_DeleteAllMessagesHandler_ReturnsRedirectToRoot()
    {
        // Arrange
        var defaultPage = await _client.GetAsync("/");
        var content = await HtmlHelpers.GetDocumentAsync(defaultPage);
    
        //Act
        var response = await _client.SendAsync(
            (IHtmlFormElement)content.QuerySelector("form[id='messages']"),
            (IHtmlButtonElement)content.QuerySelector("button[id='deleteAllBtn']"));
    
        // Assert
        Assert.That(defaultPage.StatusCode, Is.EqualTo(HttpStatusCode.OK));
        Assert.That(response.StatusCode, Is.EqualTo(HttpStatusCode.Redirect));
        Assert.That(response.Headers.Location.OriginalString, Is.EqualTo("/"));
    }
    

A SUT-nak küldött POST-kéréseknek meg kell felelniük az alkalmazás adatvédelmi rendszerének automatikusan végrehajtott hamisítás elleni ellenőrzési folyamatának. A teszt POST-kérésének rendezéséhez a tesztalkalmazásnak a következőnek kell lennie:

  1. Kérjen egy kérelmet az oldalhoz.
  2. Elemezze az azonosítóhamisítás elleni cookie-t, és kérje le az érvényesítési jogkivonatot a választól.
  3. Végezze el a POST kérést az antiforgery cookie és az érvényesítési jogkivonattal.

A mintaalkalmazás SendAsync segédmetódusai (Helpers/HttpClientExtensions.cs) és a GetDocumentAsync segédmetódus (Helpers/HtmlHelpers.cs) az AngleSharp elemzővel kezelik az antiforgery-ellenőrzést az alábbi módszerekkel:

  • GetDocumentAsync: Megkapja a HttpResponseMessage, és visszaad egy IHtmlDocument. GetDocumentAsync egy olyan gyárat használ, amely az eredeti alapján készít elő egy HttpResponseMessage-t. További információért tekintse meg az AngleSharp dokumentációt.
  • SendAsync kiterjesztési metódusai a HttpClient összállítanak egy HttpRequestMessage-t, és meghívják a SendAsync(HttpRequestMessage)-t, hogy kérelmeket küldjenek a SUT-nak. A SendAsync túlterhelései elfogadják a HTML-űrlapot (IHtmlFormElement) és a következőket:
    • Az űrlap Küldés gombja (IHtmlElement)
    • Űrlapértékek gyűjtése (IEnumerable<KeyValuePair<string, string>>)
    • Küldés gomb (IHtmlElement) és űrlapértékek (IEnumerable<KeyValuePair<string, string>>)

AngleSharp egy harmadik féltől származó elemzési kódtár, amelyet bemutató célokra használnak, ebben a cikkben és a mintaalkalmazásban. Az AngleSharp nem támogatott vagy szükséges ASP.NET Core-alkalmazások integrációs teszteléséhez. Más elemzők is használhatók, például a Html Agility Pack (HAP). Egy másik módszer, ha olyan kódot írunk, amely közvetlenül kezeli az antiforgery rendszer kérés-ellenőrzési tokenjét és az antiforgery cookie-t. További információkért tekintse meg az antiforgery-ellenőrzésekről szóló cikket itt: AngleSharp vs Application Parts,.

A EF-Core memóriabeli adatbázis-szolgáltató korlátozott és alapszintű teszteléshez használható, de a SQLite-szolgáltató a memóriabeli teszteléshez ajánlott.

Lásd: Indítás kiterjesztése indítási szűrőkkel, amely bemutatja, hogyan konfigurálhat köztes szoftvereket IStartupFilterhasználatával, ami akkor hasznos, ha egy teszthez egyéni szolgáltatásra vagy köztes szoftverre van szükség.

Az ügyfél testreszabása a WithWebHostBuilder használatával

Ha egy tesztmetóduson belül további konfigurációra van szükség, WithWebHostBuilder létrehoz egy új WebApplicationFactory egy olyan IWebHostBuilder-vel, amelyet a konfiguráció további testreszab.

A mintakód meghívja WithWebHostBuilder, hogy a konfigurált szolgáltatásokat tesztcsomókkal cserélje le. További információért és a példák használatáért lásd a Modellszolgáltatások injektálása rész ebben a cikkben.

A mintaalkalmazás Post_DeleteMessageHandler_ReturnsRedirectToRoot tesztmetódusa a WithWebHostBuilderhasználatát mutatja be. Ez a teszt egy rekord törlését hajtja végre az adatbázisban egy űrlapbeküldés SUT-ban való aktiválásával.

Mivel a IndexPageTests osztály egy másik tesztje olyan műveletet hajt végre, amely törli az adatbázis összes rekordját, és a Post_DeleteMessageHandler_ReturnsRedirectToRoot metódus előtt futhat, az adatbázist ebben a tesztmetódusban újból el kell végezni, hogy a rendszer egy rekordot adjon meg a SUT számára a törléshez. A SUT-ban a messages űrlap első törlési gombjának kiválasztását a rendszer szimulálja a SUT-nak küldött kérésben:

[Fact]
public async Task Post_DeleteMessageHandler_ReturnsRedirectToRoot()
{
    // Arrange
    using (var scope = _factory.Services.CreateScope())
    {
        var scopedServices = scope.ServiceProvider;
        var db = scopedServices.GetRequiredService<ApplicationDbContext>();

        Utilities.ReinitializeDbForTests(db);
    }

    var defaultPage = await _client.GetAsync("/");
    var content = await HtmlHelpers.GetDocumentAsync(defaultPage);

    //Act
    var response = await _client.SendAsync(
        (IHtmlFormElement)content.QuerySelector("form[id='messages']"),
        (IHtmlButtonElement)content.QuerySelector("form[id='messages']")
            .QuerySelector("div[class='panel-body']")
            .QuerySelector("button"));

    // Assert
    Assert.Equal(HttpStatusCode.OK, defaultPage.StatusCode);
    Assert.Equal(HttpStatusCode.Redirect, response.StatusCode);
    Assert.Equal("/", response.Headers.Location.OriginalString);
}
[TestMethod]
public async Task Post_DeleteMessageHandler_ReturnsRedirectToRoot()
{
    // Arrange
    using (var scope = _factory.Services.CreateScope())
    {
        var scopedServices = scope.ServiceProvider;
        var db = scopedServices.GetRequiredService<ApplicationDbContext>();

        Utilities.ReinitializeDbForTests(db);
    }

    var defaultPage = await _client.GetAsync("/");
    var content = await HtmlHelpers.GetDocumentAsync(defaultPage);

    //Act
    var response = await _client.SendAsync(
        (IHtmlFormElement)content.QuerySelector("form[id='messages']"),
        (IHtmlButtonElement)content.QuerySelector("form[id='messages']")
            .QuerySelector("div[class='panel-body']")
            .QuerySelector("button"));

    // Assert
    Assert.AreEqual(HttpStatusCode.OK, defaultPage.StatusCode);
    Assert.AreEqual(HttpStatusCode.Redirect, response.StatusCode);
    Assert.AreEqual("/", response.Headers.Location.OriginalString);
}
[Test]
public async Task Post_DeleteMessageHandler_ReturnsRedirectToRoot()
{
    // Arrange
    using (var scope = _factory.Services.CreateScope())
    {
        var scopedServices = scope.ServiceProvider;
        var db = scopedServices.GetRequiredService<ApplicationDbContext>();

        Utilities.ReinitializeDbForTests(db);
    }

    var defaultPage = await _client.GetAsync("/");
    var content = await HtmlHelpers.GetDocumentAsync(defaultPage);

    //Act
    var response = await _client.SendAsync(
        (IHtmlFormElement)content.QuerySelector("form[id='messages']"),
        (IHtmlButtonElement)content.QuerySelector("form[id='messages']")
            .QuerySelector("div[class='panel-body']")
            .QuerySelector("button"));

    // Assert
    Assert.That(defaultPage.StatusCode, Is.EqualTo(HttpStatusCode.OK));
    Assert.That(response.StatusCode, Is.EqualTo(HttpStatusCode.Redirect));
    Assert.That(response.Headers.Location.OriginalString, Is.EqualTo("/"));
}

Ügyfélbeállítások

A WebApplicationFactoryClientOptions példányok létrehozásakor az alapértelmezett beállításokat és az elérhető beállításokat a HttpClient lapon tekinti meg.

Hozza létre a WebApplicationFactoryClientOptions osztályt, és adja át a CreateClient() metódusnak:

public class IndexPageTests :
    IClassFixture<CustomWebApplicationFactory<Program>>
{
    private readonly HttpClient _client;
    private readonly CustomWebApplicationFactory<Program>
        _factory;

    public IndexPageTests(
        CustomWebApplicationFactory<Program> factory)
    {
        _factory = factory;
        _client = factory.CreateClient(new WebApplicationFactoryClientOptions
        {
            AllowAutoRedirect = false
        });
    }
[TestClass]
public class IndexPageTests
{
    private static HttpClient _client;
    private static CustomWebApplicationFactory<Program>
        _factory;

    [ClassInitialize]
    public static void AssemblyInitialize(TestContext _)
    {
        _factory = new CustomWebApplicationFactory<Program>();
        _client = _factory.CreateClient(new WebApplicationFactoryClientOptions
        {
            AllowAutoRedirect = false
        });
    }

    [ClassCleanup(ClassCleanupBehavior.EndOfClass)]
    public static void AssemblyCleanup(TestContext _)
    {
        _factory.Dispose();
    }
public class IndexPageTests
{

    private HttpClient _client;
    private CustomWebApplicationFactory<Program>
        _factory;

    [SetUp]
    public void SetUp()
    {
        _factory = new CustomWebApplicationFactory<Program>();
        _client = _factory.CreateClient(new WebApplicationFactoryClientOptions
        {
            AllowAutoRedirect = false
        });
    }

    [TearDown]
    public void TearDown()
    {
        _factory.Dispose();
        _client.Dispose();
    }

MEGJEGYZÉS: A HTTPS-átirányítási figyelmeztetések elkerülése érdekében, amikor a HTTPS-átirányító köztes réteget használja, állítsa be a BaseAddress = new Uri("https://localhost")

Makettszolgáltatások injektálása

A szolgáltatások felülírhatók egy tesztben a ConfigureTestServices hívásával a gazdagép építőn. A felülbírált szolgáltatások konkrét teszt hatóköréhez a WithWebHostBuilder metódus használható egy hostkészítő lekérésére. Ez a következő tesztekben látható:

A minta SUT tartalmaz egy hatókörű szolgáltatást, amely egy idézetet ad vissza. Az indexlap kérésekor az idézet az Index lap rejtett mezőjébe van beágyazva.

Services/IQuoteService.cs:

public interface IQuoteService
{
    Task<string> GenerateQuote();
}

Services/QuoteService.cs:

// Quote ©1975 BBC: The Doctor (Tom Baker); Dr. Who: Planet of Evil
// https://www.bbc.co.uk/programmes/p00pyrx6
public class QuoteService : IQuoteService
{
    public Task<string> GenerateQuote()
    {
        return Task.FromResult<string>(
            "Come on, Sarah. We've an appointment in London, " +
            "and we're already 30,000 years late.");
    }
}

Program.cs:

services.AddScoped<IQuoteService, QuoteService>();

Pages/Index.cshtml.cs:

public class IndexModel : PageModel
{
    private readonly ApplicationDbContext _db;
    private readonly IQuoteService _quoteService;

    public IndexModel(ApplicationDbContext db, IQuoteService quoteService)
    {
        _db = db;
        _quoteService = quoteService;
    }

    [BindProperty]
    public Message Message { get; set; }

    public IList<Message> Messages { get; private set; }

    [TempData]
    public string MessageAnalysisResult { get; set; }

    public string Quote { get; private set; }

    public async Task OnGetAsync()
    {
        Messages = await _db.GetMessagesAsync();

        Quote = await _quoteService.GenerateQuote();
    }

Pages/Index.cs:

<input id="quote" type="hidden" value="@Model.Quote">

A SUT-alkalmazás futtatásakor a következő korrektúra jön létre:

<input id="quote" type="hidden" value="Come on, Sarah. We&#x27;ve an appointment in 
    London, and we&#x27;re already 30,000 years late.">

A szolgáltatás és az injektálás teszteléséhez integrációs tesztben a teszt egy ál-szolgáltatást injektál a SUT-ba. A modellszolgáltatás lecseréli az alkalmazás QuoteService-t a tesztalkalmazás által biztosított, TestQuoteServicenevű szolgáltatásra.

IntegrationTests.IndexPageTests.cs:

// Quote ©1975 BBC: The Doctor (Tom Baker); Pyramids of Mars
// https://www.bbc.co.uk/programmes/p00pys55
public class TestQuoteService : IQuoteService
{
    public Task<string> GenerateQuote()
    {
        return Task.FromResult(
            "Something's interfering with time, Mr. Scarman, " +
            "and time is my business.");
    }
}
// Quote ©1975 BBC: The Doctor (Tom Baker); Pyramids of Mars
// https://www.bbc.co.uk/programmes/p00pys55
public class TestQuoteService : IQuoteService
{
    public Task<string> GenerateQuote()
    {
        return Task.FromResult(
            "Something's interfering with time, Mr. Scarman, " +
            "and time is my business.");
    }
}
// Quote ©1975 BBC: The Doctor (Tom Baker); Pyramids of Mars
// https://www.bbc.co.uk/programmes/p00pys55
public class TestQuoteService : IQuoteService
{
    public Task<string> GenerateQuote()
    {
        return Task.FromResult(
            "Something's interfering with time, Mr. Scarman, " +
            "and time is my business.");
    }
}

ConfigureTestServices van meghívva, és a hatókörön belüli szolgáltatás regisztrálva van:

[Fact]
public async Task Get_QuoteService_ProvidesQuoteInPage()
{
    // Arrange
    var client = _factory.WithWebHostBuilder(builder =>
        {
            builder.ConfigureTestServices(services =>
            {
                services.AddScoped<IQuoteService, TestQuoteService>();
            });
        })
        .CreateClient();

    //Act
    var defaultPage = await client.GetAsync("/");
    var content = await HtmlHelpers.GetDocumentAsync(defaultPage);
    var quoteElement = content.QuerySelector("#quote");

    // Assert
    Assert.Equal("Something's interfering with time, Mr. Scarman, " +
        "and time is my business.", quoteElement.Attributes["value"].Value);
}
[TestMethod]
public async Task Get_QuoteService_ProvidesQuoteInPage()
{
    // Arrange
    var client = _factory.WithWebHostBuilder(builder =>
        {
            builder.ConfigureTestServices(services =>
            {
                services.AddScoped<IQuoteService, TestQuoteService>();
            });
        })
        .CreateClient();

    //Act
    var defaultPage = await client.GetAsync("/");
    var content = await HtmlHelpers.GetDocumentAsync(defaultPage);
    var quoteElement = content.QuerySelector("#quote");

    // Assert
    Assert.AreEqual("Something's interfering with time, Mr. Scarman, " +
        "and time is my business.", quoteElement.Attributes["value"].Value);
}
[Test]
public async Task Get_QuoteService_ProvidesQuoteInPage()
{
    // Arrange
    var client = _factory.WithWebHostBuilder(builder =>
        {
            builder.ConfigureTestServices(services =>
            {
                services.AddScoped<IQuoteService, TestQuoteService>();
            });
        })
        .CreateClient();

    //Act
    var defaultPage = await client.GetAsync("/");
    var content = await HtmlHelpers.GetDocumentAsync(defaultPage);
    var quoteElement = content.QuerySelector("#quote");

    // Assert
    Assert.That(quoteElement.Attributes["value"].Value, Is.EqualTo(
        "Something's interfering with time, Mr. Scarman, " +
        "and time is my business."));
}

A teszt végrehajtása során létrehozott jelölések a TestQuoteServiceáltal megadott idézőjelszöveget tükrözik, ezért az állítás sikeres.

<input id="quote" type="hidden" value="Something&#x27;s interfering with time, 
    Mr. Scarman, and time is my business.">

Szimulált hitelesítés

Az AuthTests osztály tesztjei ellenőrzik, hogy egy biztonságos végpont:

  • Átirányít egy nem hitelesített felhasználót az alkalmazás bejelentkezési oldalára.
  • Egy hitelesített felhasználó tartalmát adja vissza.

A SUT-ban a /SecurePage lap a AuthorizePage konvenciót használja, hogy egy AuthorizeFilter-t alkalmazzon a lapra. További információkért lásd a Razor oldalak engedélyezési konvencióit.

services.AddRazorPages(options =>
{
    options.Conventions.AuthorizePage("/SecurePage");
});

A Get_SecurePageRedirectsAnUnauthenticatedUser tesztben egy WebApplicationFactoryClientOptions úgy van beállítva, hogy letiltsa az átirányításokat azáltal, hogy AllowAutoRedirect-t false-ra állítja.

[Fact]
public async Task Get_SecurePageRedirectsAnUnauthenticatedUser()
{
    // Arrange
    var client = _factory.CreateClient(
        new WebApplicationFactoryClientOptions
        {
            AllowAutoRedirect = false
        });

    // Act
    var response = await client.GetAsync("/SecurePage");

    // Assert
    Assert.Equal(HttpStatusCode.Redirect, response.StatusCode);
    Assert.StartsWith("http://localhost/Identity/Account/Login",
        response.Headers.Location.OriginalString);
}
[TestMethod]
public async Task Get_SecurePageRedirectsAnUnauthenticatedUser()
{
    // Arrange
    var client = _factory.CreateClient(
        new WebApplicationFactoryClientOptions
        {
            AllowAutoRedirect = false
        });

    // Act
    var response = await client.GetAsync("/SecurePage");

    // Assert
    Assert.AreEqual(HttpStatusCode.Redirect, response.StatusCode);
    StringAssert.StartsWith(response.Headers.Location.OriginalString, "http://localhost/Identity/Account/Login");
}
[Test]
public async Task Get_SecurePageRedirectsAnUnauthenticatedUser()
{
    // Arrange
    var client = _factory.CreateClient(
        new WebApplicationFactoryClientOptions
        {
            AllowAutoRedirect = false
        });

    // Act
    var response = await client.GetAsync("/SecurePage");

    // Assert
    Assert.That(response.StatusCode, Is.EqualTo(HttpStatusCode.Redirect));
    Assert.That(response.Headers.Location.OriginalString, Does.StartWith("http://localhost/Identity/Account/Login"));
}

Az ügyfél átirányításának megakadályozásával a következő ellenőrzéseket hajthatja végre:

  • A SUT által visszaadott állapotkód a várt HttpStatusCode.Redirect eredményen ellenőrizhető, nem pedig a bejelentkezési lapra való átirányítás utáni végleges állapotkóddal, amely HttpStatusCode.OK.
  • A válaszfejlécek közül a Location fejléc értékét ellenőrzik annak megállapítására, hogy http://localhost/Identity/Account/Login-el kezdődik-e, nem pedig a végleges bejelentkezési oldal válaszát, ahol a Location fejléc nem lenne jelen.

A tesztalkalmazás szimulálhat egy AuthenticationHandler<TOptions>ConfigureTestServices-ben a hitelesítés és az engedélyezés szempontjainak teszteléséhez. Egy minimális forgatókönyv AuthenticateResult.Success-t ad vissza.

public class TestAuthHandler : AuthenticationHandler<AuthenticationSchemeOptions>
{
    public TestAuthHandler(IOptionsMonitor<AuthenticationSchemeOptions> options,
        ILoggerFactory logger, UrlEncoder encoder)
        : base(options, logger, encoder)
    {
    }

    protected override Task<AuthenticateResult> HandleAuthenticateAsync()
    {
        var claims = new[] { new Claim(ClaimTypes.Name, "Test user") };
        var identity = new ClaimsIdentity(claims, "Test");
        var principal = new ClaimsPrincipal(identity);
        var ticket = new AuthenticationTicket(principal, "TestScheme");

        var result = AuthenticateResult.Success(ticket);

        return Task.FromResult(result);
    }
}
public class TestAuthHandler : AuthenticationHandler<AuthenticationSchemeOptions>
{
    public TestAuthHandler(IOptionsMonitor<AuthenticationSchemeOptions> options,
        ILoggerFactory logger, UrlEncoder encoder)
        : base(options, logger, encoder)
    {
    }

    protected override Task<AuthenticateResult> HandleAuthenticateAsync()
    {
        var claims = new[] { new Claim(ClaimTypes.Name, "Test user") };
        var identity = new ClaimsIdentity(claims, "Test");
        var principal = new ClaimsPrincipal(identity);
        var ticket = new AuthenticationTicket(principal, "TestScheme");

        var result = AuthenticateResult.Success(ticket);

        return Task.FromResult(result);
    }
}
public class TestAuthHandler : AuthenticationHandler<AuthenticationSchemeOptions>
{
    public TestAuthHandler(IOptionsMonitor<AuthenticationSchemeOptions> options,
        ILoggerFactory logger, UrlEncoder encoder)
        : base(options, logger, encoder)
    {
    }

    protected override Task<AuthenticateResult> HandleAuthenticateAsync()
    {
        var claims = new[] { new Claim(ClaimTypes.Name, "Test user") };
        var identity = new ClaimsIdentity(claims, "Test");
        var principal = new ClaimsPrincipal(identity);
        var ticket = new AuthenticationTicket(principal, "TestScheme");

        var result = AuthenticateResult.Success(ticket);

        return Task.FromResult(result);
    }
}

A TestAuthHandler egy felhasználó hitelesítésére hívják meg, amikor a hitelesítési séma TestScheme van beállítva, ahol a AddAuthentication regisztrálva van a ConfigureTestServices-ra. Fontos, hogy a TestScheme séma megfeleljen az alkalmazás által várt sémának. Ellenkező esetben a hitelesítés nem működik.

[Fact]
public async Task Get_SecurePageIsReturnedForAnAuthenticatedUser()
{
    // Arrange
    var client = _factory.WithWebHostBuilder(builder =>
        {
            builder.ConfigureTestServices(services =>
            {
                services.AddAuthentication(defaultScheme: "TestScheme")
                    .AddScheme<AuthenticationSchemeOptions, TestAuthHandler>(
                        "TestScheme", options => { });
            });
        })
        .CreateClient(new WebApplicationFactoryClientOptions
        {
            AllowAutoRedirect = false,
        });

    client.DefaultRequestHeaders.Authorization =
        new AuthenticationHeaderValue(scheme: "TestScheme");

    //Act
    var response = await client.GetAsync("/SecurePage");

    // Assert
    Assert.Equal(HttpStatusCode.OK, response.StatusCode);
}
[TestMethod]
public async Task Get_SecurePageIsReturnedForAnAuthenticatedUser()
{
    // Arrange
    var client = _factory.WithWebHostBuilder(builder =>
    {
        builder.ConfigureTestServices(services =>
        {
            services.AddAuthentication(defaultScheme: "TestScheme")
                .AddScheme<AuthenticationSchemeOptions, TestAuthHandler>(
                    "TestScheme", options => { });
        });
    })
        .CreateClient(new WebApplicationFactoryClientOptions
        {
            AllowAutoRedirect = false,
        });

    client.DefaultRequestHeaders.Authorization =
        new AuthenticationHeaderValue(scheme: "TestScheme");

    //Act
    var response = await client.GetAsync("/SecurePage");

    // Assert
    Assert.AreEqual(HttpStatusCode.OK, response.StatusCode);
}
[Test]
public async Task Get_SecurePageIsReturnedForAnAuthenticatedUser()
{
    // Arrange
    var client = _factory.WithWebHostBuilder(builder =>
    {
        builder.ConfigureTestServices(services =>
        {
            services.AddAuthentication(defaultScheme: "TestScheme")
                .AddScheme<AuthenticationSchemeOptions, TestAuthHandler>(
                    "TestScheme", options => { });
        });
    })
        .CreateClient(new WebApplicationFactoryClientOptions
        {
            AllowAutoRedirect = false,
        });

    client.DefaultRequestHeaders.Authorization =
        new AuthenticationHeaderValue(scheme: "TestScheme");

    //Act
    var response = await client.GetAsync("/SecurePage");

    // Assert
    Assert.That(response.StatusCode, Is.EqualTo(HttpStatusCode.OK));
}

A WebApplicationFactoryClientOptionskapcsolatos további információkért lásd a Ügyfélbeállítások című szakaszt.

A köztes szoftver hitelesítésének alapszintű tesztjei

A Köztes szoftver hitelesítésének alapszintű teszteléséhez tekintse meg ezt a GitHub-adattárat. A tesztforgatókönyvre jellemző tesztkiszolgálót tartalmaz.

A környezet beállítása

Állítsa be a környezetet az egyéni alkalmazás-előállítóban:

public class CustomWebApplicationFactory<TProgram>
    : WebApplicationFactory<TProgram> where TProgram : class
{
    protected override void ConfigureWebHost(IWebHostBuilder builder)
    {
        builder.ConfigureServices(services =>
        {
            var dbContextDescriptor = services.SingleOrDefault(
                d => d.ServiceType == 
                    typeof(IDbContextOptionsConfiguration<ApplicationDbContext>));

            services.Remove(dbContextDescriptor);

            var dbConnectionDescriptor = services.SingleOrDefault(
                d => d.ServiceType ==
                    typeof(DbConnection));

            services.Remove(dbConnectionDescriptor);

            // Create open SqliteConnection so EF won't automatically close it.
            services.AddSingleton<DbConnection>(container =>
            {
                var connection = new SqliteConnection("DataSource=:memory:");
                connection.Open();

                return connection;
            });

            services.AddDbContext<ApplicationDbContext>((container, options) =>
            {
                var connection = container.GetRequiredService<DbConnection>();
                options.UseSqlite(connection);
            });
        });

        builder.UseEnvironment("Development");
    }
}
public class CustomWebApplicationFactory<TProgram>
    : WebApplicationFactory<TProgram> where TProgram : class
{
    protected override void ConfigureWebHost(IWebHostBuilder builder)
    {
        builder.ConfigureServices(services =>
        {
            var dbContextDescriptor = services.SingleOrDefault(
                d => d.ServiceType ==
                    typeof(IDbContextOptionsConfiguration<ApplicationDbContext>));

            services.Remove(dbContextDescriptor);

            var dbConnectionDescriptor = services.SingleOrDefault(
                d => d.ServiceType ==
                    typeof(DbConnection));

            services.Remove(dbConnectionDescriptor);

            // Create open SqliteConnection so EF won't automatically close it.
            services.AddSingleton<DbConnection>(container =>
            {
                var connection = new SqliteConnection("DataSource=:memory:");
                connection.Open();

                return connection;
            });

            services.AddDbContext<ApplicationDbContext>((container, options) =>
            {
                var connection = container.GetRequiredService<DbConnection>();
                options.UseSqlite(connection);
            });
        });

        builder.UseEnvironment("Development");
    }
}
public class CustomWebApplicationFactory<TProgram>
    : WebApplicationFactory<TProgram> where TProgram : class
{
    protected override void ConfigureWebHost(IWebHostBuilder builder)
    {
        builder.ConfigureServices(services =>
        {
            var dbContextDescriptor = services.SingleOrDefault(
                d => d.ServiceType ==
                    typeof(IDbContextOptionsConfiguration<ApplicationDbContext>));

            services.Remove(dbContextDescriptor);

            var dbConnectionDescriptor = services.SingleOrDefault(
                d => d.ServiceType ==
                    typeof(DbConnection));

            services.Remove(dbConnectionDescriptor);

            // Create open SqliteConnection so EF won't automatically close it.
            services.AddSingleton<DbConnection>(container =>
            {
                var connection = new SqliteConnection("DataSource=:memory:");
                connection.Open();

                return connection;
            });

            services.AddDbContext<ApplicationDbContext>((container, options) =>
            {
                var connection = container.GetRequiredService<DbConnection>();
                options.UseSqlite(connection);
            });
        });

        builder.UseEnvironment("Development");
    }
}

Hogyan következtet a tesztinfrastruktúra az alkalmazás tartalomgyökerének elérési útjára?

A WebApplicationFactory konstruktor meghatározza az alkalmazás tartalom gyökér elérési útját úgy, hogy a WebApplicationFactoryContentRootAttribute szerelvény TEntryPointkulccsal a System.Reflection.Assembly.FullName-t keres az integrációs teszteket tartalmazó szerelvényen. Ha nem található a megfelelő kulccsal rendelkező attribútum, WebApplicationFactory visszatér a megoldásfájl (.sln) kereséséhez, és hozzáadja a TEntryPoint szerelvény nevét a megoldáskönyvtárhoz. Az alkalmazás gyökérkönyvtára (a tartalom gyökérútvonala) a nézetek és tartalomfájlok felderítésére szolgál.

Árnyékmásolás letiltása

Az árnyékmásolás miatt a tesztek a kimeneti könyvtártól eltérő könyvtárban futnak. Ha a tesztek a fájlok Assembly.Location-hoz viszonyított betöltésére támaszkodnak, és problémákat tapasztalunk, előfordulhat, hogy le kell tiltani az árnyékmásolást.

Ha le szeretné tiltani az árnyékmásolást az xUnit használatakor, hozzon létre egy xunit.runner.json fájlt a tesztprojekt könyvtárában a megfelelő konfigurációs beállítással:

{
  "shadowCopy": false
}

Objektumok ártalmatlanítása

A IClassFixture implementáció tesztjeinek végrehajtása után TestServer és HttpClient megsemmisülnek, amikor az xUnit megsemmisíti a WebApplicationFactory-at. Ha a fejlesztő által példányosított objektumok ártalmatlanítást igényelnek, azokat a IClassFixture implementációban kell megsemmisíteni. További információért lásd: Dispose metódus implementálása.

A TestClass tesztjeinek végrehajtása után az MSTest a TestServer metódusban megsemmisíti a HttpClient-et, ekkor pedig a WebApplicationFactory és ClassCleanup is megsemmisülnek. Ha a fejlesztő által példányosított objektumok megsemmisítést igényelnek, a ClassCleanup metódusban kell megsemmisíteni őket. További információért lásd: Dispose metódus implementálása.

A tesztosztály tesztjeinek végrehajtása után az NUnit megsemmisíti a TestServer a HttpClient metódusban. Ha a fejlesztő által példányosított objektumok megsemmisítést igényelnek, a TearDown metódusban kell megsemmisíteni őket. További információért lásd: Dispose metódus implementálása.

Integrációs tesztek mintája

A mintaalkalmazás két alkalmazásból áll:

Alkalmazás Projektmappa Leírás
Üzenetküldő alkalmazás (a SUT) src/RazorPagesProject Lehetővé teszi a felhasználó számára, hogy hozzáadjon, töröljön egyet, törölje az összeset, és elemezze az üzeneteket.
Alkalmazás tesztelése tests/RazorPagesProject.Tests A SUT integrációs tesztelésére szolgál.

A tesztek egy IDE beépített tesztfunkcióival futtathatók, például Visual Studio. Ha Visual Studio Code vagy parancssort használ, hajtsa végre a következő parancsot egy parancssorban a tests/RazorPagesProject.Tests könyvtárban:

dotnet test

Üzenetküldő alkalmazás (SUT) szervezete

A SUT egy Razor Pages üzenetrendszer, amely a következő jellemzőkkel rendelkezik:

  • Az alkalmazás indexoldala (Pages/Index.cshtml és Pages/Index.cshtml.cs) felhasználói felületi és lapmodellezési módszereket biztosít az üzenetek hozzáadásának, törlésének és elemzésének szabályozásához (átlagos szavak üzenetenként).
  • Az üzeneteket a Message osztály (Data/Message.cs) írja le két tulajdonságokkal: Id (kulcs) és Text (üzenet). A Text tulajdonság megadása kötelező, és legfeljebb 200 karakter hosszúságú lehet.
  • Az üzenetek tárolása Entity Framework memórián belüli adatbázisának† használatával történik.
  • Az alkalmazás egy adatelérési réteget (DAL) tartalmaz az adatbázis környezeti osztályában, AppDbContext (Data/AppDbContext.cs).
  • Ha az adatbázis üres az alkalmazás indításakor, az üzenettároló három üzenettel lesz inicializálva.
  • Az alkalmazás tartalmaz egy /SecurePage, amelyet csak hitelesített felhasználó érhet el.

†Az EF-cikk, Az InMemorytesztelése című cikk bemutatja, hogyan használható memóriabeli adatbázis az MSTesttel végzett tesztekhez. Ez a témakör az xUnit tesztelési keretrendszert használja. A különböző tesztelési keretrendszerek tesztelési fogalmai és tesztelési implementációi hasonlóak, de nem azonosak.

Bár az alkalmazás nem használja az adattár mintát, és nem hatékony példa a Munkaegység (UoW) mintára, a Razor Pages támogatja ezeket a fejlesztési mintákat. További információ: Az infrastruktúra perzisztenciaréteg tervezése és Tesztvezérlő logika (a minta implementálja az adattár mintát).

Tesztalkalmazás szervezése

A tesztalkalmazás egy konzolalkalmazás a tests/RazorPagesProject.Tests könyvtárban.

Alkalmazáskönyvtár tesztelése Leírás
AuthTests A következő tesztelési módszereket tartalmazza:
  • Biztonságos lap elérése hitelesítés nélküli felhasználó által.
  • Biztonságos lap elérése egy hitelesített felhasználó által egy ál-AuthenticationHandler<TOptions>.
  • GitHub-felhasználói profil beszerzése és a profil felhasználói bejelentkezésének ellenőrzése.
BasicTests Az útválasztás és a tartalomtípus tesztelési módszerét tartalmazza.
IntegrationTests Az Index lap integrációs tesztjeit tartalmazza egyéni WebApplicationFactory osztály használatával.
Helpers/Utilities
  • Utilities.cs tartalmazza az adatbázis tesztelési adatokkal való üzembe helyezésekor használt InitializeDbForTests metódust.
  • HtmlHelpers.cs egy AngleSharp IHtmlDocument visszaadására szolgáló metódust biztosít a tesztelési módszerek számára.
  • HttpClientExtensions.cs túlterhelést biztosít a SendAsync számára, hogy kéréseket küldjön a SUT-nak.

A tesztelési keretrendszer xUnit. Az integrációs teszteket a Microsoft.AspNetCore.TestHostsegítségével hajtják végre, amely magában foglalja a TestServer-et. Mivel a Microsoft.AspNetCore.Mvc.Testing csomag a tesztgazda és a tesztkiszolgáló konfigurálására szolgál, a TestHost és TestServer csomagok nem igényelnek közvetlen csomaghivatkozásokat a tesztalkalmazás projektfájljában vagy fejlesztői konfigurációjában a tesztalkalmazásban.

Az integrációs tesztek általában kis adatkészletet igényelnek az adatbázisban a teszt végrehajtása előtt. A törlési teszt például egy adatbázisrekord törlését kéri, ezért az adatbázisnak legalább egy rekorddal kell rendelkeznie ahhoz, hogy a törlési kérelem sikeres legyen.

A mintaalkalmazás három üzenettel tölti fel az adatbázist a Utilities.cs-ben, amelyeket a tesztek futtatásakor használhatnak.

public static void InitializeDbForTests(ApplicationDbContext db)
{
    db.Messages.AddRange(GetSeedingMessages());
    db.SaveChanges();
}

public static void ReinitializeDbForTests(ApplicationDbContext db)
{
    db.Messages.RemoveRange(db.Messages);
    InitializeDbForTests(db);
}

public static List<Message> GetSeedingMessages()
{
    return new List<Message>()
    {
        new Message(){ Text = "TEST RECORD: You're standing on my scarf." },
        new Message(){ Text = "TEST RECORD: Would you like a jelly baby?" },
        new Message(){ Text = "TEST RECORD: To the rational mind, " +
            "nothing is inexplicable; only unexplained." }
    };
}

A SUT adatbázis-környezetét regisztrálták Program.cs-ben. A tesztalkalmazás builder.ConfigureServices visszahívása az alkalmazás lesz végrehajtva. Ha másik adatbázist szeretne használni a tesztekhez, az alkalmazás adatbázis-környezetét le kell cserélni a builder.ConfigureServices-ban. A további információkért lásd a WebApplicationFactory testreszabása szakaszt.

További erőforrások