Not
Bu sayfaya erişim yetkilendirme gerektiriyor. Oturum açmayı veya dizinleri değiştirmeyi deneyebilirsiniz.
Bu sayfaya erişim yetkilendirme gerektiriyor. Dizinleri değiştirmeyi deneyebilirsiniz.
Jos van der Til, Martin Costello ve Javier Calvarro Nelson tarafından.
Tümleştirme testleri, bir uygulamanın bileşenlerinin veritabanı, dosya sistemi ve ağ gibi uygulamanın destekleyici altyapısını içeren bir düzeyde düzgün çalışmasını sağlar. ASP.NET Core, test web konağı ve bellek içi test sunucusu ile birim test çerçevesi kullanarak tümleştirme testlerini destekler.
Bu makalede, birim testleri hakkında temel bilgiler olduğu varsayılır. Test kavramlarını bilmiyorsanız .NET'te test etme makalesine ve bağlantılı içeriğine bakın.
Örnek kodu görüntüleme veya indirme (indirme)
Örnek uygulama bir Razor Pages uygulamasıdır ve Razor Sayfalar hakkında temel bir anlayış olduğunu varsayar. Pages'ı tanımıyorsanız, Razor aşağıdaki makalelere bakın.
SPA'ları test etme amacıyla tarayıcıyı otomatikleştirebilen .NET için Playwright gibi bir araç öneririz.
Tümleştirme testlerine giriş
Tümleştirme testleri, bir uygulamanın bileşenlerini birim testlerinden daha geniş bir düzeyde değerlendirir. Birim testleri, tek tek sınıf yöntemleri gibi yalıtılmış yazılım bileşenlerini test etmek için kullanılır. Tümleştirme testleri, iki veya daha fazla uygulama bileşeninin birlikte çalışarak beklenen bir sonuç elde ettiğini ve büyük olasılıkla bir isteği tam olarak işlemek için gereken her bileşenin de dahil olduğunu onaylar.
Bu daha geniş testler, genellikle aşağıdaki bileşenler de dahil olmak üzere uygulamanın altyapısını ve tüm çerçevesini test etmek için kullanılır:
- Database
- Dosya sistemi
- Ağ gereçleri
- İstek-yanıt işlem hattı
Birim testleri, altyapı bileşenleri yerine sahte veya sahte nesneler olarak bilinen fabrikatlanmış bileşenleri kullanır.
Birim testlerinin aksine tümleştirme testleri:
- Uygulamanın üretimde kullandığı gerçek bileşenleri kullanın.
- Daha fazla kod ve veri işleme gerektirir.
- Çalıştırılması daha uzun sürer.
Bu nedenle, tümleştirme testlerinin kullanımını en önemli altyapı senaryolarıyla sınırlayın. Bir davranış birim testi veya tümleştirme testi kullanılarak test edilebiliyorsa birim testini seçin.
Tümleştirme testlerinin tartışmalarında, test edilen projeye genellikle Test Altındaki Sistem veya kısaca "SUT" adı verilir. "SUT" bu makalede test edilen ASP.NET Core uygulamasına başvurmak için kullanılır.
Veritabanları ve dosya sistemleriyle verilerin ve dosya erişiminin her permütasyonu için tümleştirme testleri yazmayın. Bir uygulama genelinde veritabanları ve dosya sistemleriyle kaç yerde etkileşim kurduğundan bağımsız olarak, odaklanmış bir dizi okuma, yazma, güncelleştirme ve silme tümleştirme testi genellikle veritabanı ve dosya sistemi bileşenlerini yeterince test edebilmektedir. Bu bileşenlerle etkileşim kuran yöntem mantığının rutin testleri için birim testlerini kullanın. Birim testlerinde, altyapı sahte nesneleri veya taklitlerinin kullanımı, testlerin daha hızlı yürütülmesine olanak tanır.
ASP.NET Core tümleştirme testleri
ASP.NET Core'daki tümleştirme testleri aşağıdakileri gerektirir:
- Testleri içermek ve yürütmek için bir test projesi kullanılır. Test projesinin SUT'a referansı vardır.
- Test projesi SUT için bir test web konağı oluşturur ve SUT ile istekleri ve yanıtları işlemek için bir test sunucusu istemcisi kullanır.
- Testleri yürütmek ve test sonuçlarını raporlamak için bir test çalıştırıcısı kullanılır.
Tümleştirme testleri, her zamanki Düzenleme, Eylem ve Onay testi adımlarını içeren bir dizi olayı izler:
- SUT'nin web konağı yapılandırıldı.
- Uygulamaya istek göndermek için bir test sunucusu istemcisi oluşturulur.
- Test düzenleme adımı yürütülür: Test uygulaması bir istek hazırlar.
- Eylem test adımı yürütülür: İstemci isteği gönderir ve yanıtı alır.
- Assert test adımı yürütülür: Gerçek yanıt, beklenen yanıta göre geçti veya başarısız olarak doğrulanır.
- Tüm testler yürütülene kadar işlem devam eder.
- Test sonuçları bildirilir.
Genellikle test web konağı, test çalıştırmaları için uygulamanın normal web ana bilgisayarından farklı yapılandırılır. Örneğin, testler için farklı bir veritabanı veya farklı uygulama ayarları kullanılabilir.
Test web konağı ve bellek içi test sunucusu ()TestServer gibi altyapı bileşenleri Microsoft.AspNetCore.Mvc.Testing paketi tarafından sağlanır veya yönetilir. Bu paketin kullanımı test oluşturma ve yürütmeyi kolaylaştırır.
Paket Microsoft.AspNetCore.Mvc.Testing aşağıdaki görevleri işler:
- Bağımlılık dosyasını (
.deps) SUT'den test projesininbindizinine kopyalar. - testler yürütürken statik dosyaların ve sayfaların/görünümlerin bulunması için içerik kökünü SUT'nin proje köküne ayarlar.
- WebApplicationFactory sınıfını ile SUT önyüklemesini kolaylaştırmak için sağlar.
Birim testleri belgelerinde bir test projesi ve test çalıştırıcısının nasıl ayarlanacağı ve testleri çalıştırma hakkında ayrıntılı yönergelerin yanı sıra testleri ve test sınıflarını adlandırmaya yönelik öneriler açıklanmaktadır.
Birim testlerini ve tümleştirme testlerini ayrı projelere ayırın. Testleri ayırma:
- Altyapı testi bileşenlerinin birim testlerine yanlışlıkla dahil edilmesini önlemeye yardımcı olur.
- Hangi test kümesinin çalıştırıldığı üzerinde denetime izin verir.
Sayfalar uygulamalarının ve MVC uygulamalarının testlerinin Razor yapılandırması arasında neredeyse hiçbir fark yoktur. Tek fark, testlerin nasıl adlandırıldığındandır.
Razor Sayfalar uygulamasında, sayfa uç noktalarının testleri genellikle sayfa modeli sınıfından (örneğin, IndexPageTests Dizin sayfası için bileşen tümleştirmesini test etmek için) adlandırılır. MVC uygulamasında testler, genellikle test ettikleri denetleyici sınıflarına göre düzenlenir ve bu denetleyicilerin adlarını alır (örneğin, HomeControllerTests bileşen tümleştirmesini Home denetleyicisi için test etmek üzere).
Uygulama önkoşullarını test edin
Test projesinin aşağıdakileri yapması gerekir:
-
Microsoft.AspNetCore.Mvc.Testingpaketine başvurun. - Proje dosyasında (
<Project Sdk="Microsoft.NET.Sdk.Web">) Web SDK'sını belirtin.
Bu önkoşullar örnek uygulamada görülebilir.
tests/RazorPagesProject.Tests/RazorPagesProject.Tests.csproj Dosyayı inceleyin. Örnek uygulama xUnit test çerçevesini ve AngleSharp ayrıştırıcı kitaplığını kullandığından örnek uygulama da aşağıdakilere başvurur:
Sürüm 2.4.2 veya üzerini kullanan xunit.runner.visualstudio uygulamalarda, test projesi Microsoft.NET.Test.Sdk paketine başvurmalıdır.
Entity Framework Core, testlerde de kullanılır. GitHub'daki proje dosyasına bakın.
SUT ortamı
SUT'nin ortamı ayarlı değilse, ortam varsayılan olarak olarak Developmentayarlanır.
Varsayılan WebApplicationFactory ile temel testler
WebApplicationFactory<TEntryPoint> tümleştirme testlerine yönelik bir TestServer oluşturmak için kullanılır.
TEntryPoint genellikle Program.csSUT'nin giriş noktası sınıfıdır.
Test sınıfları, sınıfın testleri içerdiğini belirtmek ve sınıftaki testler arasında paylaşılan nesne örnekleri sağlamak için bir sınıf fikstür arabirimi (IClassFixture) uygular.
Aşağıdaki test sınıfı, SUT'yi önyüklemek için BasicTests kullanır ve bir test yöntemine WebApplicationFactory sağlamak için HttpClient öğesini kullanır, Get_EndpointsReturnSuccessAndCorrectContentType. Yöntem, yanıt durum kodunun başarılı (200-299) olduğunu ve Content-Type üst bilgisinin birkaç uygulama sayfası için text/html; charset=utf-8 olduğunu doğrular.
CreateClient(), otomatik olarak yeniden yönlendirmeleri izleyen ve tanımlama bilgilerini işleyen bir HttpClient örneği oluşturur.
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"));
}
}
Varsayılan olarak, Genel Veri Koruma Yönetmeliği onay ilkesi etkinleştirildiğinde gerekli olmayan tanımlama bilgileri istekler arasında korunmaz. Gerekli olmayan tanımlama bilgilerini, örneğin TempData sağlayıcısı tarafından kullanılanları, korumak için bunları testlerinizde önemli olarak işaretleyin. Bir cookie öğesini önemli olarak işaretleme talimatları için, bkz Temel tanımlama bilgileri.
AngleSharp ve Application Parts antiforgery kontrollerinin karşılaştırılması
Bu makalede, antiforgery denetimlerini işlemek için AngleSharp ayrıştırıcısı kullanılır; bu, sayfaları yükleyip HTML'yi ayrıştırarak gerçekleştirilir. Denetleyici ve Razor Sayfalar görünümlerinin uç noktalarını, tarayıcıda nasıl görüntülendiklerine önem vermeden daha alt düzeyde test etmek için Application Parts kullanmayı göz önünde bulundurun.
Uygulama Bölümleri yaklaşımı, uygulamaya gerekli değerleri almak için JSON isteklerinde bulunmak için kullanılabilecek bir denetleyici veya Razor Sayfa ekler. Daha fazla bilgi için Antiforgery Kullanarak ASP.NET Core Kaynaklarının Tümleştirme Testi ve Uygulama Parçaları blogunu ve Martin Costello tarafından oluşturulan ilişkili GitHub deposunu inceleyin.
WebApplicationFactory'i özelleştirme
Web sunucu yapılandırması, bir veya daha fazla özel fabrika oluşturmak için WebApplicationFactory<TEntryPoint> öğesinden devralınmak suretiyle test sınıflarından bağımsız olarak oluşturulabilir.
WebApplicationFactorysınıfından devralın ve ConfigureWebHost yöntemini geçersiz kılın. IWebHostBuilder hizmet koleksiyonununIWebHostBuilder.ConfigureServicesile yapılandırılmasına izin verir.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"); } }Veritabanı tohumlama, örnek uygulamada
InitializeDbForTestsyöntemi ile gerçekleştirilir. Yöntemi, Tümleştirme testleri örneği: Test uygulaması kuruluşu bölümünde açıklanmıştır.SUT'nin veritabanı bağlamı
Program.csiçinde kaydedilir. Test uygulamasınınbuilder.ConfigureServicesgeri çağırması, yürütülür. Testlerde uygulamanın veritabanından farklı bir veritabanı kullanmak için, uygulamanın veritabanı bağlamı içindebuilder.ConfigureServicesdeğiştirilmelidir.Örnek uygulama, veritabanı bağlamı için hizmet tanımlayıcısını bulur ve hizmet kaydını kaldırmak için tanımlayıcıyı kullanır. Ardından fabrika, testler için bellek içi veritabanı kullanan
ApplicationDbContextyeni bir ekliyor.Farklı bir veritabanına bağlanmak için öğesini
DbConnectiondeğiştirin. SQL Server test veritabanını kullanmak için:- Proje dosyasındaKi
Microsoft.EntityFrameworkCore.SqlServerNuGet paketine başvurun. -
UseInMemoryDatabaseçağrısı yapın.
- Proje dosyasındaKi
Test sınıflarında özel
CustomWebApplicationFactoryöğesini kullanın. Aşağıdaki örnek,IndexPageTestssınıfındaki fabrikayı kullanır: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(); }Örnek uygulamanın istemcisi,
HttpClient'nin yeniden yönlendirmeleri takip etmemesi için yapılandırılır. Sahte kimlik doğrulaması bölümünde daha sonra açıklandığı gibi bu, testlerin uygulamanın ilk yanıtının sonucunu denetlemesine izin verir. İlk yanıt, bu testlerin çoğundaLocationbaşlık içeren bir yeniden yönlendirmedir.Tipik bir test, isteği ve yanıtı işlemek için
HttpClientve yardımcı yöntemlerini kullanır.[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("/")); }
SUT'ye yapılan herhangi bir POST isteği, uygulamanın veri koruma önleme sistemi tarafından otomatik olarak yapılan kötü amaçlı yazılımdan koruma denetimini karşılamalıdır. Testin POST isteğini düzenlemek için test uygulamasının şunları yapması gerekir:
- Sayfa için bir istekte bulun.
- Yanıttan sahtecilik önleme (cookie) ve doğrulama belirtecini ayrıştırın.
- POST isteğini sahteciliğe cookie karşı koruma ve istek doğrulama belirteci ile gerçekleştirin.
Örnek
-
GetDocumentAsync: HttpResponseMessage alır ve birIHtmlDocumentdöndürür.GetDocumentAsync, özgün dayalı birHttpResponseMessagehazırlayan bir fabrika kullanır. Daha fazla bilgi için AngleSharp belgelerine bakın. -
SendAsyncuzantı yöntemleri,HttpClientbirleştirir ve HttpRequestMessage aracılığıyla SUT'ye istek göndermek için SendAsync(HttpRequestMessage) çağrısı yapar.SendAsynciçin aşırı yüklemeler, HTML formunu (IHtmlFormElement) ve aşağıdakileri kabul eder:- Formun Gönder düğmesi (
IHtmlElement) - Form değerleri koleksiyonu (
IEnumerable<KeyValuePair<string, string>>) - Gönder düğmesi (
IHtmlElement) ve form değerleri (IEnumerable<KeyValuePair<string, string>>)
- Formun Gönder düğmesi (
AngleSharp, bu makalede ve örnek uygulamada gösterim amacıyla kullanılan bir üçüncü taraf ayrıştırma kitaplığıdır. AngleSharp, ASP.NET Core uygulamalarının tümleştirme testi için desteklenmez veya gerekli değildir. Html Çeviklik Paketi (HAP) gibi diğer ayrıştırıcılar kullanılabilir. Bir diğer yaklaşım da, sahteciliğe karşı koruma sisteminin istek doğrulama belirtecini ve kötü amaçlı yazılımdan korumayı doğrudan işlemek için kod yazmaktır cookie . Daha fazla bilgi için bu makaledeki AngleSharp vs Application Parts antiforgery denetimlerine bakın.
EF-Core bellek içi veritabanı sağlayıcısı sınırlı ve temel test için kullanılabilir, ancak bellek içi test için önerilen seçenek SQLite sağlayıcısıdır.
StartUp'u başlangıç filtreleri ile genişletme hakkında bilgi için Başlangıç'ı Başlangıç Filtreleriyle Genişletme kısmına bakın. Bu, bir test özel bir hizmet veya ara yazılım gerektirdiğinde kullanılan IStartupFilter ile ara yazılımın nasıl yapılandırılacağını gösterir.
WithWebHostBuilder ile istemciyi özelleştirme
Ek yapılandırma bir test yönteminde gerekli olduğunda, WithWebHostBuilder bir WebApplicationFactory içeren ve yapılandırmayla daha da özelleştirilen yeni bir IWebHostBuilder oluşturur.
Örnek kod, yapılandırılmış hizmetleri test saptamalarıyla değiştirmek için çağırırWithWebHostBuilder. Daha fazla bilgi ve örnek kullanım için bu makaledeki Sahte hizmetler ekleme bölümüne bakın.
Post_DeleteMessageHandler_ReturnsRedirectToRoot
Örnek uygulamanın test yöntemi, WithWebHostBuilder kullanımını gösterir. Bu test, SUT'da form gönderimini tetikleyerek veritabanında bir kayıt silme işlemi gerçekleştirir.
Bir IndexPageTests sınıfındaki diğer bir test, veritabanındaki tüm kayıtları silen bir işlemi gerçekleştirebilir ve Post_DeleteMessageHandler_ReturnsRedirectToRoot yönteminden önce çalışabilir. Bu nedenle, SUT'nin silebilmesi için bir kaydın mevcut olduğundan emin olmak amacıyla bu test yönteminde veritabanı tekrar oluşturulur. SUT talebinde SUT'daki formun ilk sil düğmesinin messages seçilmesi simüle edilir.
[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("/"));
}
İstemci seçenekleri
WebApplicationFactoryClientOptions Örnek oluştururken HttpClient varsayılanlar ve kullanılabilir seçenekler için sayfaya bakın.
WebApplicationFactoryClientOptions sınıfını oluşturup bunu CreateClient() yöntemine geçirin:
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();
}
NOT: HTTPS Yeniden Yönlendirme ara yazılımı kullanılırken günlüklerde HTTPS yeniden yönlendirme uyarılarından kaçınmak için BaseAddress = new Uri("https://localhost")
Sahte hizmetleri enjekte et
Testte, ConfigureTestServices ile konak oluşturucusunda hizmetler geçersiz kılınabilir. Testin kendisine geçersiz kılınan hizmetleri tanımlamak için WithWebHostBuilder yöntemi, bir konak oluşturucu almak amacıyla kullanılır. Bu durum aşağıdaki testlerde görülebilir:
- Get_QuoteService_ProvidesQuoteInPage
- Get_GithubProfilePageCanGetAGithubUser
- Get_SecurePageIsReturnedForAnAuthenticatedUser
Örnek SUT, teklif döndüren kapsamlı bir hizmet içerir. Alıntı, Dizin sayfası istendiğinde Dizin sayfasındaki gizli bir alana yerleştirilir.
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">
SUT uygulaması çalıştırıldığında aşağıdaki işaretleme oluşturulur:
<input id="quote" type="hidden" value="Come on, Sarah. We've an appointment in
London, and we're already 30,000 years late.">
Tümleştirme testinde hizmeti ve teklif eklemeyi test etmek için test tarafından SUT'ye sahte hizmet eklenir. Sahte hizmet, uygulamanın QuoteService öğesini, test uygulaması tarafından sağlanan TestQuoteService adlı bir hizmetle değiştirir.
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 çağrılır ve kapsamı belirlenmiş hizmet kaydedilir:
[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."));
}
Testin yürütülmesi sırasında oluşturulan belirteç, c0 tarafından sağlanan tırnak metnini yansıtır, bu nedenle doğrulama başarılı olur.
<input id="quote" type="hidden" value="Something's interfering with time,
Mr. Scarman, and time is my business.">
Sahte kimlik doğrulaması
Sınıfındaki AuthTests testler güvenli bir uç nokta olup olmadığını denetler:
- Kimliği doğrulanmamış bir kullanıcıyı uygulamanın oturum açma sayfasına yönlendirir.
- Kimliği doğrulanmış bir kullanıcının içeriğini döndürür.
SUT'de, /SecurePage sayfası, bir AuthorizePage kuralı kullanarak sayfaya bir AuthorizeFilter uygular. Daha fazla bilgi için bkz Razor . Sayfa yetkilendirme kuralları.
services.AddRazorPages(options =>
{
options.Conventions.AuthorizePage("/SecurePage");
});
Get_SecurePageRedirectsAnUnauthenticatedUser testinde, WebApplicationFactoryClientOptionsAllowAutoRedirectfalse olarak ayarlanarak yeniden yönlendirmelere izin vermemek üzere ayarlanmıştır.
[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"));
}
İstemcinin yeniden yönlendirmeyi izlemesine izin vermeyerek aşağıdaki denetimler yapılabilir:
- SUT tarafından döndürülen durum kodu, oturum açma sayfasına yeniden yönlendirmeden sonraki son durum koduna değil, beklenen HttpStatusCode.Redirect sonuca göre denetlenebilir. Bu, HttpStatusCode.OK olacaktır.
- Yanıt üst bilgilerindeki
Locationüst bilgi değeri, üst bilginin bulunmayacağı son oturum açma sayfası yanıtıyla değil,http://localhost/Identity/Account/Loginile başladığını onaylamak için denetlenir.
Test uygulaması, kimlik doğrulama ve yetkilendirme unsurlarını test etmek için AuthenticationHandler<TOptions>'i ConfigureTestServices içinde simüle edebilir. En basit senaryo bir AuthenticateResult.Success döndürür.
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);
}
}
TestAuthHandler, kimlik doğrulama düzeni TestScheme olarak ayarlandığında, AddAuthentication için ConfigureTestServices kaydedildiğinde kullanıcının kimliğini doğrulamak için çağrılır. Uygulamanızın beklediği düzenle TestScheme düzeninin eşleşmesi önemlidir. Aksi takdirde kimlik doğrulaması çalışmaz.
[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));
}
hakkında WebApplicationFactoryClientOptionsdaha fazla bilgi için İstemci seçenekleri bölümüne bakın.
Kimlik doğrulaması ara yazılımı için temel testler
Kimlik doğrulama ara yazılımının temel testleri için bu GitHub deposuna bakın. Test senaryosuna özgü bir test sunucusu içerir.
Ortamı ayarlama
Ortamı özel uygulama fabrikasında ayarlayın:
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");
}
}
Test altyapısı uygulama içeriği kök yolunu nasıl çıkarsar?
Uygulama WebApplicationFactory yolunu çıkarsamak için oluşturucu, anahtarına eşit anahtarla tümleştirme testlerini içeren derlemede WebApplicationFactoryContentRootAttribute arar ve bu derleme TEntryPoint içerirSystem.Reflection.Assembly.FullName. Doğru anahtara sahip bir özniteliğin bulunamazsa, WebApplicationFactory çözüm dosyasını aramaya (.sln) geri döner ve derleme adını çözüm dizinine ekler TEntryPoint . Uygulama kök dizini (içerik kök yolu), görünümleri ve içerik dosyalarını bulmak için kullanılır.
Gölge kopyalamayı devre dışı bırakma
Gölge kopyalama, testlerin çıkış dizininden farklı bir dizinde yürütülmesine neden olur. Testleriniz Assembly.Location dosyalarını yüklemeye bağlıysa ve sorunlarla karşılaşırsanız, gölge kopyalamayı devre dışı bırakmanız gerekebilir.
xUnit kullanırken gölge kopyalamayı devre dışı bırakmak için test projesi dizininizde doğru yapılandırma ayarıyla xunit.runner.jsonbir dosya oluşturun:
{
"shadowCopy": false
}
Nesnelerin atılması
Testlerin yürütülmesinden sonra IClassFixture uygulaması için ve xUnit TestServer nesnesini yok ettiğinde, HttpClient ve WebApplicationFactory de kapatılır. Geliştirici tarafından oluşturulan nesneler yok edilmesi gerekiyorsa, bu nesneleri IClassFixture uygulamasında yok edin. Daha fazla bilgi için bkz Bir Dispose yöntemi uygulamak.
testler TestClass yürütüldükten sonra, MSTest TestServer yönteminde HttpClient öğesini attığında, WebApplicationFactory ve ClassCleanup atılır. Geliştirici tarafından örneklenen nesnelerin atılması gerekiyorsa, bunları ClassCleanup yönteminde atın. Daha fazla bilgi için bkz Bir Dispose yöntemi uygulamak.
Test sınıfının testleri yürütüldükten sonra, TestServer ve HttpClient öğeleri, WebApplicationFactory metodunda NUnit TearDown atma işlemi yapıldığında atılır. Geliştirici tarafından örneklenen nesnelerin atılması gerekiyorsa, bunları TearDown yönteminde atın. Daha fazla bilgi için bkz Bir Dispose yöntemi uygulamak.
Tümleştirme testleri örneği
Örnek uygulama iki uygulamadan oluşur:
| App | Proje dizini | Description |
|---|---|---|
| İleti uygulaması (SUT) | src/RazorPagesProject |
Kullanıcının bir ileti eklemesine, silmesine, tümünü silmesine ve iletileri analiz etmesine izin verir. |
| Test uygulaması | tests/RazorPagesProject.Tests |
SUT'yi tümleştirme testi için kullanılır. |
Testler, Visual Studio gibi bir IDE'nin yerleşik test özellikleri kullanılarak çalıştırılabilir. Visual Studio Code veya komut satırı kullanıyorsanız, dizinindeki bir komut isteminde aşağıdaki komutu yürütün:
dotnet test
İleti uygulaması (SUT) kuruluşu
SUT, aşağıdaki özelliklere sahip bir Razor Sayfalar ileti sistemidir:
- Uygulamanın (
Pages/Index.cshtmlvePages/Index.cshtml.cs) Dizin sayfası, iletilerin eklenmesini, silinmesini ve analizini (ileti başına ortalama sözcükler) denetlemek için bir kullanıcı arabirimi ve sayfa modeli yöntemleri sağlar. - Bir ileti,
Messagesınıfı (Data/Message.cs) tarafından iki özelliğe sahip olarak açıklanır:Id(anahtar) veText(ileti).Textözelliği gereklidir ve 200 karakterle sınırlıdır. - İletiler Entity Framework'ün bellek içi veritabanı† kullanılarak depolanır.
- Uygulama, veritabanı bağlam sınıfında
AppDbContext() bir veri erişim katmanı (Data/AppDbContext.csDAL) içerir. - Uygulama başlangıcında veritabanı boşsa, ileti deposu üç iletiyle başlatılır.
- Uygulama, yalnızca kimliği doğrulanmış bir kullanıcı tarafından erişilebilen bir
/SecurePageiçerir.
†EF makalesi InMemory ile Test, MSTest ile testler için bellek içi veritabanının nasıl kullanılacağını açıklar. Bu konu, xUnit test çerçevesini kullanır. Farklı test çerçevelerindeki test kavramları ve test uygulamaları benzerdir ancak aynı değildir.
Uygulama depo desenini kullanmasa ve çalışma birimi (UoW) deseninin etkili bir örneği olmasa da, Razor Sayfalar bu geliştirme desenlerini destekler. Daha fazla bilgi için bkz . Altyapı kalıcılık katmanını tasarlama ve Test denetleyicisi mantığı (örnek, depo düzenini uygular).
Uygulama kuruluşunu test et
Test uygulaması, dizinin içindeki tests/RazorPagesProject.Tests bir konsol uygulamasıdır.
| Uygulama dizinini test et | Description |
|---|---|
AuthTests |
Aşağıdakiler için test yöntemlerini içerir:
|
BasicTests |
Yönlendirme ve içerik türü için bir test yöntemi içerir. |
IntegrationTests |
Özel WebApplicationFactory sınıf kullanarak Dizin sayfasının tümleştirme testlerini içerir. |
Helpers/Utilities |
|
Test çerçevesi xUnit'tir. Tümleştirme testleri, Microsoft.AspNetCore.TestHost kullanılarak, TestServer öğesini içeren yapılır.
Microsoft.AspNetCore.Mvc.Testing Paket test ana bilgisayarını ve test sunucusunu yapılandırmak için kullanıldığından TestHost ve TestServer paketleri, test uygulamasının proje dosyasında veya test uygulamasındaki geliştirici yapılandırmasında doğrudan paket başvuruları gerektirmez.
Tümleştirme testleri genellikle test yürütmeden önce veritabanında küçük bir veri kümesi gerektirir. Örneğin, silme testi veritabanı kaydı silme işlemini çağırır, bu nedenle silme isteğinin başarılı olması için veritabanında en az bir kayıt olmalıdır.
Örnek uygulama, testlerin çalıştırıldığında kullanabileceği üç iletiyi Utilities.cs veritabanına ekler:
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." }
};
}
SUT'nin veritabanı bağlamı Program.cs içinde kaydedilir. Test uygulamasının builder.ConfigureServices geri çağırması, yürütülür. Testlerde farklı bir veritabanı kullanmak için, uygulamanın veritabanı bağlamı builder.ConfigureServices içinde değiştirilmelidir. Daha fazla bilgi için WebApplicationFactory'yi Özelleştirme bölümüne bakın.
Ek kaynaklar
Bu konu, birim testlerinin temel bir anlayışını varsayar. Test kavramlarını bilmiyorsanız .NET Core ve .NET Standard'da Birim Testi konusuna ve bağlantılı içeriğine bakın.
Örnek kodu görüntüleme veya indirme (indirme)
Örnek uygulama bir Razor Pages uygulamasıdır ve Razor Sayfalar hakkında temel bir anlayış olduğunu varsayar. Sayfalar hakkında bilginiz yoksa Razor aşağıdaki konulara bakın:
Note
SPA'ları test etme amacıyla tarayıcıyı otomatikleştirebilen .NET için Playwright gibi bir araç öneririz.
Tümleştirme testlerine giriş
Tümleştirme testleri, bir uygulamanın bileşenlerini birim testlerinden daha geniş bir düzeyde değerlendirir. Birim testleri, tek tek sınıf yöntemleri gibi yalıtılmış yazılım bileşenlerini test etmek için kullanılır. Tümleştirme testleri, iki veya daha fazla uygulama bileşeninin birlikte çalışarak beklenen bir sonuç elde ettiğini ve büyük olasılıkla bir isteği tam olarak işlemek için gereken her bileşenin de dahil olduğunu onaylar.
Bu daha geniş testler, genellikle aşağıdaki bileşenler de dahil olmak üzere uygulamanın altyapısını ve tüm çerçevesini test etmek için kullanılır:
- Database
- Dosya sistemi
- Ağ gereçleri
- İstek-yanıt işlem hattı
Birim testleri, altyapı bileşenleri yerine sahte veya sahte nesneler olarak bilinen fabrikatlanmış bileşenleri kullanır.
Birim testlerinin aksine tümleştirme testleri:
- Uygulamanın üretimde kullandığı gerçek bileşenleri kullanın.
- Daha fazla kod ve veri işleme gerektirir.
- Çalıştırılması daha uzun sürer.
Bu nedenle, tümleştirme testlerinin kullanımını en önemli altyapı senaryolarıyla sınırlayın. Bir davranış birim testi veya tümleştirme testi kullanılarak test edilebiliyorsa birim testini seçin.
Tümleştirme testlerinin tartışmalarında, test edilen projeye genellikle Test Altındaki Sistem veya kısaca "SUT" adı verilir. "SUT" bu makalede test edilen ASP.NET Core uygulamasına başvurmak için kullanılır.
Veritabanları ve dosya sistemleriyle verilerin ve dosya erişiminin her permütasyonu için tümleştirme testleri yazmayın. Bir uygulama genelinde veritabanları ve dosya sistemleriyle kaç yerde etkileşim kurduğundan bağımsız olarak, odaklanmış bir dizi okuma, yazma, güncelleştirme ve silme tümleştirme testi genellikle veritabanı ve dosya sistemi bileşenlerini yeterince test edebilmektedir. Bu bileşenlerle etkileşim kuran yöntem mantığının rutin testleri için birim testlerini kullanın. Birim testlerinde, altyapı sahte nesneleri veya taklitlerinin kullanımı, testlerin daha hızlı yürütülmesine olanak tanır.
ASP.NET Core tümleştirme testleri
ASP.NET Core'daki tümleştirme testleri aşağıdakileri gerektirir:
- Testleri içermek ve yürütmek için bir test projesi kullanılır. Test projesinin SUT'a referansı vardır.
- Test projesi SUT için bir test web konağı oluşturur ve SUT ile istekleri ve yanıtları işlemek için bir test sunucusu istemcisi kullanır.
- Testleri yürütmek ve test sonuçlarını raporlamak için bir test çalıştırıcısı kullanılır.
Tümleştirme testleri, her zamanki Düzenleme, Eylem ve Onay testi adımlarını içeren bir dizi olayı izler:
- SUT'nin web konağı yapılandırıldı.
- Uygulamaya istek göndermek için bir test sunucusu istemcisi oluşturulur.
- Test düzenleme adımı yürütülür: Test uygulaması bir istek hazırlar.
- Eylem test adımı yürütülür: İstemci isteği gönderir ve yanıtı alır.
- Assert test adımı yürütülür: Gerçek yanıt, beklenen yanıta göre geçti veya başarısız olarak doğrulanır.
- Tüm testler yürütülene kadar işlem devam eder.
- Test sonuçları bildirilir.
Genellikle test web konağı, test çalıştırmaları için uygulamanın normal web ana bilgisayarından farklı yapılandırılır. Örneğin, testler için farklı bir veritabanı veya farklı uygulama ayarları kullanılabilir.
Test web konağı ve bellek içi test sunucusu ()TestServer gibi altyapı bileşenleri Microsoft.AspNetCore.Mvc.Testing paketi tarafından sağlanır veya yönetilir. Bu paketin kullanımı test oluşturma ve yürütmeyi kolaylaştırır.
Paket Microsoft.AspNetCore.Mvc.Testing aşağıdaki görevleri işler:
- Bağımlılık dosyasını (
.deps) SUT'den test projesininbindizinine kopyalar. - testler yürütürken statik dosyaların ve sayfaların/görünümlerin bulunması için içerik kökünü SUT'nin proje köküne ayarlar.
- WebApplicationFactory sınıfını ile SUT önyüklemesini kolaylaştırmak için sağlar.
Birim testleri belgelerinde bir test projesi ve test çalıştırıcısının nasıl ayarlanacağı ve testleri çalıştırma hakkında ayrıntılı yönergelerin yanı sıra testleri ve test sınıflarını adlandırmaya yönelik öneriler açıklanmaktadır.
Birim testlerini ve tümleştirme testlerini ayrı projelere ayırın. Testleri ayırma:
- Altyapı testi bileşenlerinin birim testlerine yanlışlıkla dahil edilmesini önlemeye yardımcı olur.
- Hangi test kümesinin çalıştırıldığı üzerinde denetime izin verir.
Sayfalar uygulamalarının ve MVC uygulamalarının testlerinin Razor yapılandırması arasında neredeyse hiçbir fark yoktur. Tek fark, testlerin nasıl adlandırıldığındandır.
Razor Sayfalar uygulamasında, sayfa uç noktalarının testleri genellikle sayfa modeli sınıfından (örneğin, IndexPageTests Dizin sayfası için bileşen tümleştirmesini test etmek için) adlandırılır. MVC uygulamasında testler, genellikle test ettikleri denetleyici sınıflarına göre düzenlenir ve bu denetleyicilerin adlarını alır (örneğin, HomeControllerTests bileşen tümleştirmesini Home denetleyicisi için test etmek üzere).
Uygulama önkoşullarını test edin
Test projesinin aşağıdakileri yapması gerekir:
-
Microsoft.AspNetCore.Mvc.Testingpaketine başvurun. - Proje dosyasında (
<Project Sdk="Microsoft.NET.Sdk.Web">) Web SDK'sını belirtin.
Bu önkoşullar örnek uygulamada görülebilir.
tests/RazorPagesProject.Tests/RazorPagesProject.Tests.csproj Dosyayı inceleyin. Örnek uygulama xUnit test çerçevesini ve AngleSharp ayrıştırıcı kitaplığını kullandığından örnek uygulama da aşağıdakilere başvurur:
Sürüm 2.4.2 veya üzerini kullanan xunit.runner.visualstudio uygulamalarda, test projesi Microsoft.NET.Test.Sdk paketine başvurmalıdır.
Entity Framework Core, testlerde de kullanılır. Uygulama şu referansları içerir:
Microsoft.AspNetCore.Diagnostics.EntityFrameworkCoreMicrosoft.AspNetCore.Identity.EntityFrameworkCoreMicrosoft.EntityFrameworkCoreMicrosoft.EntityFrameworkCore.InMemoryMicrosoft.EntityFrameworkCore.Tools
SUT ortamı
SUT'nin ortamı ayarlı değilse, ortam varsayılan olarak olarak Developmentayarlanır.
Varsayılan WebApplicationFactory ile temel testler
WebApplicationFactory<TEntryPoint> tümleştirme testlerine yönelik bir TestServer oluşturmak için kullanılır.
TEntryPoint sınıfı, genellikle Startup sınıfı olan, SUT'nin giriş noktasıdır.
Test sınıfları, sınıfın testleri içerdiğini belirtmek ve sınıftaki testler arasında paylaşılan nesne örnekleri sağlamak için bir sınıf fikstür arabirimi (IClassFixture) uygular.
Aşağıdaki test sınıfı, SUT'yi önyüklemek için BasicTests kullanır ve bir test yöntemine WebApplicationFactory sağlamak için HttpClient öğesini kullanır, Get_EndpointsReturnSuccessAndCorrectContentType. yöntemi, yanıt durum kodunun başarılı olup olmadığını (200-299 aralığındaki durum kodları) ve Content-Type üst bilginin birkaç uygulama sayfası için olup olmadığını text/html; charset=utf-8 denetler.
CreateClient(), otomatik olarak yeniden yönlendirmeleri izleyen ve tanımlama bilgilerini işleyen bir HttpClient örneği oluşturur.
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());
}
}
Varsayılan olarak, GDPR onay ilkesi etkinleştirildiğinde gerekli olmayan tanımlama bilgileri istekler arasında korunmaz. Gerekli olmayan tanımlama bilgilerini, örneğin TempData sağlayıcısı tarafından kullanılanları, korumak için bunları testlerinizde önemli olarak işaretleyin. Bir cookie öğesini önemli olarak işaretleme talimatları için, bkz Temel tanımlama bilgileri.
WebApplicationFactory'i özelleştirme
Web sunucu yapılandırması, bir veya daha fazla özel fabrika oluşturmak için WebApplicationFactory öğesinden devralınmak suretiyle test sınıflarından bağımsız olarak oluşturulabilir.
WebApplicationFactorysınıfından devralın ve ConfigureWebHost yöntemini geçersiz kılın. ile IWebHostBuilder hizmet koleksiyonunun ConfigureServicesyapılandırılmasına izin verir: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); } } }); } }Veritabanı tohumlama, örnek uygulamada
InitializeDbForTestsyöntemi ile gerçekleştirilir. Yöntemi, Tümleştirme testleri örneği: Test uygulaması kuruluşu bölümünde açıklanmıştır.SUT'nin veritabanı bağlamı
Startup.ConfigureServicesyöntemi içinde kaydedilir. Test uygulamasınınbuilder.ConfigureServicesgeri çağırması, yürütülür. Generic Host için yürütme sırası, ASP.NET Core 3.0 sürümüyle önemli bir değişikliktir. Testlerde uygulamanın veritabanından farklı bir veritabanı kullanmak için, uygulamanın veritabanı bağlamı içindebuilder.ConfigureServicesdeğiştirilmelidir.Web Konağını kullanmaya devam eden SUT'ler için test uygulamasının
builder.ConfigureServicesgeri çağırması SUT kodundan önceStartup.ConfigureServices. Test uygulamasınınbuilder.ConfigureTestServicesgeri çağırması daha sonra yürütülür.Örnek uygulama, veritabanı bağlamı için hizmet tanımlayıcısını bulur ve hizmet kaydını kaldırmak için tanımlayıcıyı kullanır. Ardından fabrika, testler için bellek içi veritabanı kullanan yeni
ApplicationDbContextbir veritabanı ekler.Bellek içi veritabanı yerine farklı bir veritabanına bağlanmak için,
UseInMemoryDatabaseçağrısını değiştirerek bağlamı o veritabanına bağlayın. SQL Server test veritabanını kullanmak için:- Proje dosyasındaKi
Microsoft.EntityFrameworkCore.SqlServerNuGet paketine başvurun. - Veritabanına bir bağlantı dizesi ile çağrısı
UseSqlServeryapın.
services.AddDbContext<ApplicationDbContext>((options, context) => { context.UseSqlServer( Configuration.GetConnectionString("TestingDbConnectionString")); });- Proje dosyasındaKi
Test sınıflarında özel
CustomWebApplicationFactoryöğesini kullanın. Aşağıdaki örnek,IndexPageTestssınıfındaki fabrikayı kullanır: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 }); }Örnek uygulamanın istemcisi,
HttpClient'nin yeniden yönlendirmeleri takip etmemesi için yapılandırılır. Sahte kimlik doğrulaması bölümünde daha sonra açıklandığı gibi bu, testlerin uygulamanın ilk yanıtının sonucunu denetlemesine izin verir. İlk yanıt, bu testlerin çoğundaLocationbaşlık içeren bir yeniden yönlendirmedir.Tipik bir test, isteği ve yanıtı işlemek için
HttpClientve yardımcı yöntemlerini kullanır.[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); }
SUT'ye yapılan herhangi bir POST isteği, uygulamanın veri koruma önleme sistemi tarafından otomatik olarak yapılan kötü amaçlı yazılımdan koruma denetimini karşılamalıdır. Testin POST isteğini düzenlemek için test uygulamasının şunları yapması gerekir:
- Sayfa için bir istekte bulun.
- Yanıttan sahtecilik önleme (cookie) ve doğrulama belirtecini ayrıştırın.
- POST isteğini sahteciliğe cookie karşı koruma ve istek doğrulama belirteci ile gerçekleştirin.
Örnek
-
GetDocumentAsync: HttpResponseMessage alır ve birIHtmlDocumentdöndürür.GetDocumentAsync, özgün dayalı birHttpResponseMessagehazırlayan bir fabrika kullanır. Daha fazla bilgi için AngleSharp belgelerine bakın. -
SendAsyncuzantı yöntemleri,HttpClientbirleştirir ve HttpRequestMessage aracılığıyla SUT'ye istek göndermek için SendAsync(HttpRequestMessage) çağrısı yapar.SendAsynciçin aşırı yüklemeler, HTML formunu (IHtmlFormElement) ve aşağıdakileri kabul eder:- Formun Gönder düğmesi (
IHtmlElement) - Form değerleri koleksiyonu (
IEnumerable<KeyValuePair<string, string>>) - Gönder düğmesi (
IHtmlElement) ve form değerleri (IEnumerable<KeyValuePair<string, string>>)
- Formun Gönder düğmesi (
Note
AngleSharp , bu konuda ve örnek uygulamada gösterim amacıyla kullanılan bir üçüncü taraf ayrıştırma kitaplığıdır. AngleSharp, ASP.NET Core uygulamalarının tümleştirme testi için desteklenmez veya gerekli değildir. Html Çeviklik Paketi (HAP) gibi diğer ayrıştırıcılar kullanılabilir. Bir diğer yaklaşım da, sahteciliğe karşı koruma sisteminin istek doğrulama belirtecini ve kötü amaçlı yazılımdan korumayı doğrudan işlemek için kod yazmaktır cookie .
Note
EF-Core bellek içi veritabanı sağlayıcısı sınırlı ve temel test için kullanılabilir, ancak bellek içi test için önerilen seçenek SQLite sağlayıcısıdır.
WithWebHostBuilder ile istemciyi özelleştirme
Ek yapılandırma bir test yönteminde gerekli olduğunda, WithWebHostBuilder bir WebApplicationFactory içeren ve yapılandırmayla daha da özelleştirilen yeni bir IWebHostBuilder oluşturur.
Post_DeleteMessageHandler_ReturnsRedirectToRoot
Örnek uygulamanın test yöntemi, WithWebHostBuilder kullanımını gösterir. Bu test, SUT'da form gönderimini tetikleyerek veritabanında bir kayıt silme işlemi gerçekleştirir.
Bir IndexPageTests sınıfındaki diğer bir test, veritabanındaki tüm kayıtları silen bir işlemi gerçekleştirebilir ve Post_DeleteMessageHandler_ReturnsRedirectToRoot yönteminden önce çalışabilir. Bu nedenle, SUT'nin silebilmesi için bir kaydın mevcut olduğundan emin olmak amacıyla bu test yönteminde veritabanı tekrar oluşturulur. SUT talebinde SUT'daki formun ilk sil düğmesinin messages seçilmesi simüle edilir.
[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);
}
İstemci seçenekleri
Aşağıdaki tabloda, WebApplicationFactoryClientOptions örnekleri oluşturulurken kullanılabilen varsayılan HttpClient değerler gösterilmektedir.
| Option | Description | Default |
|---|---|---|
| AllowAutoRedirect |
HttpClient örneklerinin yeniden yönlendirme yanıtlarını otomatik olarak takip edip etmeyeceğini alır veya ayarlar. |
true |
| BaseAddress | Örneklerin HttpClient temel adresini alır veya ayarlar. |
http://localhost |
| HandleCookies |
HttpClient örneklerinin tanımlama bilgilerini işleyip işlemeyecek şekilde ayarlar veya alır. |
true |
| MaxAutomaticRedirections |
HttpClient örneklerinin izlemesi gereken en fazla yeniden yönlendirme yanıtı sayısını alır veya ayarlar. |
7 |
sınıfını WebApplicationFactoryClientOptions oluşturun ve yöntemine CreateClient() geçirin (varsayılan değerler kod örneğinde gösterilir):
// 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);
Sahte hizmetleri enjekte et
Testte, ConfigureTestServices ile konak oluşturucusunda hizmetler geçersiz kılınabilir. Sahte hizmetler eklemek için SUT'nin bir Startup sınıfı ve bir Startup.ConfigureServices yöntemi olmalıdır.
Örnek SUT, teklif döndüren kapsamlı bir hizmet içerir. Alıntı, Dizin sayfası istendiğinde Dizin sayfasındaki gizli bir alana yerleştirilir.
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">
SUT uygulaması çalıştırıldığında aşağıdaki işaretleme oluşturulur:
<input id="quote" type="hidden" value="Come on, Sarah. We've an appointment in
London, and we're already 30,000 years late.">
Tümleştirme testinde hizmeti ve teklif eklemeyi test etmek için test tarafından SUT'ye sahte hizmet eklenir. Sahte hizmet, uygulamanın QuoteService öğesini, test uygulaması tarafından sağlanan TestQuoteService adlı bir hizmetle değiştirir.
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 çağrılır ve kapsamı belirlenmiş hizmet kaydedilir:
[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);
}
Testin yürütülmesi sırasında oluşturulan belirteç, c0 tarafından sağlanan tırnak metnini yansıtır, bu nedenle doğrulama başarılı olur.
<input id="quote" type="hidden" value="Something's interfering with time,
Mr. Scarman, and time is my business.">
Sahte kimlik doğrulaması
Sınıfındaki AuthTests testler güvenli bir uç nokta olup olmadığını denetler:
- Kimliği doğrulanmamış bir kullanıcıyı uygulamanın Oturum Açma sayfasına yönlendirir.
- Kimliği doğrulanmış bir kullanıcının içeriğini döndürür.
SUT'de, /SecurePage sayfası, bir AuthorizePage kuralı kullanarak sayfaya bir AuthorizeFilter uygular. Daha fazla bilgi için bkz Razor . Sayfa yetkilendirme kuralları.
services.AddRazorPages(options =>
{
options.Conventions.AuthorizePage("/SecurePage");
});
Get_SecurePageRedirectsAnUnauthenticatedUser testinde, WebApplicationFactoryClientOptionsAllowAutoRedirectfalse olarak ayarlanarak yeniden yönlendirmelere izin vermemek üzere ayarlanmıştır.
[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);
}
İstemcinin yeniden yönlendirmeyi izlemesine izin vermeyerek aşağıdaki denetimler yapılabilir:
- SUT tarafından döndürülen durum kodu, beklenen HttpStatusCode.Redirect sonuca göre kontrol edilebilir, Oturum Açma sayfasına yönlendirildikten sonraki son durum kodu olan HttpStatusCode.OK ile değil.
- Yanıt üst bilgilerindeki
Locationüst bilgi değeri,http://localhost/Identity/Account/Loginile başlayan bir değer olup olmadığını doğrulamak için kontrol edilir; bu kontrol,Locationüst bilgisinin bulunmayacağı son Oturum Açma sayfası yanıtı için geçerli değildir.
Test uygulaması, kimlik doğrulama ve yetkilendirme unsurlarını test etmek için AuthenticationHandler<TOptions>'i ConfigureTestServices içinde simüle edebilir. En basit senaryo bir AuthenticateResult.Success döndürür.
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);
}
}
TestAuthHandler, kimlik doğrulama düzeni Test olarak ayarlandığında, AddAuthentication için ConfigureTestServices kaydedildiğinde kullanıcının kimliğini doğrulamak için çağrılır. Uygulamanızın beklediği düzenle Test düzeninin eşleşmesi önemlidir. Aksi takdirde kimlik doğrulaması çalışmaz.
[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);
}
hakkında WebApplicationFactoryClientOptionsdaha fazla bilgi için İstemci seçenekleri bölümüne bakın.
Ortamı ayarlama
Varsayılan olarak, SUT'nin ana bilgisayar ve uygulama ortamı Development ortamını kullanacak şekilde yapılandırılmıştır.
IHostBuilder kullanırken SUT ortamını geçersiz kılmak için:
- Ortam değişkenini
ASPNETCORE_ENVIRONMENTolarak ayarlayın (örneğin,Staging,ProductionveyaTestinggibi başka bir özel değer). - Override
CreateHostBuildertest uygulamasında,ASPNETCOREile başlayan ortam değişkenlerini okumak için.
protected override IHostBuilder CreateHostBuilder() =>
base.CreateHostBuilder()
.ConfigureHostConfiguration(
config => config.AddEnvironmentVariables("ASPNETCORE"));
SUT Web Konağını ()IWebHostBuilder kullanıyorsa, geçersiz kılın CreateWebHostBuilder:
protected override IWebHostBuilder CreateWebHostBuilder() =>
base.CreateWebHostBuilder().UseEnvironment("Testing");
Test altyapısı uygulama içeriği kök yolunu nasıl çıkarsar?
Uygulama WebApplicationFactory yolunu çıkarsamak için oluşturucu, anahtarına eşit anahtarla tümleştirme testlerini içeren derlemede WebApplicationFactoryContentRootAttribute arar ve bu derleme TEntryPoint içerirSystem.Reflection.Assembly.FullName. Doğru anahtara sahip bir özniteliğin bulunamazsa, WebApplicationFactory çözüm dosyasını aramaya (.sln) geri döner ve derleme adını çözüm dizinine ekler TEntryPoint . Uygulama kök dizini (içerik kök yolu), görünümleri ve içerik dosyalarını bulmak için kullanılır.
Gölge kopyalamayı devre dışı bırakma
Gölge kopyalama, testlerin çıkış dizininden farklı bir dizinde yürütülmesine neden olur. Testleriniz Assembly.Location dosyalarını yüklemeye bağlıysa ve sorunlarla karşılaşırsanız, gölge kopyalamayı devre dışı bırakmanız gerekebilir.
xUnit kullanırken gölge kopyalamayı devre dışı bırakmak için test projesi dizininizde doğru yapılandırma ayarıyla xunit.runner.jsonbir dosya oluşturun:
{
"shadowCopy": false
}
Nesnelerin atılması
Testlerin yürütülmesinden sonra IClassFixture uygulaması için ve xUnit TestServer nesnesini yok ettiğinde, HttpClient ve WebApplicationFactory de kapatılır. Geliştirici tarafından oluşturulan nesneler yok edilmesi gerekiyorsa, bu nesneleri IClassFixture uygulamasında yok edin. Daha fazla bilgi için bkz Bir Dispose yöntemi uygulamak.
Tümleştirme testleri örneği
Örnek uygulama iki uygulamadan oluşur:
| App | Proje dizini | Description |
|---|---|---|
| İleti uygulaması (SUT) | src/RazorPagesProject |
Kullanıcının bir ileti eklemesine, silmesine, tümünü silmesine ve iletileri analiz etmesine izin verir. |
| Test uygulaması | tests/RazorPagesProject.Tests |
SUT'yi tümleştirme testi için kullanılır. |
Testler, Visual Studio gibi bir IDE'nin yerleşik test özellikleri kullanılarak çalıştırılabilir. Visual Studio Code veya komut satırı kullanıyorsanız, dizinindeki bir komut isteminde aşağıdaki komutu yürütün:
dotnet test
İleti uygulaması (SUT) kuruluşu
SUT, aşağıdaki özelliklere sahip bir Razor Sayfalar ileti sistemidir:
- Uygulamanın (
Pages/Index.cshtmlvePages/Index.cshtml.cs) Dizin sayfası, iletilerin eklenmesini, silinmesini ve analizini (ileti başına ortalama sözcükler) denetlemek için bir kullanıcı arabirimi ve sayfa modeli yöntemleri sağlar. - Bir ileti,
Messagesınıfı (Data/Message.cs) tarafından iki özelliğe sahip olarak açıklanır:Id(anahtar) veText(ileti).Textözelliği gereklidir ve 200 karakterle sınırlıdır. - İletiler Entity Framework'ün bellek içi veritabanı† kullanılarak depolanır.
- Uygulama, veritabanı bağlam sınıfında
AppDbContext() bir veri erişim katmanı (Data/AppDbContext.csDAL) içerir. - Uygulama başlangıcında veritabanı boşsa, ileti deposu üç iletiyle başlatılır.
- Uygulama, yalnızca kimliği doğrulanmış bir kullanıcı tarafından erişilebilen bir
/SecurePageiçerir.
†EF konusuna göre, InMemory Testi, MSTest ile testler için bir bellek içi veritabanının nasıl kullanılacağını açıklar. Bu konu, xUnit test çerçevesini kullanır. Farklı test çerçevelerindeki test kavramları ve test uygulamaları benzerdir ancak aynı değildir.
Uygulama depo desenini kullanmasa ve çalışma birimi (UoW) deseninin etkili bir örneği olmasa da, Razor Sayfalar bu geliştirme desenlerini destekler. Daha fazla bilgi için bkz . Altyapı kalıcılık katmanını tasarlama ve Test denetleyicisi mantığı (örnek, depo düzenini uygular).
Uygulama kuruluşunu test et
Test uygulaması, dizinin içindeki tests/RazorPagesProject.Tests bir konsol uygulamasıdır.
| Uygulama dizinini test et | Description |
|---|---|
AuthTests |
Aşağıdakiler için test yöntemlerini içerir:
|
BasicTests |
Yönlendirme ve içerik türü için bir test yöntemi içerir. |
IntegrationTests |
Özel WebApplicationFactory sınıf kullanarak Dizin sayfasının tümleştirme testlerini içerir. |
Helpers/Utilities |
|
Test çerçevesi xUnit'tir. Tümleştirme testleri, Microsoft.AspNetCore.TestHost kullanılarak, TestServer öğesini içeren yapılır.
Microsoft.AspNetCore.Mvc.Testing Paket test ana bilgisayarını ve test sunucusunu yapılandırmak için kullanıldığından TestHost ve TestServer paketleri, test uygulamasının proje dosyasında veya test uygulamasındaki geliştirici yapılandırmasında doğrudan paket başvuruları gerektirmez.
Tümleştirme testleri genellikle test yürütmeden önce veritabanında küçük bir veri kümesi gerektirir. Örneğin, silme testi veritabanı kaydı silme işlemini çağırır, bu nedenle silme isteğinin başarılı olması için veritabanında en az bir kayıt olmalıdır.
Örnek uygulama, testlerin çalıştırıldığında kullanabileceği üç iletiyi Utilities.cs veritabanına ekler:
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." }
};
}
SUT'nin veritabanı bağlamı Startup.ConfigureServices yöntemi içinde kaydedilir. Test uygulamasının builder.ConfigureServices geri çağırması, yürütülür. Testlerde farklı bir veritabanı kullanmak için, uygulamanın veritabanı bağlamı builder.ConfigureServices içinde değiştirilmelidir. Daha fazla bilgi için WebApplicationFactory'yi Özelleştirme bölümüne bakın.
Web Konağını kullanmaya devam eden SUT'ler için test uygulamasının builder.ConfigureServices geri çağırması SUT kodundan önceStartup.ConfigureServices. Test uygulamasının builder.ConfigureTestServices geri çağırması daha sonra yürütülür.
Ek kaynaklar
Bu makalede, birim testleri hakkında temel bilgiler olduğu varsayılır. Test kavramlarını bilmiyorsanız .NET'te test etme makalesine ve bağlantılı içeriğine bakın.
Örnek kodu görüntüleme veya indirme (indirme)
Örnek uygulama bir Razor Pages uygulamasıdır ve Razor Sayfalar hakkında temel bir anlayış olduğunu varsayar. Pages'ı tanımıyorsanız, Razor aşağıdaki makalelere bakın.
SPA'ları test etme amacıyla tarayıcıyı otomatikleştirebilen .NET için Playwright gibi bir araç öneririz.
Tümleştirme testlerine giriş
Tümleştirme testleri, bir uygulamanın bileşenlerini birim testlerinden daha geniş bir düzeyde değerlendirir. Birim testleri, tek tek sınıf yöntemleri gibi yalıtılmış yazılım bileşenlerini test etmek için kullanılır. Tümleştirme testleri, iki veya daha fazla uygulama bileşeninin birlikte çalışarak beklenen bir sonuç elde ettiğini ve büyük olasılıkla bir isteği tam olarak işlemek için gereken her bileşenin de dahil olduğunu onaylar.
Bu daha geniş testler, genellikle aşağıdaki bileşenler de dahil olmak üzere uygulamanın altyapısını ve tüm çerçevesini test etmek için kullanılır:
- Database
- Dosya sistemi
- Ağ gereçleri
- İstek-yanıt işlem hattı
Birim testleri, altyapı bileşenleri yerine sahte veya sahte nesneler olarak bilinen fabrikatlanmış bileşenleri kullanır.
Birim testlerinin aksine tümleştirme testleri:
- Uygulamanın üretimde kullandığı gerçek bileşenleri kullanın.
- Daha fazla kod ve veri işleme gerektirir.
- Çalıştırılması daha uzun sürer.
Bu nedenle, tümleştirme testlerinin kullanımını en önemli altyapı senaryolarıyla sınırlayın. Bir davranış birim testi veya tümleştirme testi kullanılarak test edilebiliyorsa birim testini seçin.
Tümleştirme testlerinin tartışmalarında, test edilen projeye genellikle Test Altındaki Sistem veya kısaca "SUT" adı verilir. "SUT" bu makalede test edilen ASP.NET Core uygulamasına başvurmak için kullanılır.
Veritabanları ve dosya sistemleriyle verilerin ve dosya erişiminin her permütasyonu için tümleştirme testleri yazmayın. Bir uygulama genelinde veritabanları ve dosya sistemleriyle kaç yerde etkileşim kurduğundan bağımsız olarak, odaklanmış bir dizi okuma, yazma, güncelleştirme ve silme tümleştirme testi genellikle veritabanı ve dosya sistemi bileşenlerini yeterince test edebilmektedir. Bu bileşenlerle etkileşim kuran yöntem mantığının rutin testleri için birim testlerini kullanın. Birim testlerinde, altyapı sahte nesneleri veya taklitlerinin kullanımı, testlerin daha hızlı yürütülmesine olanak tanır.
ASP.NET Core tümleştirme testleri
ASP.NET Core'daki tümleştirme testleri aşağıdakileri gerektirir:
- Testleri içermek ve yürütmek için bir test projesi kullanılır. Test projesinin SUT'a referansı vardır.
- Test projesi SUT için bir test web konağı oluşturur ve SUT ile istekleri ve yanıtları işlemek için bir test sunucusu istemcisi kullanır.
- Testleri yürütmek ve test sonuçlarını raporlamak için bir test çalıştırıcısı kullanılır.
Tümleştirme testleri, her zamanki Düzenleme, Eylem ve Onay testi adımlarını içeren bir dizi olayı izler:
- SUT'nin web konağı yapılandırıldı.
- Uygulamaya istek göndermek için bir test sunucusu istemcisi oluşturulur.
- Test düzenleme adımı yürütülür: Test uygulaması bir istek hazırlar.
- Eylem test adımı yürütülür: İstemci isteği gönderir ve yanıtı alır.
- Assert test adımı yürütülür: Gerçek yanıt, beklenen yanıta göre geçti veya başarısız olarak doğrulanır.
- Tüm testler yürütülene kadar işlem devam eder.
- Test sonuçları bildirilir.
Genellikle test web konağı, test çalıştırmaları için uygulamanın normal web ana bilgisayarından farklı yapılandırılır. Örneğin, testler için farklı bir veritabanı veya farklı uygulama ayarları kullanılabilir.
Test web konağı ve bellek içi test sunucusu ()TestServer gibi altyapı bileşenleri Microsoft.AspNetCore.Mvc.Testing paketi tarafından sağlanır veya yönetilir. Bu paketin kullanımı test oluşturma ve yürütmeyi kolaylaştırır.
Paket Microsoft.AspNetCore.Mvc.Testing aşağıdaki görevleri işler:
- Bağımlılık dosyasını (
.deps) SUT'den test projesininbindizinine kopyalar. - testler yürütürken statik dosyaların ve sayfaların/görünümlerin bulunması için içerik kökünü SUT'nin proje köküne ayarlar.
- WebApplicationFactory sınıfını ile SUT önyüklemesini kolaylaştırmak için sağlar.
Birim testleri belgelerinde bir test projesi ve test çalıştırıcısının nasıl ayarlanacağı ve testleri çalıştırma hakkında ayrıntılı yönergelerin yanı sıra testleri ve test sınıflarını adlandırmaya yönelik öneriler açıklanmaktadır.
Birim testlerini ve tümleştirme testlerini ayrı projelere ayırın. Testleri ayırma:
- Altyapı testi bileşenlerinin birim testlerine yanlışlıkla dahil edilmesini önlemeye yardımcı olur.
- Hangi test kümesinin çalıştırıldığı üzerinde denetime izin verir.
Sayfalar uygulamalarının ve MVC uygulamalarının testlerinin Razor yapılandırması arasında neredeyse hiçbir fark yoktur. Tek fark, testlerin nasıl adlandırıldığındandır.
Razor Sayfalar uygulamasında, sayfa uç noktalarının testleri genellikle sayfa modeli sınıfından (örneğin, IndexPageTests Dizin sayfası için bileşen tümleştirmesini test etmek için) adlandırılır. MVC uygulamasında testler, genellikle test ettikleri denetleyici sınıflarına göre düzenlenir ve bu denetleyicilerin adlarını alır (örneğin, HomeControllerTests bileşen tümleştirmesini Home denetleyicisi için test etmek üzere).
Uygulama önkoşullarını test edin
Test projesinin aşağıdakileri yapması gerekir:
-
Microsoft.AspNetCore.Mvc.Testingpaketine başvurun. - Proje dosyasında (
<Project Sdk="Microsoft.NET.Sdk.Web">) Web SDK'sını belirtin.
Bu önkoşullar örnek uygulamada görülebilir.
tests/RazorPagesProject.Tests/RazorPagesProject.Tests.csproj Dosyayı inceleyin. Örnek uygulama xUnit test çerçevesini ve AngleSharp ayrıştırıcı kitaplığını kullandığından örnek uygulama da aşağıdakilere başvurur:
Sürüm 2.4.2 veya üzerini kullanan xunit.runner.visualstudio uygulamalarda, test projesi Microsoft.NET.Test.Sdk paketine başvurmalıdır.
Entity Framework Core, testlerde de kullanılır. GitHub'daki proje dosyasına bakın.
SUT ortamı
SUT'nin ortamı ayarlı değilse, ortam varsayılan olarak olarak Developmentayarlanır.
Varsayılan WebApplicationFactory ile temel testler
Aşağıdakilerden birini yaparak örtük olarak tanımlanmış Program sınıfı test projesinde kullanıma sunma:
Web uygulamasının iç türlerini test projesine açın. Bu işlem, SUT projesinin dosyasında (
.csproj) gerçekleştirilebilir.<ItemGroup> <InternalsVisibleTo Include="MyTestProject" /> </ItemGroup>Kısmi sınıf bildirimi kullanarak
Programsınıfı genel yapın:var builder = WebApplication.CreateBuilder(args); // ... Configure services, routes, etc. app.Run(); + public partial class Program { }Örnek uygulama kısmi sınıf yaklaşımını
Programkullanır.
WebApplicationFactory<TEntryPoint> tümleştirme testlerine yönelik bir TestServer oluşturmak için kullanılır.
TEntryPoint genellikle Program.csSUT'nin giriş noktası sınıfıdır.
Test sınıfları, sınıfın testleri içerdiğini belirtmek ve sınıftaki testler arasında paylaşılan nesne örnekleri sağlamak için bir sınıf fikstür arabirimi (IClassFixture) uygular.
Aşağıdaki test sınıfı, SUT'yi önyüklemek için BasicTests kullanır ve bir test yöntemine WebApplicationFactory sağlamak için HttpClient öğesini kullanır, Get_EndpointsReturnSuccessAndCorrectContentType. Yöntem, yanıt durum kodunun başarılı (200-299) olduğunu ve Content-Type üst bilgisinin birkaç uygulama sayfası için text/html; charset=utf-8 olduğunu doğrular.
CreateClient(), otomatik olarak yeniden yönlendirmeleri izleyen ve tanımlama bilgilerini işleyen bir HttpClient örneği oluşturur.
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());
}
}
Varsayılan olarak, Genel Veri Koruma Yönetmeliği onay ilkesi etkinleştirildiğinde gerekli olmayan tanımlama bilgileri istekler arasında korunmaz. Gerekli olmayan tanımlama bilgilerini, örneğin TempData sağlayıcısı tarafından kullanılanları, korumak için bunları testlerinizde önemli olarak işaretleyin. Bir cookie öğesini önemli olarak işaretleme talimatları için, bkz Temel tanımlama bilgileri.
AngleSharp ve Application Parts antiforgery kontrollerinin karşılaştırılması
Bu makalede, antiforgery denetimlerini işlemek için AngleSharp ayrıştırıcısı kullanılır; bu, sayfaları yükleyip HTML'yi ayrıştırarak gerçekleştirilir. Denetleyici ve Razor Sayfalar görünümlerinin uç noktalarını, tarayıcıda nasıl görüntülendiklerine önem vermeden daha alt düzeyde test etmek için Application Parts kullanmayı göz önünde bulundurun.
Uygulama Bölümleri yaklaşımı, uygulamaya gerekli değerleri almak için JSON isteklerinde bulunmak için kullanılabilecek bir denetleyici veya Razor Sayfa ekler. Daha fazla bilgi için Antiforgery Kullanarak ASP.NET Core Kaynaklarının Tümleştirme Testi ve Uygulama Parçaları blogunu ve Martin Costello tarafından oluşturulan ilişkili GitHub deposunu inceleyin.
WebApplicationFactory'i özelleştirme
Web sunucu yapılandırması, bir veya daha fazla özel fabrika oluşturmak için WebApplicationFactory<TEntryPoint> öğesinden devralınmak suretiyle test sınıflarından bağımsız olarak oluşturulabilir.
WebApplicationFactorysınıfından devralın ve ConfigureWebHost yöntemini geçersiz kılın. IWebHostBuilder hizmet koleksiyonununIWebHostBuilder.ConfigureServicesile yapılandırılmasına izin verir.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"); } }Veritabanı tohumlama, örnek uygulamada
InitializeDbForTestsyöntemi ile gerçekleştirilir. Yöntemi, Tümleştirme testleri örneği: Test uygulaması kuruluşu bölümünde açıklanmıştır.SUT'nin veritabanı bağlamı
Program.csiçinde kaydedilir. Test uygulamasınınbuilder.ConfigureServicesgeri çağırması, yürütülür. Testlerde uygulamanın veritabanından farklı bir veritabanı kullanmak için, uygulamanın veritabanı bağlamı içindebuilder.ConfigureServicesdeğiştirilmelidir.Örnek uygulama, veritabanı bağlamı için hizmet tanımlayıcısını bulur ve hizmet kaydını kaldırmak için tanımlayıcıyı kullanır. Ardından fabrika, testler için bellek içi veritabanı kullanan
ApplicationDbContextyeni bir ekliyor.Farklı bir veritabanına bağlanmak için öğesini
DbConnectiondeğiştirin. SQL Server test veritabanını kullanmak için:
- Proje dosyasındaKi
Microsoft.EntityFrameworkCore.SqlServerNuGet paketine başvurun. -
UseInMemoryDatabaseçağrısı yapın.
Test sınıflarında özel
CustomWebApplicationFactoryöğesini kullanın. Aşağıdaki örnek,IndexPageTestssınıfındaki fabrikayı kullanır: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 }); }Örnek uygulamanın istemcisi,
HttpClient'nin yeniden yönlendirmeleri takip etmemesi için yapılandırılır. Sahte kimlik doğrulaması bölümünde daha sonra açıklandığı gibi bu, testlerin uygulamanın ilk yanıtının sonucunu denetlemesine izin verir. İlk yanıt, bu testlerin çoğundaLocationbaşlık içeren bir yeniden yönlendirmedir.Tipik bir test, isteği ve yanıtı işlemek için
HttpClientve yardımcı yöntemlerini kullanır.[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); }
SUT'ye yapılan herhangi bir POST isteği, uygulamanın veri koruma önleme sistemi tarafından otomatik olarak yapılan kötü amaçlı yazılımdan koruma denetimini karşılamalıdır. Testin POST isteğini düzenlemek için test uygulamasının şunları yapması gerekir:
- Sayfa için bir istekte bulun.
- Yanıttan sahtecilik önleme (cookie) ve doğrulama belirtecini ayrıştırın.
- POST isteğini sahteciliğe cookie karşı koruma ve istek doğrulama belirteci ile gerçekleştirin.
Örnek
-
GetDocumentAsync: HttpResponseMessage alır ve birIHtmlDocumentdöndürür.GetDocumentAsync, özgün dayalı birHttpResponseMessagehazırlayan bir fabrika kullanır. Daha fazla bilgi için AngleSharp belgelerine bakın. -
SendAsyncuzantı yöntemleri,HttpClientbirleştirir ve HttpRequestMessage aracılığıyla SUT'ye istek göndermek için SendAsync(HttpRequestMessage) çağrısı yapar.SendAsynciçin aşırı yüklemeler, HTML formunu (IHtmlFormElement) ve aşağıdakileri kabul eder:- Formun Gönder düğmesi (
IHtmlElement) - Form değerleri koleksiyonu (
IEnumerable<KeyValuePair<string, string>>) - Gönder düğmesi (
IHtmlElement) ve form değerleri (IEnumerable<KeyValuePair<string, string>>)
- Formun Gönder düğmesi (
AngleSharp, bu makalede ve örnek uygulamada gösterim amacıyla kullanılan bir üçüncü taraf ayrıştırma kitaplığıdır. AngleSharp, ASP.NET Core uygulamalarının tümleştirme testi için desteklenmez veya gerekli değildir. Html Çeviklik Paketi (HAP) gibi diğer ayrıştırıcılar kullanılabilir. Bir diğer yaklaşım da, sahteciliğe karşı koruma sisteminin istek doğrulama belirtecini ve kötü amaçlı yazılımdan korumayı doğrudan işlemek için kod yazmaktır cookie . Daha fazla bilgi için bu makaledeki AngleSharp vs Application Parts antiforgery denetimlerine bakın.
EF-Core bellek içi veritabanı sağlayıcısı sınırlı ve temel test için kullanılabilir, ancak bellek içi test için önerilen seçenek SQLite sağlayıcısıdır.
StartUp'u başlangıç filtreleri ile genişletme hakkında bilgi için Başlangıç'ı Başlangıç Filtreleriyle Genişletme kısmına bakın. Bu, bir test özel bir hizmet veya ara yazılım gerektirdiğinde kullanılan IStartupFilter ile ara yazılımın nasıl yapılandırılacağını gösterir.
WithWebHostBuilder ile istemciyi özelleştirme
Ek yapılandırma bir test yönteminde gerekli olduğunda, WithWebHostBuilder bir WebApplicationFactory içeren ve yapılandırmayla daha da özelleştirilen yeni bir IWebHostBuilder oluşturur.
Örnek kod, yapılandırılmış hizmetleri test saptamalarıyla değiştirmek için çağırırWithWebHostBuilder. Daha fazla bilgi ve örnek kullanım için bu makaledeki Sahte hizmetler ekleme bölümüne bakın.
Post_DeleteMessageHandler_ReturnsRedirectToRoot
Örnek uygulamanın test yöntemi, WithWebHostBuilder kullanımını gösterir. Bu test, SUT'da form gönderimini tetikleyerek veritabanında bir kayıt silme işlemi gerçekleştirir.
Bir IndexPageTests sınıfındaki diğer bir test, veritabanındaki tüm kayıtları silen bir işlemi gerçekleştirebilir ve Post_DeleteMessageHandler_ReturnsRedirectToRoot yönteminden önce çalışabilir. Bu nedenle, SUT'nin silebilmesi için bir kaydın mevcut olduğundan emin olmak amacıyla bu test yönteminde veritabanı tekrar oluşturulur. SUT talebinde SUT'daki formun ilk sil düğmesinin messages seçilmesi simüle edilir.
[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);
}
İstemci seçenekleri
WebApplicationFactoryClientOptions Örnek oluştururken HttpClient varsayılanlar ve kullanılabilir seçenekler için sayfaya bakın.
WebApplicationFactoryClientOptions sınıfını oluşturup bunu CreateClient() yöntemine geçirin:
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
});
}
NOT: HTTPS Yeniden Yönlendirme ara yazılımı kullanılırken günlüklerde HTTPS yeniden yönlendirme uyarılarından kaçınmak için BaseAddress = new Uri("https://localhost")
Sahte hizmetleri enjekte et
Testte, ConfigureTestServices ile konak oluşturucusunda hizmetler geçersiz kılınabilir. Testin kendisine geçersiz kılınan hizmetleri tanımlamak için WithWebHostBuilder yöntemi, bir konak oluşturucu almak amacıyla kullanılır. Bu durum aşağıdaki testlerde görülebilir:
- Get_QuoteService_ProvidesQuoteInPage
- Get_GithubProfilePageCanGetAGithubUser
- Get_SecurePageIsReturnedForAnAuthenticatedUser
Örnek SUT, teklif döndüren kapsamlı bir hizmet içerir. Alıntı, Dizin sayfası istendiğinde Dizin sayfasındaki gizli bir alana yerleştirilir.
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">
SUT uygulaması çalıştırıldığında aşağıdaki işaretleme oluşturulur:
<input id="quote" type="hidden" value="Come on, Sarah. We've an appointment in
London, and we're already 30,000 years late.">
Tümleştirme testinde hizmeti ve teklif eklemeyi test etmek için test tarafından SUT'ye sahte hizmet eklenir. Sahte hizmet, uygulamanın QuoteService öğesini, test uygulaması tarafından sağlanan TestQuoteService adlı bir hizmetle değiştirir.
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 çağrılır ve kapsamı belirlenmiş hizmet kaydedilir:
[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);
}
Testin yürütülmesi sırasında oluşturulan belirteç, c0 tarafından sağlanan tırnak metnini yansıtır, bu nedenle doğrulama başarılı olur.
<input id="quote" type="hidden" value="Something's interfering with time,
Mr. Scarman, and time is my business.">
Sahte kimlik doğrulaması
Sınıfındaki AuthTests testler güvenli bir uç nokta olup olmadığını denetler:
- Kimliği doğrulanmamış bir kullanıcıyı uygulamanın oturum açma sayfasına yönlendirir.
- Kimliği doğrulanmış bir kullanıcının içeriğini döndürür.
SUT'de, /SecurePage sayfası, bir AuthorizePage kuralı kullanarak sayfaya bir AuthorizeFilter uygular. Daha fazla bilgi için bkz Razor . Sayfa yetkilendirme kuralları.
services.AddRazorPages(options =>
{
options.Conventions.AuthorizePage("/SecurePage");
});
Get_SecurePageRedirectsAnUnauthenticatedUser testinde, WebApplicationFactoryClientOptionsAllowAutoRedirectfalse olarak ayarlanarak yeniden yönlendirmelere izin vermemek üzere ayarlanmıştır.
[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);
}
İstemcinin yeniden yönlendirmeyi izlemesine izin vermeyerek aşağıdaki denetimler yapılabilir:
- SUT tarafından döndürülen durum kodu, oturum açma sayfasına yeniden yönlendirmeden sonraki son durum koduna değil, beklenen HttpStatusCode.Redirect sonuca göre denetlenebilir. Bu, HttpStatusCode.OK olacaktır.
- Yanıt üst bilgilerindeki
Locationüst bilgi değeri, üst bilginin bulunmayacağı son oturum açma sayfası yanıtıyla değil,http://localhost/Identity/Account/Loginile başladığını onaylamak için denetlenir.
Test uygulaması, kimlik doğrulama ve yetkilendirme unsurlarını test etmek için AuthenticationHandler<TOptions>'i ConfigureTestServices içinde simüle edebilir. En basit senaryo bir AuthenticateResult.Success döndürür.
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);
}
}
TestAuthHandler, kimlik doğrulama düzeni TestScheme olarak ayarlandığında, AddAuthentication için ConfigureTestServices kaydedildiğinde kullanıcının kimliğini doğrulamak için çağrılır. Uygulamanızın beklediği düzenle TestScheme düzeninin eşleşmesi önemlidir. Aksi takdirde kimlik doğrulaması çalışmaz.
[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);
}
hakkında WebApplicationFactoryClientOptionsdaha fazla bilgi için İstemci seçenekleri bölümüne bakın.
Kimlik doğrulaması ara yazılımı için temel testler
Kimlik doğrulama ara yazılımının temel testleri için bu GitHub deposuna bakın. Test senaryosuna özgü bir test sunucusu içerir.
Ortamı ayarlama
Ortamı özel uygulama fabrikasında ayarlayın:
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");
}
}
Test altyapısı uygulama içeriği kök yolunu nasıl çıkarsar?
Uygulama WebApplicationFactory yolunu çıkarsamak için oluşturucu, anahtarına eşit anahtarla tümleştirme testlerini içeren derlemede WebApplicationFactoryContentRootAttribute arar ve bu derleme TEntryPoint içerirSystem.Reflection.Assembly.FullName. Doğru anahtara sahip bir özniteliğin bulunamazsa, WebApplicationFactory çözüm dosyasını aramaya (.sln) geri döner ve derleme adını çözüm dizinine ekler TEntryPoint . Uygulama kök dizini (içerik kök yolu), görünümleri ve içerik dosyalarını bulmak için kullanılır.
Gölge kopyalamayı devre dışı bırakma
Gölge kopyalama, testlerin çıkış dizininden farklı bir dizinde yürütülmesine neden olur. Testleriniz Assembly.Location dosyalarını yüklemeye bağlıysa ve sorunlarla karşılaşırsanız, gölge kopyalamayı devre dışı bırakmanız gerekebilir.
xUnit kullanırken gölge kopyalamayı devre dışı bırakmak için test projesi dizininizde doğru yapılandırma ayarıyla xunit.runner.jsonbir dosya oluşturun:
{
"shadowCopy": false
}
Nesnelerin atılması
Testlerin yürütülmesinden sonra IClassFixture uygulaması için ve xUnit TestServer nesnesini yok ettiğinde, HttpClient ve WebApplicationFactory de kapatılır. Geliştirici tarafından oluşturulan nesneler yok edilmesi gerekiyorsa, bu nesneleri IClassFixture uygulamasında yok edin. Daha fazla bilgi için bkz Bir Dispose yöntemi uygulamak.
Tümleştirme testleri örneği
Örnek uygulama iki uygulamadan oluşur:
| App | Proje dizini | Description |
|---|---|---|
| İleti uygulaması (SUT) | src/RazorPagesProject |
Kullanıcının bir ileti eklemesine, silmesine, tümünü silmesine ve iletileri analiz etmesine izin verir. |
| Test uygulaması | tests/RazorPagesProject.Tests |
SUT'yi tümleştirme testi için kullanılır. |
Testler, Visual Studio gibi bir IDE'nin yerleşik test özellikleri kullanılarak çalıştırılabilir. Visual Studio Code veya komut satırı kullanıyorsanız, dizinindeki bir komut isteminde aşağıdaki komutu yürütün:
dotnet test
İleti uygulaması (SUT) kuruluşu
SUT, aşağıdaki özelliklere sahip bir Razor Sayfalar ileti sistemidir:
- Uygulamanın (
Pages/Index.cshtmlvePages/Index.cshtml.cs) Dizin sayfası, iletilerin eklenmesini, silinmesini ve analizini (ileti başına ortalama sözcükler) denetlemek için bir kullanıcı arabirimi ve sayfa modeli yöntemleri sağlar. - Bir ileti,
Messagesınıfı (Data/Message.cs) tarafından iki özelliğe sahip olarak açıklanır:Id(anahtar) veText(ileti).Textözelliği gereklidir ve 200 karakterle sınırlıdır. - İletiler Entity Framework'ün bellek içi veritabanı† kullanılarak depolanır.
- Uygulama, veritabanı bağlam sınıfında
AppDbContext() bir veri erişim katmanı (Data/AppDbContext.csDAL) içerir. - Uygulama başlangıcında veritabanı boşsa, ileti deposu üç iletiyle başlatılır.
- Uygulama, yalnızca kimliği doğrulanmış bir kullanıcı tarafından erişilebilen bir
/SecurePageiçerir.
†EF makalesi InMemory ile Test, MSTest ile testler için bellek içi veritabanının nasıl kullanılacağını açıklar. Bu konu, xUnit test çerçevesini kullanır. Farklı test çerçevelerindeki test kavramları ve test uygulamaları benzerdir ancak aynı değildir.
Uygulama depo desenini kullanmasa ve çalışma birimi (UoW) deseninin etkili bir örneği olmasa da, Razor Sayfalar bu geliştirme desenlerini destekler. Daha fazla bilgi için bkz . Altyapı kalıcılık katmanını tasarlama ve Test denetleyicisi mantığı (örnek, depo düzenini uygular).
Uygulama kuruluşunu test et
Test uygulaması, dizinin içindeki tests/RazorPagesProject.Tests bir konsol uygulamasıdır.
| Uygulama dizinini test et | Description |
|---|---|
AuthTests |
Aşağıdakiler için test yöntemlerini içerir:
|
BasicTests |
Yönlendirme ve içerik türü için bir test yöntemi içerir. |
IntegrationTests |
Özel WebApplicationFactory sınıf kullanarak Dizin sayfasının tümleştirme testlerini içerir. |
Helpers/Utilities |
|
Test çerçevesi xUnit'tir. Tümleştirme testleri, Microsoft.AspNetCore.TestHost kullanılarak, TestServer öğesini içeren yapılır.
Microsoft.AspNetCore.Mvc.Testing Paket test ana bilgisayarını ve test sunucusunu yapılandırmak için kullanıldığından TestHost ve TestServer paketleri, test uygulamasının proje dosyasında veya test uygulamasındaki geliştirici yapılandırmasında doğrudan paket başvuruları gerektirmez.
Tümleştirme testleri genellikle test yürütmeden önce veritabanında küçük bir veri kümesi gerektirir. Örneğin, silme testi veritabanı kaydı silme işlemini çağırır, bu nedenle silme isteğinin başarılı olması için veritabanında en az bir kayıt olmalıdır.
Örnek uygulama, testlerin çalıştırıldığında kullanabileceği üç iletiyi Utilities.cs veritabanına ekler:
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." }
};
}
SUT'nin veritabanı bağlamı Program.cs içinde kaydedilir. Test uygulamasının builder.ConfigureServices geri çağırması, yürütülür. Testlerde farklı bir veritabanı kullanmak için, uygulamanın veritabanı bağlamı builder.ConfigureServices içinde değiştirilmelidir. Daha fazla bilgi için WebApplicationFactory'yi Özelleştirme bölümüne bakın.
Ek kaynaklar
Bu makalede, birim testleri hakkında temel bilgiler olduğu varsayılır. Test kavramlarını bilmiyorsanız .NET'te test etme makalesine ve bağlantılı içeriğine bakın.
Örnek kodu görüntüleme veya indirme (indirme)
Örnek uygulama bir Razor Pages uygulamasıdır ve Razor Sayfalar hakkında temel bir anlayış olduğunu varsayar. Pages'ı tanımıyorsanız, Razor aşağıdaki makalelere bakın.
SPA'ları test etme amacıyla tarayıcıyı otomatikleştirebilen .NET için Playwright gibi bir araç öneririz.
Tümleştirme testlerine giriş
Tümleştirme testleri, bir uygulamanın bileşenlerini birim testlerinden daha geniş bir düzeyde değerlendirir. Birim testleri, tek tek sınıf yöntemleri gibi yalıtılmış yazılım bileşenlerini test etmek için kullanılır. Tümleştirme testleri, iki veya daha fazla uygulama bileşeninin birlikte çalışarak beklenen bir sonuç elde ettiğini ve büyük olasılıkla bir isteği tam olarak işlemek için gereken her bileşenin de dahil olduğunu onaylar.
Bu daha geniş testler, genellikle aşağıdaki bileşenler de dahil olmak üzere uygulamanın altyapısını ve tüm çerçevesini test etmek için kullanılır:
- Database
- Dosya sistemi
- Ağ gereçleri
- İstek-yanıt işlem hattı
Birim testleri, altyapı bileşenleri yerine sahte veya sahte nesneler olarak bilinen fabrikatlanmış bileşenleri kullanır.
Birim testlerinin aksine tümleştirme testleri:
- Uygulamanın üretimde kullandığı gerçek bileşenleri kullanın.
- Daha fazla kod ve veri işleme gerektirir.
- Çalıştırılması daha uzun sürer.
Bu nedenle, tümleştirme testlerinin kullanımını en önemli altyapı senaryolarıyla sınırlayın. Bir davranış birim testi veya tümleştirme testi kullanılarak test edilebiliyorsa birim testini seçin.
Tümleştirme testlerinin tartışmalarında, test edilen projeye genellikle Test Altındaki Sistem veya kısaca "SUT" adı verilir. "SUT" bu makalede test edilen ASP.NET Core uygulamasına başvurmak için kullanılır.
Veritabanları ve dosya sistemleriyle verilerin ve dosya erişiminin her permütasyonu için tümleştirme testleri yazmayın. Bir uygulama genelinde veritabanları ve dosya sistemleriyle kaç yerde etkileşim kurduğundan bağımsız olarak, odaklanmış bir dizi okuma, yazma, güncelleştirme ve silme tümleştirme testi genellikle veritabanı ve dosya sistemi bileşenlerini yeterince test edebilmektedir. Bu bileşenlerle etkileşim kuran yöntem mantığının rutin testleri için birim testlerini kullanın. Birim testlerinde, altyapı sahte nesneleri veya taklitlerinin kullanımı, testlerin daha hızlı yürütülmesine olanak tanır.
ASP.NET Core tümleştirme testleri
ASP.NET Core'daki tümleştirme testleri aşağıdakileri gerektirir:
- Testleri içermek ve yürütmek için bir test projesi kullanılır. Test projesinin SUT'a referansı vardır.
- Test projesi SUT için bir test web konağı oluşturur ve SUT ile istekleri ve yanıtları işlemek için bir test sunucusu istemcisi kullanır.
- Testleri yürütmek ve test sonuçlarını raporlamak için bir test çalıştırıcısı kullanılır.
Tümleştirme testleri, her zamanki Düzenleme, Eylem ve Onay testi adımlarını içeren bir dizi olayı izler:
- SUT'nin web konağı yapılandırıldı.
- Uygulamaya istek göndermek için bir test sunucusu istemcisi oluşturulur.
- Test düzenleme adımı yürütülür: Test uygulaması bir istek hazırlar.
- Eylem test adımı yürütülür: İstemci isteği gönderir ve yanıtı alır.
- Assert test adımı yürütülür: Gerçek yanıt, beklenen yanıta göre geçti veya başarısız olarak doğrulanır.
- Tüm testler yürütülene kadar işlem devam eder.
- Test sonuçları bildirilir.
Genellikle test web konağı, test çalıştırmaları için uygulamanın normal web ana bilgisayarından farklı yapılandırılır. Örneğin, testler için farklı bir veritabanı veya farklı uygulama ayarları kullanılabilir.
Test web konağı ve bellek içi test sunucusu ()TestServer gibi altyapı bileşenleri Microsoft.AspNetCore.Mvc.Testing paketi tarafından sağlanır veya yönetilir. Bu paketin kullanımı test oluşturma ve yürütmeyi kolaylaştırır.
Paket Microsoft.AspNetCore.Mvc.Testing aşağıdaki görevleri işler:
- Bağımlılık dosyasını (
.deps) SUT'den test projesininbindizinine kopyalar. - testler yürütürken statik dosyaların ve sayfaların/görünümlerin bulunması için içerik kökünü SUT'nin proje köküne ayarlar.
- WebApplicationFactory sınıfını ile SUT önyüklemesini kolaylaştırmak için sağlar.
Birim testleri belgelerinde bir test projesi ve test çalıştırıcısının nasıl ayarlanacağı ve testleri çalıştırma hakkında ayrıntılı yönergelerin yanı sıra testleri ve test sınıflarını adlandırmaya yönelik öneriler açıklanmaktadır.
Birim testlerini ve tümleştirme testlerini ayrı projelere ayırın. Testleri ayırma:
- Altyapı testi bileşenlerinin birim testlerine yanlışlıkla dahil edilmesini önlemeye yardımcı olur.
- Hangi test kümesinin çalıştırıldığı üzerinde denetime izin verir.
Sayfalar uygulamalarının ve MVC uygulamalarının testlerinin Razor yapılandırması arasında neredeyse hiçbir fark yoktur. Tek fark, testlerin nasıl adlandırıldığındandır.
Razor Sayfalar uygulamasında, sayfa uç noktalarının testleri genellikle sayfa modeli sınıfından (örneğin, IndexPageTests Dizin sayfası için bileşen tümleştirmesini test etmek için) adlandırılır. MVC uygulamasında testler, genellikle test ettikleri denetleyici sınıflarına göre düzenlenir ve bu denetleyicilerin adlarını alır (örneğin, HomeControllerTests bileşen tümleştirmesini Home denetleyicisi için test etmek üzere).
Uygulama önkoşullarını test edin
Test projesinin aşağıdakileri yapması gerekir:
-
Microsoft.AspNetCore.Mvc.Testingpaketine başvurun. - Proje dosyasında (
<Project Sdk="Microsoft.NET.Sdk.Web">) Web SDK'sını belirtin.
Bu önkoşullar örnek uygulamada görülebilir.
tests/RazorPagesProject.Tests/RazorPagesProject.Tests.csproj Dosyayı inceleyin. Örnek uygulama xUnit test çerçevesini ve AngleSharp ayrıştırıcı kitaplığını kullandığından örnek uygulama da aşağıdakilere başvurur:
Sürüm 2.4.2 veya üzerini kullanan xunit.runner.visualstudio uygulamalarda, test projesi Microsoft.NET.Test.Sdk paketine başvurmalıdır.
Entity Framework Core, testlerde de kullanılır. GitHub'daki proje dosyasına bakın.
SUT ortamı
SUT'nin ortamı ayarlı değilse, ortam varsayılan olarak olarak Developmentayarlanır.
Varsayılan WebApplicationFactory ile temel testler
Aşağıdakilerden birini yaparak örtük olarak tanımlanmış Program sınıfı test projesinde kullanıma sunma:
Web uygulamasının iç türlerini test projesine açın. Bu işlem, SUT projesinin dosyasında (
.csproj) gerçekleştirilebilir.<ItemGroup> <InternalsVisibleTo Include="MyTestProject" /> </ItemGroup>Kısmi sınıf bildirimi kullanarak
Programsınıfı genel yapın:var builder = WebApplication.CreateBuilder(args); // ... Configure services, routes, etc. app.Run(); + public partial class Program { }Örnek uygulama kısmi sınıf yaklaşımını
Programkullanır.
WebApplicationFactory<TEntryPoint> tümleştirme testlerine yönelik bir TestServer oluşturmak için kullanılır.
TEntryPoint genellikle Program.csSUT'nin giriş noktası sınıfıdır.
Test sınıfları, sınıfın testleri içerdiğini belirtmek ve sınıftaki testler arasında paylaşılan nesne örnekleri sağlamak için bir sınıf fikstür arabirimi (IClassFixture) uygular.
Aşağıdaki test sınıfı, SUT'yi önyüklemek için BasicTests kullanır ve bir test yöntemine WebApplicationFactory sağlamak için HttpClient öğesini kullanır, Get_EndpointsReturnSuccessAndCorrectContentType. Yöntem, yanıt durum kodunun başarılı (200-299) olduğunu ve Content-Type üst bilgisinin birkaç uygulama sayfası için text/html; charset=utf-8 olduğunu doğrular.
CreateClient(), otomatik olarak yeniden yönlendirmeleri izleyen ve tanımlama bilgilerini işleyen bir HttpClient örneği oluşturur.
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());
}
}
Varsayılan olarak, Genel Veri Koruma Yönetmeliği onay ilkesi etkinleştirildiğinde gerekli olmayan tanımlama bilgileri istekler arasında korunmaz. Gerekli olmayan tanımlama bilgilerini, örneğin TempData sağlayıcısı tarafından kullanılanları, korumak için bunları testlerinizde önemli olarak işaretleyin. Bir cookie öğesini önemli olarak işaretleme talimatları için, bkz Temel tanımlama bilgileri.
AngleSharp ve Application Parts antiforgery kontrollerinin karşılaştırılması
Bu makalede, antiforgery denetimlerini işlemek için AngleSharp ayrıştırıcısı kullanılır; bu, sayfaları yükleyip HTML'yi ayrıştırarak gerçekleştirilir. Denetleyici ve Razor Sayfalar görünümlerinin uç noktalarını, tarayıcıda nasıl görüntülendiklerine önem vermeden daha alt düzeyde test etmek için Application Parts kullanmayı göz önünde bulundurun.
Uygulama Bölümleri yaklaşımı, uygulamaya gerekli değerleri almak için JSON isteklerinde bulunmak için kullanılabilecek bir denetleyici veya Razor Sayfa ekler. Daha fazla bilgi için Antiforgery Kullanarak ASP.NET Core Kaynaklarının Tümleştirme Testi ve Uygulama Parçaları blogunu ve Martin Costello tarafından oluşturulan ilişkili GitHub deposunu inceleyin.
WebApplicationFactory'i özelleştirme
Web sunucu yapılandırması, bir veya daha fazla özel fabrika oluşturmak için WebApplicationFactory<TEntryPoint> öğesinden devralınmak suretiyle test sınıflarından bağımsız olarak oluşturulabilir.
WebApplicationFactorysınıfından devralın ve ConfigureWebHost yöntemini geçersiz kılın. IWebHostBuilder hizmet koleksiyonununIWebHostBuilder.ConfigureServicesile yapılandırılmasına izin verir.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"); } }Veritabanı tohumlama, örnek uygulamada
InitializeDbForTestsyöntemi ile gerçekleştirilir. Yöntemi, Tümleştirme testleri örneği: Test uygulaması kuruluşu bölümünde açıklanmıştır.SUT'nin veritabanı bağlamı
Program.csiçinde kaydedilir. Test uygulamasınınbuilder.ConfigureServicesgeri çağırması, yürütülür. Testlerde uygulamanın veritabanından farklı bir veritabanı kullanmak için, uygulamanın veritabanı bağlamı içindebuilder.ConfigureServicesdeğiştirilmelidir.Örnek uygulama, veritabanı bağlamı için hizmet tanımlayıcısını bulur ve hizmet kaydını kaldırmak için tanımlayıcıyı kullanır. Ardından fabrika, testler için bellek içi veritabanı kullanan
ApplicationDbContextyeni bir ekliyor.Farklı bir veritabanına bağlanmak için öğesini
DbConnectiondeğiştirin. SQL Server test veritabanını kullanmak için:- Proje dosyasındaKi
Microsoft.EntityFrameworkCore.SqlServerNuGet paketine başvurun. -
UseInMemoryDatabaseçağrısı yapın.
- Proje dosyasındaKi
Test sınıflarında özel
CustomWebApplicationFactoryöğesini kullanın. Aşağıdaki örnek,IndexPageTestssınıfındaki fabrikayı kullanır: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 }); } }Örnek uygulamanın istemcisi,
HttpClient'nin yeniden yönlendirmeleri takip etmemesi için yapılandırılır. Sahte kimlik doğrulaması bölümünde daha sonra açıklandığı gibi bu, testlerin uygulamanın ilk yanıtının sonucunu denetlemesine izin verir. İlk yanıt, bu testlerin çoğundaLocationbaşlık içeren bir yeniden yönlendirmedir.Tipik bir test, isteği ve yanıtı işlemek için
HttpClientve yardımcı yöntemlerini kullanır.[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); }
SUT'ye yapılan herhangi bir POST isteği, uygulamanın veri koruma önleme sistemi tarafından otomatik olarak yapılan kötü amaçlı yazılımdan koruma denetimini karşılamalıdır. Testin POST isteğini düzenlemek için test uygulamasının şunları yapması gerekir:
- Sayfa için bir istekte bulun.
- Yanıttan sahtecilik önleme (cookie) ve doğrulama belirtecini ayrıştırın.
- POST isteğini sahteciliğe cookie karşı koruma ve istek doğrulama belirteci ile gerçekleştirin.
Örnek
-
GetDocumentAsync: HttpResponseMessage alır ve birIHtmlDocumentdöndürür.GetDocumentAsync, özgün dayalı birHttpResponseMessagehazırlayan bir fabrika kullanır. Daha fazla bilgi için AngleSharp belgelerine bakın. -
SendAsyncuzantı yöntemleri,HttpClientbirleştirir ve HttpRequestMessage aracılığıyla SUT'ye istek göndermek için SendAsync(HttpRequestMessage) çağrısı yapar.SendAsynciçin aşırı yüklemeler, HTML formunu (IHtmlFormElement) ve aşağıdakileri kabul eder:- Formun Gönder düğmesi (
IHtmlElement) - Form değerleri koleksiyonu (
IEnumerable<KeyValuePair<string, string>>) - Gönder düğmesi (
IHtmlElement) ve form değerleri (IEnumerable<KeyValuePair<string, string>>)
- Formun Gönder düğmesi (
AngleSharp, bu makalede ve örnek uygulamada gösterim amacıyla kullanılan bir üçüncü taraf ayrıştırma kitaplığıdır. AngleSharp, ASP.NET Core uygulamalarının tümleştirme testi için desteklenmez veya gerekli değildir. Html Çeviklik Paketi (HAP) gibi diğer ayrıştırıcılar kullanılabilir. Bir diğer yaklaşım da, sahteciliğe karşı koruma sisteminin istek doğrulama belirtecini ve kötü amaçlı yazılımdan korumayı doğrudan işlemek için kod yazmaktır cookie . Daha fazla bilgi için bu makaledeki AngleSharp vs Application Parts antiforgery denetimlerine bakın.
EF-Core bellek içi veritabanı sağlayıcısı sınırlı ve temel test için kullanılabilir, ancak bellek içi test için önerilen seçenek SQLite sağlayıcısıdır.
StartUp'u başlangıç filtreleri ile genişletme hakkında bilgi için Başlangıç'ı Başlangıç Filtreleriyle Genişletme kısmına bakın. Bu, bir test özel bir hizmet veya ara yazılım gerektirdiğinde kullanılan IStartupFilter ile ara yazılımın nasıl yapılandırılacağını gösterir.
WithWebHostBuilder ile istemciyi özelleştirme
Ek yapılandırma bir test yönteminde gerekli olduğunda, WithWebHostBuilder bir WebApplicationFactory içeren ve yapılandırmayla daha da özelleştirilen yeni bir IWebHostBuilder oluşturur.
Örnek kod, yapılandırılmış hizmetleri test saptamalarıyla değiştirmek için çağırırWithWebHostBuilder. Daha fazla bilgi ve örnek kullanım için bu makaledeki Sahte hizmetler ekleme bölümüne bakın.
Post_DeleteMessageHandler_ReturnsRedirectToRoot
Örnek uygulamanın test yöntemi, WithWebHostBuilder kullanımını gösterir. Bu test, SUT'da form gönderimini tetikleyerek veritabanında bir kayıt silme işlemi gerçekleştirir.
Bir IndexPageTests sınıfındaki diğer bir test, veritabanındaki tüm kayıtları silen bir işlemi gerçekleştirebilir ve Post_DeleteMessageHandler_ReturnsRedirectToRoot yönteminden önce çalışabilir. Bu nedenle, SUT'nin silebilmesi için bir kaydın mevcut olduğundan emin olmak amacıyla bu test yönteminde veritabanı tekrar oluşturulur. SUT talebinde SUT'daki formun ilk sil düğmesinin messages seçilmesi simüle edilir.
[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);
}
İstemci seçenekleri
WebApplicationFactoryClientOptions Örnek oluştururken HttpClient varsayılanlar ve kullanılabilir seçenekler için sayfaya bakın.
WebApplicationFactoryClientOptions sınıfını oluşturup bunu CreateClient() yöntemine geçirin:
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
});
}
}
NOT: HTTPS Yeniden Yönlendirme ara yazılımı kullanılırken günlüklerde HTTPS yeniden yönlendirme uyarılarından kaçınmak için BaseAddress = new Uri("https://localhost")
Sahte hizmetleri enjekte et
Testte, ConfigureTestServices ile konak oluşturucusunda hizmetler geçersiz kılınabilir. Testin kendisine geçersiz kılınan hizmetleri tanımlamak için WithWebHostBuilder yöntemi, bir konak oluşturucu almak amacıyla kullanılır. Bu durum aşağıdaki testlerde görülebilir:
- Get_QuoteService_ProvidesQuoteInPage
- Get_GithubProfilePageCanGetAGithubUser
- Get_SecurePageIsReturnedForAnAuthenticatedUser
Örnek SUT, teklif döndüren kapsamlı bir hizmet içerir. Alıntı, Dizin sayfası istendiğinde Dizin sayfasındaki gizli bir alana yerleştirilir.
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">
SUT uygulaması çalıştırıldığında aşağıdaki işaretleme oluşturulur:
<input id="quote" type="hidden" value="Come on, Sarah. We've an appointment in
London, and we're already 30,000 years late.">
Tümleştirme testinde hizmeti ve teklif eklemeyi test etmek için test tarafından SUT'ye sahte hizmet eklenir. Sahte hizmet, uygulamanın QuoteService öğesini, test uygulaması tarafından sağlanan TestQuoteService adlı bir hizmetle değiştirir.
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 çağrılır ve kapsamı belirlenmiş hizmet kaydedilir:
[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);
}
Testin yürütülmesi sırasında oluşturulan belirteç, c0 tarafından sağlanan tırnak metnini yansıtır, bu nedenle doğrulama başarılı olur.
<input id="quote" type="hidden" value="Something's interfering with time,
Mr. Scarman, and time is my business.">
Sahte kimlik doğrulaması
Sınıfındaki AuthTests testler güvenli bir uç nokta olup olmadığını denetler:
- Kimliği doğrulanmamış bir kullanıcıyı uygulamanın oturum açma sayfasına yönlendirir.
- Kimliği doğrulanmış bir kullanıcının içeriğini döndürür.
SUT'de, /SecurePage sayfası, bir AuthorizePage kuralı kullanarak sayfaya bir AuthorizeFilter uygular. Daha fazla bilgi için bkz Razor . Sayfa yetkilendirme kuralları.
services.AddRazorPages(options =>
{
options.Conventions.AuthorizePage("/SecurePage");
});
Get_SecurePageRedirectsAnUnauthenticatedUser testinde, WebApplicationFactoryClientOptionsAllowAutoRedirectfalse olarak ayarlanarak yeniden yönlendirmelere izin vermemek üzere ayarlanmıştır.
[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);
}
İstemcinin yeniden yönlendirmeyi izlemesine izin vermeyerek aşağıdaki denetimler yapılabilir:
- SUT tarafından döndürülen durum kodu, oturum açma sayfasına yeniden yönlendirmeden sonraki son durum koduna değil, beklenen HttpStatusCode.Redirect sonuca göre denetlenebilir. Bu, HttpStatusCode.OK olacaktır.
- Yanıt üst bilgilerindeki
Locationüst bilgi değeri, üst bilginin bulunmayacağı son oturum açma sayfası yanıtıyla değil,http://localhost/Identity/Account/Loginile başladığını onaylamak için denetlenir.
Test uygulaması, kimlik doğrulama ve yetkilendirme unsurlarını test etmek için AuthenticationHandler<TOptions>'i ConfigureTestServices içinde simüle edebilir. En basit senaryo bir AuthenticateResult.Success döndürür.
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);
}
}
TestAuthHandler, kimlik doğrulama düzeni TestScheme olarak ayarlandığında, AddAuthentication için ConfigureTestServices kaydedildiğinde kullanıcının kimliğini doğrulamak için çağrılır. Uygulamanızın beklediği düzenle TestScheme düzeninin eşleşmesi önemlidir. Aksi takdirde kimlik doğrulaması çalışmaz.
[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);
}
hakkında WebApplicationFactoryClientOptionsdaha fazla bilgi için İstemci seçenekleri bölümüne bakın.
Kimlik doğrulaması ara yazılımı için temel testler
Kimlik doğrulama ara yazılımının temel testleri için bu GitHub deposuna bakın. Test senaryosuna özgü bir test sunucusu içerir.
Ortamı ayarlama
Ortamı özel uygulama fabrikasında ayarlayın:
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");
}
}
Test altyapısı uygulama içeriği kök yolunu nasıl çıkarsar?
Uygulama WebApplicationFactory yolunu çıkarsamak için oluşturucu, anahtarına eşit anahtarla tümleştirme testlerini içeren derlemede WebApplicationFactoryContentRootAttribute arar ve bu derleme TEntryPoint içerirSystem.Reflection.Assembly.FullName. Doğru anahtara sahip bir özniteliğin bulunamazsa, WebApplicationFactory çözüm dosyasını aramaya (.sln) geri döner ve derleme adını çözüm dizinine ekler TEntryPoint . Uygulama kök dizini (içerik kök yolu), görünümleri ve içerik dosyalarını bulmak için kullanılır.
Gölge kopyalamayı devre dışı bırakma
Gölge kopyalama, testlerin çıkış dizininden farklı bir dizinde yürütülmesine neden olur. Testleriniz Assembly.Location dosyalarını yüklemeye bağlıysa ve sorunlarla karşılaşırsanız, gölge kopyalamayı devre dışı bırakmanız gerekebilir.
xUnit kullanırken gölge kopyalamayı devre dışı bırakmak için test projesi dizininizde doğru yapılandırma ayarıyla xunit.runner.jsonbir dosya oluşturun:
{
"shadowCopy": false
}
Nesnelerin atılması
Testlerin yürütülmesinden sonra IClassFixture uygulaması için ve xUnit TestServer nesnesini yok ettiğinde, HttpClient ve WebApplicationFactory de kapatılır. Geliştirici tarafından oluşturulan nesneler yok edilmesi gerekiyorsa, bu nesneleri IClassFixture uygulamasında yok edin. Daha fazla bilgi için bkz Bir Dispose yöntemi uygulamak.
Tümleştirme testleri örneği
Örnek uygulama iki uygulamadan oluşur:
| App | Proje dizini | Description |
|---|---|---|
| İleti uygulaması (SUT) | src/RazorPagesProject |
Kullanıcının bir ileti eklemesine, silmesine, tümünü silmesine ve iletileri analiz etmesine izin verir. |
| Test uygulaması | tests/RazorPagesProject.Tests |
SUT'yi tümleştirme testi için kullanılır. |
Testler, Visual Studio gibi bir IDE'nin yerleşik test özellikleri kullanılarak çalıştırılabilir. Visual Studio Code veya komut satırı kullanıyorsanız, dizinindeki bir komut isteminde aşağıdaki komutu yürütün:
dotnet test
İleti uygulaması (SUT) kuruluşu
SUT, aşağıdaki özelliklere sahip bir Razor Sayfalar ileti sistemidir:
- Uygulamanın (
Pages/Index.cshtmlvePages/Index.cshtml.cs) Dizin sayfası, iletilerin eklenmesini, silinmesini ve analizini (ileti başına ortalama sözcükler) denetlemek için bir kullanıcı arabirimi ve sayfa modeli yöntemleri sağlar. - Bir ileti,
Messagesınıfı (Data/Message.cs) tarafından iki özelliğe sahip olarak açıklanır:Id(anahtar) veText(ileti).Textözelliği gereklidir ve 200 karakterle sınırlıdır. - İletiler Entity Framework'ün bellek içi veritabanı† kullanılarak depolanır.
- Uygulama, veritabanı bağlam sınıfında
AppDbContext() bir veri erişim katmanı (Data/AppDbContext.csDAL) içerir. - Uygulama başlangıcında veritabanı boşsa, ileti deposu üç iletiyle başlatılır.
- Uygulama, yalnızca kimliği doğrulanmış bir kullanıcı tarafından erişilebilen bir
/SecurePageiçerir.
†EF makalesi InMemory ile Test, MSTest ile testler için bellek içi veritabanının nasıl kullanılacağını açıklar. Bu konu, xUnit test çerçevesini kullanır. Farklı test çerçevelerindeki test kavramları ve test uygulamaları benzerdir ancak aynı değildir.
Uygulama depo desenini kullanmasa ve çalışma birimi (UoW) deseninin etkili bir örneği olmasa da, Razor Sayfalar bu geliştirme desenlerini destekler. Daha fazla bilgi için bkz . Altyapı kalıcılık katmanını tasarlama ve Test denetleyicisi mantığı (örnek, depo düzenini uygular).
Uygulama kuruluşunu test et
Test uygulaması, dizinin içindeki tests/RazorPagesProject.Tests bir konsol uygulamasıdır.
| Uygulama dizinini test et | Description |
|---|---|
AuthTests |
Aşağıdakiler için test yöntemlerini içerir:
|
BasicTests |
Yönlendirme ve içerik türü için bir test yöntemi içerir. |
IntegrationTests |
Özel WebApplicationFactory sınıf kullanarak Dizin sayfasının tümleştirme testlerini içerir. |
Helpers/Utilities |
|
Test çerçevesi xUnit'tir. Tümleştirme testleri, Microsoft.AspNetCore.TestHost kullanılarak, TestServer öğesini içeren yapılır.
Microsoft.AspNetCore.Mvc.Testing Paket test ana bilgisayarını ve test sunucusunu yapılandırmak için kullanıldığından TestHost ve TestServer paketleri, test uygulamasının proje dosyasında veya test uygulamasındaki geliştirici yapılandırmasında doğrudan paket başvuruları gerektirmez.
Tümleştirme testleri genellikle test yürütmeden önce veritabanında küçük bir veri kümesi gerektirir. Örneğin, silme testi veritabanı kaydı silme işlemini çağırır, bu nedenle silme isteğinin başarılı olması için veritabanında en az bir kayıt olmalıdır.
Örnek uygulama, testlerin çalıştırıldığında kullanabileceği üç iletiyi Utilities.cs veritabanına ekler:
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." }
};
}
SUT'nin veritabanı bağlamı Program.cs içinde kaydedilir. Test uygulamasının builder.ConfigureServices geri çağırması, yürütülür. Testlerde farklı bir veritabanı kullanmak için, uygulamanın veritabanı bağlamı builder.ConfigureServices içinde değiştirilmelidir. Daha fazla bilgi için WebApplicationFactory'yi Özelleştirme bölümüne bakın.
Ek kaynaklar
Bu makalede, birim testleri hakkında temel bilgiler olduğu varsayılır. Test kavramlarını bilmiyorsanız .NET'te test etme makalesine ve bağlantılı içeriğine bakın.
Örnek kodu görüntüleme veya indirme (indirme)
Örnek uygulama bir Razor Pages uygulamasıdır ve Razor Sayfalar hakkında temel bir anlayış olduğunu varsayar. Pages'ı tanımıyorsanız, Razor aşağıdaki makalelere bakın.
SPA'ları test etme amacıyla tarayıcıyı otomatikleştirebilen .NET için Playwright gibi bir araç öneririz.
Tümleştirme testlerine giriş
Tümleştirme testleri, bir uygulamanın bileşenlerini birim testlerinden daha geniş bir düzeyde değerlendirir. Birim testleri, tek tek sınıf yöntemleri gibi yalıtılmış yazılım bileşenlerini test etmek için kullanılır. Tümleştirme testleri, iki veya daha fazla uygulama bileşeninin birlikte çalışarak beklenen bir sonuç elde ettiğini ve büyük olasılıkla bir isteği tam olarak işlemek için gereken her bileşenin de dahil olduğunu onaylar.
Bu daha geniş testler, genellikle aşağıdaki bileşenler de dahil olmak üzere uygulamanın altyapısını ve tüm çerçevesini test etmek için kullanılır:
- Database
- Dosya sistemi
- Ağ gereçleri
- İstek-yanıt işlem hattı
Birim testleri, altyapı bileşenleri yerine sahte veya sahte nesneler olarak bilinen fabrikatlanmış bileşenleri kullanır.
Birim testlerinin aksine tümleştirme testleri:
- Uygulamanın üretimde kullandığı gerçek bileşenleri kullanın.
- Daha fazla kod ve veri işleme gerektirir.
- Çalıştırılması daha uzun sürer.
Bu nedenle, tümleştirme testlerinin kullanımını en önemli altyapı senaryolarıyla sınırlayın. Bir davranış birim testi veya tümleştirme testi kullanılarak test edilebiliyorsa birim testini seçin.
Tümleştirme testlerinin tartışmalarında, test edilen projeye genellikle Test Altındaki Sistem veya kısaca "SUT" adı verilir. "SUT" bu makalede test edilen ASP.NET Core uygulamasına başvurmak için kullanılır.
Veritabanları ve dosya sistemleriyle verilerin ve dosya erişiminin her permütasyonu için tümleştirme testleri yazmayın. Bir uygulama genelinde veritabanları ve dosya sistemleriyle kaç yerde etkileşim kurduğundan bağımsız olarak, odaklanmış bir dizi okuma, yazma, güncelleştirme ve silme tümleştirme testi genellikle veritabanı ve dosya sistemi bileşenlerini yeterince test edebilmektedir. Bu bileşenlerle etkileşim kuran yöntem mantığının rutin testleri için birim testlerini kullanın. Birim testlerinde, altyapı sahte nesneleri veya taklitlerinin kullanımı, testlerin daha hızlı yürütülmesine olanak tanır.
ASP.NET Core tümleştirme testleri
ASP.NET Core'daki tümleştirme testleri aşağıdakileri gerektirir:
- Testleri içermek ve yürütmek için bir test projesi kullanılır. Test projesinin SUT'a referansı vardır.
- Test projesi SUT için bir test web konağı oluşturur ve SUT ile istekleri ve yanıtları işlemek için bir test sunucusu istemcisi kullanır.
- Testleri yürütmek ve test sonuçlarını raporlamak için bir test çalıştırıcısı kullanılır.
Tümleştirme testleri, her zamanki Düzenleme, Eylem ve Onay testi adımlarını içeren bir dizi olayı izler:
- SUT'nin web konağı yapılandırıldı.
- Uygulamaya istek göndermek için bir test sunucusu istemcisi oluşturulur.
- Test düzenleme adımı yürütülür: Test uygulaması bir istek hazırlar.
- Eylem test adımı yürütülür: İstemci isteği gönderir ve yanıtı alır.
- Assert test adımı yürütülür: Gerçek yanıt, beklenen yanıta göre geçti veya başarısız olarak doğrulanır.
- Tüm testler yürütülene kadar işlem devam eder.
- Test sonuçları bildirilir.
Genellikle test web konağı, test çalıştırmaları için uygulamanın normal web ana bilgisayarından farklı yapılandırılır. Örneğin, testler için farklı bir veritabanı veya farklı uygulama ayarları kullanılabilir.
Test web konağı ve bellek içi test sunucusu ()TestServer gibi altyapı bileşenleri Microsoft.AspNetCore.Mvc.Testing paketi tarafından sağlanır veya yönetilir. Bu paketin kullanımı test oluşturma ve yürütmeyi kolaylaştırır.
Paket Microsoft.AspNetCore.Mvc.Testing aşağıdaki görevleri işler:
- Bağımlılık dosyasını (
.deps) SUT'den test projesininbindizinine kopyalar. - testler yürütürken statik dosyaların ve sayfaların/görünümlerin bulunması için içerik kökünü SUT'nin proje köküne ayarlar.
- WebApplicationFactory sınıfını ile SUT önyüklemesini kolaylaştırmak için sağlar.
Birim testleri belgelerinde bir test projesi ve test çalıştırıcısının nasıl ayarlanacağı ve testleri çalıştırma hakkında ayrıntılı yönergelerin yanı sıra testleri ve test sınıflarını adlandırmaya yönelik öneriler açıklanmaktadır.
Birim testlerini ve tümleştirme testlerini ayrı projelere ayırın. Testleri ayırma:
- Altyapı testi bileşenlerinin birim testlerine yanlışlıkla dahil edilmesini önlemeye yardımcı olur.
- Hangi test kümesinin çalıştırıldığı üzerinde denetime izin verir.
Sayfalar uygulamalarının ve MVC uygulamalarının testlerinin Razor yapılandırması arasında neredeyse hiçbir fark yoktur. Tek fark, testlerin nasıl adlandırıldığındandır.
Razor Sayfalar uygulamasında, sayfa uç noktalarının testleri genellikle sayfa modeli sınıfından (örneğin, IndexPageTests Dizin sayfası için bileşen tümleştirmesini test etmek için) adlandırılır. MVC uygulamasında testler, genellikle test ettikleri denetleyici sınıflarına göre düzenlenir ve bu denetleyicilerin adlarını alır (örneğin, HomeControllerTests bileşen tümleştirmesini Home denetleyicisi için test etmek üzere).
Uygulama önkoşullarını test edin
Test projesinin aşağıdakileri yapması gerekir:
-
Microsoft.AspNetCore.Mvc.Testingpaketine başvurun. - Proje dosyasında (
<Project Sdk="Microsoft.NET.Sdk.Web">) Web SDK'sını belirtin.
Bu önkoşullar örnek uygulamada görülebilir.
tests/RazorPagesProject.Tests/RazorPagesProject.Tests.csproj Dosyayı inceleyin. Örnek uygulama xUnit test çerçevesini ve AngleSharp ayrıştırıcı kitaplığını kullandığından örnek uygulama da aşağıdakilere başvurur:
Sürüm 2.4.2 veya üzerini kullanan xunit.runner.visualstudio uygulamalarda, test projesi Microsoft.NET.Test.Sdk paketine başvurmalıdır.
Entity Framework Core, testlerde de kullanılır. GitHub'daki proje dosyasına bakın.
SUT ortamı
SUT'nin ortamı ayarlı değilse, ortam varsayılan olarak olarak Developmentayarlanır.
Varsayılan WebApplicationFactory ile temel testler
Aşağıdakilerden birini yaparak örtük olarak tanımlanmış Program sınıfı test projesinde kullanıma sunma:
Web uygulamasının iç türlerini test projesine açın. Bu işlem, SUT projesinin dosyasında (
.csproj) gerçekleştirilebilir.<ItemGroup> <InternalsVisibleTo Include="MyTestProject" /> </ItemGroup>Kısmi sınıf bildirimi kullanarak
Programsınıfı genel yapın:var builder = WebApplication.CreateBuilder(args); // ... Configure services, routes, etc. app.Run(); + public partial class Program { }Örnek uygulama kısmi sınıf yaklaşımını
Programkullanır.
WebApplicationFactory<TEntryPoint> tümleştirme testlerine yönelik bir TestServer oluşturmak için kullanılır.
TEntryPoint genellikle Program.csSUT'nin giriş noktası sınıfıdır.
Test sınıfları, sınıfın testleri içerdiğini belirtmek ve sınıftaki testler arasında paylaşılan nesne örnekleri sağlamak için bir sınıf fikstür arabirimi (IClassFixture) uygular.
Aşağıdaki test sınıfı, SUT'yi önyüklemek için BasicTests kullanır ve bir test yöntemine WebApplicationFactory sağlamak için HttpClient öğesini kullanır, Get_EndpointsReturnSuccessAndCorrectContentType. Yöntem, yanıt durum kodunun başarılı (200-299) olduğunu ve Content-Type üst bilgisinin birkaç uygulama sayfası için text/html; charset=utf-8 olduğunu doğrular.
CreateClient(), otomatik olarak yeniden yönlendirmeleri izleyen ve tanımlama bilgilerini işleyen bir HttpClient örneği oluşturur.
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"));
}
}
Varsayılan olarak, Genel Veri Koruma Yönetmeliği onay ilkesi etkinleştirildiğinde gerekli olmayan tanımlama bilgileri istekler arasında korunmaz. Gerekli olmayan tanımlama bilgilerini, örneğin TempData sağlayıcısı tarafından kullanılanları, korumak için bunları testlerinizde önemli olarak işaretleyin. Bir cookie öğesini önemli olarak işaretleme talimatları için, bkz Temel tanımlama bilgileri.
AngleSharp ve Application Parts antiforgery kontrollerinin karşılaştırılması
Bu makalede, antiforgery denetimlerini işlemek için AngleSharp ayrıştırıcısı kullanılır; bu, sayfaları yükleyip HTML'yi ayrıştırarak gerçekleştirilir. Denetleyici ve Razor Sayfalar görünümlerinin uç noktalarını, tarayıcıda nasıl görüntülendiklerine önem vermeden daha alt düzeyde test etmek için Application Parts kullanmayı göz önünde bulundurun.
Uygulama Bölümleri yaklaşımı, uygulamaya gerekli değerleri almak için JSON isteklerinde bulunmak için kullanılabilecek bir denetleyici veya Razor Sayfa ekler. Daha fazla bilgi için Antiforgery Kullanarak ASP.NET Core Kaynaklarının Tümleştirme Testi ve Uygulama Parçaları blogunu ve Martin Costello tarafından oluşturulan ilişkili GitHub deposunu inceleyin.
WebApplicationFactory'i özelleştirme
Web sunucu yapılandırması, bir veya daha fazla özel fabrika oluşturmak için WebApplicationFactory<TEntryPoint> öğesinden devralınmak suretiyle test sınıflarından bağımsız olarak oluşturulabilir.
WebApplicationFactorysınıfından devralın ve ConfigureWebHost yöntemini geçersiz kılın. IWebHostBuilder hizmet koleksiyonununIWebHostBuilder.ConfigureServicesile yapılandırılmasına izin verir.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"); } }Veritabanı tohumlama, örnek uygulamada
InitializeDbForTestsyöntemi ile gerçekleştirilir. Yöntemi, Tümleştirme testleri örneği: Test uygulaması kuruluşu bölümünde açıklanmıştır.SUT'nin veritabanı bağlamı
Program.csiçinde kaydedilir. Test uygulamasınınbuilder.ConfigureServicesgeri çağırması, yürütülür. Testlerde uygulamanın veritabanından farklı bir veritabanı kullanmak için, uygulamanın veritabanı bağlamı içindebuilder.ConfigureServicesdeğiştirilmelidir.Örnek uygulama, veritabanı bağlamı için hizmet tanımlayıcısını bulur ve hizmet kaydını kaldırmak için tanımlayıcıyı kullanır. Ardından fabrika, testler için bellek içi veritabanı kullanan
ApplicationDbContextyeni bir ekliyor.Farklı bir veritabanına bağlanmak için öğesini
DbConnectiondeğiştirin. SQL Server test veritabanını kullanmak için:- Proje dosyasındaKi
Microsoft.EntityFrameworkCore.SqlServerNuGet paketine başvurun. -
UseInMemoryDatabaseçağrısı yapın.
- Proje dosyasındaKi
Test sınıflarında özel
CustomWebApplicationFactoryöğesini kullanın. Aşağıdaki örnek,IndexPageTestssınıfındaki fabrikayı kullanır: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(); }Örnek uygulamanın istemcisi,
HttpClient'nin yeniden yönlendirmeleri takip etmemesi için yapılandırılır. Sahte kimlik doğrulaması bölümünde daha sonra açıklandığı gibi bu, testlerin uygulamanın ilk yanıtının sonucunu denetlemesine izin verir. İlk yanıt, bu testlerin çoğundaLocationbaşlık içeren bir yeniden yönlendirmedir.Tipik bir test, isteği ve yanıtı işlemek için
HttpClientve yardımcı yöntemlerini kullanır.[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("/")); }
SUT'ye yapılan herhangi bir POST isteği, uygulamanın veri koruma önleme sistemi tarafından otomatik olarak yapılan kötü amaçlı yazılımdan koruma denetimini karşılamalıdır. Testin POST isteğini düzenlemek için test uygulamasının şunları yapması gerekir:
- Sayfa için bir istekte bulun.
- Yanıttan sahtecilik önleme (cookie) ve doğrulama belirtecini ayrıştırın.
- POST isteğini sahteciliğe cookie karşı koruma ve istek doğrulama belirteci ile gerçekleştirin.
Örnek
-
GetDocumentAsync: HttpResponseMessage alır ve birIHtmlDocumentdöndürür.GetDocumentAsync, özgün dayalı birHttpResponseMessagehazırlayan bir fabrika kullanır. Daha fazla bilgi için AngleSharp belgelerine bakın. -
SendAsyncuzantı yöntemleri,HttpClientbirleştirir ve HttpRequestMessage aracılığıyla SUT'ye istek göndermek için SendAsync(HttpRequestMessage) çağrısı yapar.SendAsynciçin aşırı yüklemeler, HTML formunu (IHtmlFormElement) ve aşağıdakileri kabul eder:- Formun Gönder düğmesi (
IHtmlElement) - Form değerleri koleksiyonu (
IEnumerable<KeyValuePair<string, string>>) - Gönder düğmesi (
IHtmlElement) ve form değerleri (IEnumerable<KeyValuePair<string, string>>)
- Formun Gönder düğmesi (
AngleSharp, bu makalede ve örnek uygulamada gösterim amacıyla kullanılan bir üçüncü taraf ayrıştırma kitaplığıdır. AngleSharp, ASP.NET Core uygulamalarının tümleştirme testi için desteklenmez veya gerekli değildir. Html Çeviklik Paketi (HAP) gibi diğer ayrıştırıcılar kullanılabilir. Bir diğer yaklaşım da, sahteciliğe karşı koruma sisteminin istek doğrulama belirtecini ve kötü amaçlı yazılımdan korumayı doğrudan işlemek için kod yazmaktır cookie . Daha fazla bilgi için bu makaledeki AngleSharp vs Application Parts antiforgery denetimlerine bakın.
EF-Core bellek içi veritabanı sağlayıcısı sınırlı ve temel test için kullanılabilir, ancak bellek içi test için önerilen seçenek SQLite sağlayıcısıdır.
StartUp'u başlangıç filtreleri ile genişletme hakkında bilgi için Başlangıç'ı Başlangıç Filtreleriyle Genişletme kısmına bakın. Bu, bir test özel bir hizmet veya ara yazılım gerektirdiğinde kullanılan IStartupFilter ile ara yazılımın nasıl yapılandırılacağını gösterir.
WithWebHostBuilder ile istemciyi özelleştirme
Ek yapılandırma bir test yönteminde gerekli olduğunda, WithWebHostBuilder bir WebApplicationFactory içeren ve yapılandırmayla daha da özelleştirilen yeni bir IWebHostBuilder oluşturur.
Örnek kod, yapılandırılmış hizmetleri test saptamalarıyla değiştirmek için çağırırWithWebHostBuilder. Daha fazla bilgi ve örnek kullanım için bu makaledeki Sahte hizmetler ekleme bölümüne bakın.
Post_DeleteMessageHandler_ReturnsRedirectToRoot
Örnek uygulamanın test yöntemi, WithWebHostBuilder kullanımını gösterir. Bu test, SUT'da form gönderimini tetikleyerek veritabanında bir kayıt silme işlemi gerçekleştirir.
Bir IndexPageTests sınıfındaki diğer bir test, veritabanındaki tüm kayıtları silen bir işlemi gerçekleştirebilir ve Post_DeleteMessageHandler_ReturnsRedirectToRoot yönteminden önce çalışabilir. Bu nedenle, SUT'nin silebilmesi için bir kaydın mevcut olduğundan emin olmak amacıyla bu test yönteminde veritabanı tekrar oluşturulur. SUT talebinde SUT'daki formun ilk sil düğmesinin messages seçilmesi simüle edilir.
[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("/"));
}
İstemci seçenekleri
WebApplicationFactoryClientOptions Örnek oluştururken HttpClient varsayılanlar ve kullanılabilir seçenekler için sayfaya bakın.
WebApplicationFactoryClientOptions sınıfını oluşturup bunu CreateClient() yöntemine geçirin:
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();
}
NOT: HTTPS Yeniden Yönlendirme ara yazılımı kullanılırken günlüklerde HTTPS yeniden yönlendirme uyarılarından kaçınmak için BaseAddress = new Uri("https://localhost")
Sahte hizmetleri enjekte et
Testte, ConfigureTestServices ile konak oluşturucusunda hizmetler geçersiz kılınabilir. Testin kendisine geçersiz kılınan hizmetleri tanımlamak için WithWebHostBuilder yöntemi, bir konak oluşturucu almak amacıyla kullanılır. Bu durum aşağıdaki testlerde görülebilir:
- Get_QuoteService_ProvidesQuoteInPage
- Get_GithubProfilePageCanGetAGithubUser
- Get_SecurePageIsReturnedForAnAuthenticatedUser
Örnek SUT, teklif döndüren kapsamlı bir hizmet içerir. Alıntı, Dizin sayfası istendiğinde Dizin sayfasındaki gizli bir alana yerleştirilir.
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">
SUT uygulaması çalıştırıldığında aşağıdaki işaretleme oluşturulur:
<input id="quote" type="hidden" value="Come on, Sarah. We've an appointment in
London, and we're already 30,000 years late.">
Tümleştirme testinde hizmeti ve teklif eklemeyi test etmek için test tarafından SUT'ye sahte hizmet eklenir. Sahte hizmet, uygulamanın QuoteService öğesini, test uygulaması tarafından sağlanan TestQuoteService adlı bir hizmetle değiştirir.
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 çağrılır ve kapsamı belirlenmiş hizmet kaydedilir:
[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."));
}
Testin yürütülmesi sırasında oluşturulan belirteç, c0 tarafından sağlanan tırnak metnini yansıtır, bu nedenle doğrulama başarılı olur.
<input id="quote" type="hidden" value="Something's interfering with time,
Mr. Scarman, and time is my business.">
Sahte kimlik doğrulaması
Sınıfındaki AuthTests testler güvenli bir uç nokta olup olmadığını denetler:
- Kimliği doğrulanmamış bir kullanıcıyı uygulamanın oturum açma sayfasına yönlendirir.
- Kimliği doğrulanmış bir kullanıcının içeriğini döndürür.
SUT'de, /SecurePage sayfası, bir AuthorizePage kuralı kullanarak sayfaya bir AuthorizeFilter uygular. Daha fazla bilgi için bkz Razor . Sayfa yetkilendirme kuralları.
services.AddRazorPages(options =>
{
options.Conventions.AuthorizePage("/SecurePage");
});
Get_SecurePageRedirectsAnUnauthenticatedUser testinde, WebApplicationFactoryClientOptionsAllowAutoRedirectfalse olarak ayarlanarak yeniden yönlendirmelere izin vermemek üzere ayarlanmıştır.
[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"));
}
İstemcinin yeniden yönlendirmeyi izlemesine izin vermeyerek aşağıdaki denetimler yapılabilir:
- SUT tarafından döndürülen durum kodu, oturum açma sayfasına yeniden yönlendirmeden sonraki son durum koduna değil, beklenen HttpStatusCode.Redirect sonuca göre denetlenebilir. Bu, HttpStatusCode.OK olacaktır.
- Yanıt üst bilgilerindeki
Locationüst bilgi değeri, üst bilginin bulunmayacağı son oturum açma sayfası yanıtıyla değil,http://localhost/Identity/Account/Loginile başladığını onaylamak için denetlenir.
Test uygulaması, kimlik doğrulama ve yetkilendirme unsurlarını test etmek için AuthenticationHandler<TOptions>'i ConfigureTestServices içinde simüle edebilir. En basit senaryo bir AuthenticateResult.Success döndürür.
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);
}
}
TestAuthHandler, kimlik doğrulama düzeni TestScheme olarak ayarlandığında, AddAuthentication için ConfigureTestServices kaydedildiğinde kullanıcının kimliğini doğrulamak için çağrılır. Uygulamanızın beklediği düzenle TestScheme düzeninin eşleşmesi önemlidir. Aksi takdirde kimlik doğrulaması çalışmaz.
[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));
}
hakkında WebApplicationFactoryClientOptionsdaha fazla bilgi için İstemci seçenekleri bölümüne bakın.
Kimlik doğrulaması ara yazılımı için temel testler
Kimlik doğrulama ara yazılımının temel testleri için bu GitHub deposuna bakın. Test senaryosuna özgü bir test sunucusu içerir.
Ortamı ayarlama
Ortamı özel uygulama fabrikasında ayarlayın:
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");
}
}
Test altyapısı uygulama içeriği kök yolunu nasıl çıkarsar?
Uygulama WebApplicationFactory yolunu çıkarsamak için oluşturucu, anahtarına eşit anahtarla tümleştirme testlerini içeren derlemede WebApplicationFactoryContentRootAttribute arar ve bu derleme TEntryPoint içerirSystem.Reflection.Assembly.FullName. Doğru anahtara sahip bir özniteliğin bulunamazsa, WebApplicationFactory çözüm dosyasını aramaya (.sln) geri döner ve derleme adını çözüm dizinine ekler TEntryPoint . Uygulama kök dizini (içerik kök yolu), görünümleri ve içerik dosyalarını bulmak için kullanılır.
Gölge kopyalamayı devre dışı bırakma
Gölge kopyalama, testlerin çıkış dizininden farklı bir dizinde yürütülmesine neden olur. Testleriniz Assembly.Location dosyalarını yüklemeye bağlıysa ve sorunlarla karşılaşırsanız, gölge kopyalamayı devre dışı bırakmanız gerekebilir.
xUnit kullanırken gölge kopyalamayı devre dışı bırakmak için test projesi dizininizde doğru yapılandırma ayarıyla xunit.runner.jsonbir dosya oluşturun:
{
"shadowCopy": false
}
Nesnelerin atılması
Testlerin yürütülmesinden sonra IClassFixture uygulaması için ve xUnit TestServer nesnesini yok ettiğinde, HttpClient ve WebApplicationFactory de kapatılır. Geliştirici tarafından oluşturulan nesneler yok edilmesi gerekiyorsa, bu nesneleri IClassFixture uygulamasında yok edin. Daha fazla bilgi için bkz Bir Dispose yöntemi uygulamak.
testler TestClass yürütüldükten sonra, MSTest TestServer yönteminde HttpClient öğesini attığında, WebApplicationFactory ve ClassCleanup atılır. Geliştirici tarafından örneklenen nesnelerin atılması gerekiyorsa, bunları ClassCleanup yönteminde atın. Daha fazla bilgi için bkz Bir Dispose yöntemi uygulamak.
Test sınıfının testleri yürütüldükten sonra, TestServer ve HttpClient öğeleri, WebApplicationFactory metodunda NUnit TearDown atma işlemi yapıldığında atılır. Geliştirici tarafından örneklenen nesnelerin atılması gerekiyorsa, bunları TearDown yönteminde atın. Daha fazla bilgi için bkz Bir Dispose yöntemi uygulamak.
Tümleştirme testleri örneği
Örnek uygulama iki uygulamadan oluşur:
| App | Proje dizini | Description |
|---|---|---|
| İleti uygulaması (SUT) | src/RazorPagesProject |
Kullanıcının bir ileti eklemesine, silmesine, tümünü silmesine ve iletileri analiz etmesine izin verir. |
| Test uygulaması | tests/RazorPagesProject.Tests |
SUT'yi tümleştirme testi için kullanılır. |
Testler, Visual Studio gibi bir IDE'nin yerleşik test özellikleri kullanılarak çalıştırılabilir. Visual Studio Code veya komut satırı kullanıyorsanız, dizinindeki bir komut isteminde aşağıdaki komutu yürütün:
dotnet test
İleti uygulaması (SUT) kuruluşu
SUT, aşağıdaki özelliklere sahip bir Razor Sayfalar ileti sistemidir:
- Uygulamanın (
Pages/Index.cshtmlvePages/Index.cshtml.cs) Dizin sayfası, iletilerin eklenmesini, silinmesini ve analizini (ileti başına ortalama sözcükler) denetlemek için bir kullanıcı arabirimi ve sayfa modeli yöntemleri sağlar. - Bir ileti,
Messagesınıfı (Data/Message.cs) tarafından iki özelliğe sahip olarak açıklanır:Id(anahtar) veText(ileti).Textözelliği gereklidir ve 200 karakterle sınırlıdır. - İletiler Entity Framework'ün bellek içi veritabanı† kullanılarak depolanır.
- Uygulama, veritabanı bağlam sınıfında
AppDbContext() bir veri erişim katmanı (Data/AppDbContext.csDAL) içerir. - Uygulama başlangıcında veritabanı boşsa, ileti deposu üç iletiyle başlatılır.
- Uygulama, yalnızca kimliği doğrulanmış bir kullanıcı tarafından erişilebilen bir
/SecurePageiçerir.
†EF makalesi InMemory ile Test, MSTest ile testler için bellek içi veritabanının nasıl kullanılacağını açıklar. Bu konu, xUnit test çerçevesini kullanır. Farklı test çerçevelerindeki test kavramları ve test uygulamaları benzerdir ancak aynı değildir.
Uygulama depo desenini kullanmasa ve çalışma birimi (UoW) deseninin etkili bir örneği olmasa da, Razor Sayfalar bu geliştirme desenlerini destekler. Daha fazla bilgi için bkz . Altyapı kalıcılık katmanını tasarlama ve Test denetleyicisi mantığı (örnek, depo düzenini uygular).
Uygulama kuruluşunu test et
Test uygulaması, dizinin içindeki tests/RazorPagesProject.Tests bir konsol uygulamasıdır.
| Uygulama dizinini test et | Description |
|---|---|
AuthTests |
Aşağıdakiler için test yöntemlerini içerir:
|
BasicTests |
Yönlendirme ve içerik türü için bir test yöntemi içerir. |
IntegrationTests |
Özel WebApplicationFactory sınıf kullanarak Dizin sayfasının tümleştirme testlerini içerir. |
Helpers/Utilities |
|
Test çerçevesi xUnit'tir. Tümleştirme testleri, Microsoft.AspNetCore.TestHost kullanılarak, TestServer öğesini içeren yapılır.
Microsoft.AspNetCore.Mvc.Testing Paket test ana bilgisayarını ve test sunucusunu yapılandırmak için kullanıldığından TestHost ve TestServer paketleri, test uygulamasının proje dosyasında veya test uygulamasındaki geliştirici yapılandırmasında doğrudan paket başvuruları gerektirmez.
Tümleştirme testleri genellikle test yürütmeden önce veritabanında küçük bir veri kümesi gerektirir. Örneğin, silme testi veritabanı kaydı silme işlemini çağırır, bu nedenle silme isteğinin başarılı olması için veritabanında en az bir kayıt olmalıdır.
Örnek uygulama, testlerin çalıştırıldığında kullanabileceği üç iletiyi Utilities.cs veritabanına ekler:
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." }
};
}
SUT'nin veritabanı bağlamı Program.cs içinde kaydedilir. Test uygulamasının builder.ConfigureServices geri çağırması, yürütülür. Testlerde farklı bir veritabanı kullanmak için, uygulamanın veritabanı bağlamı builder.ConfigureServices içinde değiştirilmelidir. Daha fazla bilgi için WebApplicationFactory'yi Özelleştirme bölümüne bakın.
Ek kaynaklar
ASP.NET Core