Dela via


Enhetstestning

Tips

Det här innehållet är ett utdrag från eBook, Enterprise Application Patterns Using .NET MAUI, tillgängligt på .NET Docs eller som en kostnadsfri nedladdningsbar PDF som kan läsas offline.

Enterprise-applikationsmönster med .NET MAUI miniatyrbild av e-bokomslaget.

Appar med flera plattformar har problem som liknar både skrivbordsprogram och webbaserade program. Mobila användare skiljer sig åt mellan sina enheter, nätverksanslutningar, tillgänglighet för tjänster och olika andra faktorer. Därför bör appar med flera plattformar testas eftersom de skulle användas i verkligheten för att förbättra deras kvalitet, tillförlitlighet och prestanda. Många typer av testning bör utföras på en app, inklusive enhetstestning, integreringstestning och testning av användargränssnitt. Enhetstestning är den vanligaste formen och nödvändig för att skapa högkvalitativa program.

Ett enhetstest tar en liten del av appen, vanligtvis en metod, isolerar den från resten av koden och verifierar att den fungerar som förväntat. Målet är att kontrollera att varje funktionsenhet fungerar som förväntat, så att fel inte sprids i hela appen. Det är effektivare att identifiera en bugg där den inträffar än att observera effekten av en bugg indirekt vid en sekundär felpunkt.

Enhetstestning har den viktigaste effekten på kodkvaliteten när det är en integrerad del av arbetsflödet för programvaruutveckling. Enhetstester kan fungera som designdokumentation och funktionella specifikationer för ett program. Så snart en metod har skrivits ska enhetstester skrivas som verifierar metodens beteende som svar på standard-, gräns- och felaktiga indatafall och kontrollerar eventuella explicita eller implicita antaganden som görs av koden. Alternativt skrivs enhetstester före koden med testdriven utveckling. Mer information om testdriven utveckling och hur du implementerar den finns i Genomgång: Testdriven utveckling med testutforskaren..

Kommentar

Enhetstester är mycket effektiva mot regression. Det vill: funktioner som tidigare fungerade, men som har störts av en felaktig uppdatering.

Enhetstester använder vanligtvis mönstret ordna-agera-kontrollera:

Steg beskrivning
Ordna Initierar objekt och anger värdet för de data som skickas till metoden under test.
Akt Anropar metoden som testas med de argument som krävs.
Hävda Verifierar att åtgärden för metoden som testas fungerar som förväntat.

Det här mönstret säkerställer att enhetstester är läsbara, självbeskrivande och konsekventa.

Beroendeinjektion och enhetstestning

En av motiveringarna för att införa en löst kopplad arkitektur är att den underlättar enhetstestning. En av de typer som registrerats med tjänsten för beroendeinjektion är IAppEnvironmentService-gränssnittet. I följande kodexempel visas en disposition för den här klassen:

public class OrderDetailViewModel : ViewModelBase
{
    private IAppEnvironmentService _appEnvironmentService;

    public OrderDetailViewModel(
        IAppEnvironmentService appEnvironmentService,
        IDialogService dialogService, INavigationService navigationService, ISettingsService settingsService)
        : base(dialogService, navigationService, settingsService)
    {
        _appEnvironmentService = appEnvironmentService;
    }
}

Klassen OrderDetailViewModel har ett beroende av typen IAppEnvironmentService , som containern för beroendeinmatning löser när den instansierar ett OrderDetailViewModel objekt. Istället för att skapa ett IAppEnvironmentService-objekt som använder riktiga servrar, enheter och konfigurationer för att enhetstesta OrderDetailViewModel-klassen, ersätt i stället IAppEnvironmentService-objektet med en mock-objekt i syfte att utföra testerna. Ett simulerat objekt är ett objekt som har samma signatur för ett objekt eller ett gränssnitt, men som skapas på ett specifikt sätt för att hjälpa till med enhetstestning. Den används ofta med beroendeinmatning för att tillhandahålla specifika implementeringar av gränssnitt för testning av olika data- och arbetsflödesscenarier.

Den här metoden tillåter att IAppEnvironmentService-objektet skickas in i OrderDetailViewModel-klassen under körning, och för testbarhetens skull kan en mock-klass skickas in i OrderDetailViewModel-klassen vid testtillfället. Den största fördelen med den här metoden är att den gör det möjligt att köra enhetstester utan att kräva otympliga resurser, till exempel körningsplattformsfunktioner, webbtjänster eller databaser.

Testa MVVM-program

Testning av modeller och visningsmodeller från MVVM-program är identiskt med testning av andra klasser och använder samma verktyg och tekniker. Detta inkluderar funktioner som enhetstestning och modellering. Vissa mönster som är typiska för modell- och visningsmodellklasser kan dock dra nytta av specifika enhetstestningstekniker.

Tips

Testa en sak med varje enhetstest. I takt med att komplexiteten i ett test expanderar blir verifieringen av testet svårare. Genom att begränsa ett enhetstest till ett enda problem kan vi se till att våra tester är mer repeterbara, isolerade och har en mindre körningstid. Mer metodtips finns i Metodtips för enhetstestning med .NET .

Var inte frestad att göra en enhetstestövning mer än en aspekt av enhetens beteende. Detta leder till tester som är svåra att läsa och uppdatera. Det kan också leda till förvirring vid tolkning av ett fel.

EShop-appen för flera plattformar använder MSTest för att utföra enhetstestning, som stöder två olika typer av enhetstester:

Testtyp Attribut beskrivning
Testmetod TestMethod Definierar den faktiska testmetoden som ska köras.
Datakälla DataSource Tester som endast gäller för en viss uppsättning data.

Enhetstesterna som ingår i eShop-appen för flera plattformar är TestMethod, så varje enhetstestmetod är dekorerad med TestMethod attributet. Förutom MSTest finns det flera andra testramverk tillgängliga, inklusive NUnit och xUnit.

Testa asynkrona funktioner

När du implementerar MVVM-mönstret anropar vymodeller vanligtvis åtgärder på tjänster, ofta asynkront. Tester för kod som anropar dessa operationer använder vanligtvis mock-objekt som ersättning för de faktiska tjänsterna. Följande kodexempel visar hur du testar asynkrona funktioner genom att skicka en falsk tjänst till en vymodell:

[TestMethod]
public async Task OrderPropertyIsNotNullAfterViewModelInitializationTest()
{
    // Arrange
    var orderService = new OrderMockService();
    var orderViewModel = new OrderDetailViewModel(orderService);

    // Act
    var order = await orderService.GetOrderAsync(1, GlobalSetting.Instance.AuthToken);
    await orderViewModel.InitializeAsync(order);

    // Assert
    Assert.IsNotNull(orderViewModel.Order);
}

Det här enhetstestet kontrollerar att instansens OrderOrderDetailViewModel egenskap har ett värde när InitializeAsync metoden har anropats. Metoden InitializeAsync anropas när vymodellens motsvarande vy navigeras till. Mer information om navigering finns i Navigering.

När instansen OrderDetailViewModel skapas förväntar den sig att en IOrderService instans anges som ett argument. Men OrderService hämtar data från en webbtjänst. Därför anges en OrderMockService instans, en modellversion av OrderService klassen, som argument till OrderDetailViewModel konstruktorn. Sedan hämtas mockdata istället för att kommunicera med en webbtjänst när InitializeAsync-metoden i visningsmodellen anropas, vilket använder IOrderService-operationer.

Testa INotifyPropertyChanged-implementeringar

Genom att implementera INotifyPropertyChanged-gränssnittet kan vyer reagera på ändringar som kommer från vymodeller och modeller. Dessa ändringar är inte begränsade till data som visas i kontroller – de används också för att styra vyn, till exempel visningsmodelltillstånd som gör att animeringar startas eller kontroller inaktiveras.

Egenskaper som kan uppdateras direkt av enhetstestet kan testas genom att koppla en händelsehanterare till PropertyChanged händelsen och kontrollera om händelsen genereras efter att du har angett ett nytt värde för egenskapen. Följande kodexempel visar ett sådant test:

[TestMethod]
public async Task SettingOrderPropertyShouldRaisePropertyChanged()
{
    var invoked = false;
    var orderService = new OrderMockService();
    var orderViewModel = new OrderDetailViewModel(orderService);

    orderViewModel.PropertyChanged += (sender, e) =>
    {
        if (e.PropertyName.Equals("Order"))
            invoked = true;
    };
    var order = await orderService.GetOrderAsync(1, GlobalSetting.Instance.AuthToken);
    await orderViewModel.InitializeAsync(order);

    Assert.IsTrue(invoked);
}

Det här enhetstestet anropar metoden InitializeAsync för klassen OrderViewModel, vilket gör att dess egenskap Order uppdateras. Enhetstestet kommer att godkännas, förutsatt att PropertyChanged-händelsen genereras för Order-egenskapen.

Testa meddelandebaserad kommunikation

Visa modeller som använder MessagingCenter klassen för att kommunicera mellan löst kopplade klasser kan testas genom att prenumerera på meddelandet som skickas av koden under test, vilket visas i följande kodexempel:

[TestMethod]
public void AddCatalogItemCommandSendsAddProductMessageTest()
{
    var messageReceived = false;
    var catalogService = new CatalogMockService();
    var catalogViewModel = new CatalogViewModel(catalogService);

    MessagingCenter.Subscribe<CatalogViewModel, CatalogItem>(
        this, MessageKeys.AddProduct, (sender, arg) =>
    {
        messageReceived = true;
    });
    catalogViewModel.AddCatalogItemCommand.Execute(null);

    Assert.IsTrue(messageReceived);
}

Det här enhetstestet kontrollerar att CatalogViewModel publicerar meddelandet AddProduct som svar på att AddCatalogItemCommand körs. Eftersom MessagingCenter klassen stöder multicast-meddelandeprenumerationer kan enhetstestet prenumerera på AddProduct meddelandet och köra ett återanrops-delegat som svar när det tas emot. Det här återanropsdelegatet, som anges som ett lambda-uttryck, ställer in ett booleskt fält som används av Assert-satsen för att verifiera testets beteende.

Testa undantagshantering

Enhetstester kan också skrivas som kontrollerar att specifika undantag utlöses för ogiltiga åtgärder eller indata, vilket visas i följande kodexempel:

[TestMethod]
public void InvalidEventNameShouldThrowArgumentExceptionText()
{
    var behavior = new MockEventToCommandBehavior
    {
        EventName = "OnItemTapped"
    };
    var listView = new ListView();

    Assert.Throws<ArgumentException>(() => listView.Behaviors.Add(behavior));
}

Det här enhetstestet utlöser ett undantag eftersom ListView kontrollen inte har någon händelse med namnet OnItemTapped. Metoden Assert.Throws<T> är en allmän metod där T är typen av det förväntade undantaget. Argumentet som skickas Assert.Throws<T> till metoden är ett lambda-uttryck som utlöser undantaget. Därför godkänns enhetstestet förutsatt att lambda-uttrycket genererar en ArgumentException.

Tips

Undvik att skriva enhetstester som undersöker undantagsmeddelandesträngar. Undantagsmeddelandesträngar kan ändras med tiden, så enhetstester som förlitar sig på deras närvaro betraktas som spröda.

Testa validering

Det finns två aspekter för att testa valideringsimplementeringen: testning av att valideringsregler implementeras korrekt och att ValidatableObject<T> klassen utför som förväntat.

Valideringslogik är vanligtvis enkel att testa eftersom det vanligtvis är en självständig process där utdata är beroende av indata. Det bör finnas tester på resultatet av att Validate anropa metoden på varje egenskap som har minst en associerad verifieringsregel, vilket visas i följande kodexempel:

[TestMethod]
public void CheckValidationPassesWhenBothPropertiesHaveDataTest()
{
    var mockViewModel = new MockViewModel();
    mockViewModel.Forename.Value = "John";
    mockViewModel.Surname.Value = "Smith";

    var isValid = mockViewModel.Validate();

    Assert.IsTrue(isValid);
}

Det här enhetstestet kontrollerar att valideringen lyckas när de två ValidatableObject<T> egenskaperna i instansen MockViewModel båda har data.

Förutom att kontrollera att valideringen lyckas bör valideringsenhetstester också kontrollera värdena Valueför egenskapen , IsValidoch Errors för varje ValidatableObject<T> instans, för att verifiera att klassen fungerar som förväntat. Följande kodexempel visar ett enhetstest som gör detta:

[TestMethod]
public void CheckValidationFailsWhenOnlyForenameHasDataTest()
{
    var mockViewModel = new MockViewModel();
    mockViewModel.Forename.Value = "John";

    bool isValid = mockViewModel.Validate();

    Assert.IsFalse(isValid);
    Assert.IsNotNull(mockViewModel.Forename.Value);
    Assert.IsNull(mockViewModel.Surname.Value);
    Assert.IsTrue(mockViewModel.Forename.IsValid);
    Assert.IsFalse(mockViewModel.Surname.IsValid);
    Assert.AreEqual(mockViewModel.Forename.Errors.Count(), 0);
    Assert.AreNotEqual(mockViewModel.Surname.Errors.Count(), 0);
}

Det här enhetstestet kontrollerar att valideringen misslyckas när egenskapen för Surname inte har några data, och egenskaperna MockViewModel, Value och IsValid för varje Errors-instans är korrekt inställda.

Sammanfattning

Ett enhetstest tar en liten del av appen, vanligtvis en metod, isolerar den från resten av koden och verifierar att den fungerar som förväntat. Målet är att kontrollera att varje funktionsenhet fungerar som förväntat, så att fel inte sprids i hela appen.

Beteendet för ett objekt som testas kan isoleras genom att ersätta beroende objekt med falska objekt som simulerar beteendet för de beroende objekten. Detta gör att enhetstester kan köras utan att det krävs otympliga resurser, till exempel plattformsfunktioner för körning, webbtjänster eller databaser

Testning av modeller och visningsmodeller från MVVM-program är identiskt med testning av andra klasser, och samma verktyg och tekniker kan användas.