Het testen van modules
Tip
Deze inhoud is een fragment uit het eBook, Enterprise Application Patterns Using .NET MAUI, beschikbaar op .NET Docs of als een gratis downloadbare PDF die offline kan worden gelezen.
Apps met meerdere platforms ondervinden problemen die vergelijkbaar zijn met desktoptoepassingen en webtoepassingen. Mobiele gebruikers verschillen per apparaat, netwerkverbinding, beschikbaarheid van services en verschillende andere factoren. Daarom moeten apps met meerdere platforms worden getest, omdat ze in de echte wereld worden gebruikt om hun kwaliteit, betrouwbaarheid en prestaties te verbeteren. Er moeten veel soorten tests worden uitgevoerd op een app, waaronder eenheidstests, integratietests en testen van gebruikersinterfaces. Eenheidstests zijn de meest voorkomende vorm en essentieel voor het bouwen van hoogwaardige toepassingen.
Een eenheidstest maakt gebruik van een kleine eenheid van de app, meestal een methode, isoleert deze van de rest van de code en controleert of deze zich gedraagt zoals verwacht. Het doel is om te controleren of elke functionaliteitseenheid naar verwachting presteert, zodat fouten niet worden doorgegeven in de hele app. Het detecteren van een fout waar deze zich voordoet, is efficiënter dan het effect van een fout indirect te observeren op een secundair foutpunt.
Het testen van eenheden heeft het belangrijkste effect op codekwaliteit wanneer het een integraal onderdeel is van de werkstroom voor softwareontwikkeling. Eenheidstests kunnen fungeren als ontwerpdocumentatie en functionele specificaties voor een toepassing. Zodra een methode is geschreven, moeten eenheidstests worden geschreven om het gedrag van de methode te verifiëren als reactie op standaard-, grens- en onjuiste invoergegevenscases en om expliciete of impliciete veronderstellingen van de code te controleren. Bij testgestuurde ontwikkeling worden moduletests ook geschreven vóór de code. Zie Walkthrough: Testgestuurde ontwikkeling met testverkenner voor meer informatie over testgestuurde ontwikkeling en hoe u deze implementeert..
Notitie
Eenheidstests zijn zeer effectief tegen regressie. Dat wil gezegd, functionaliteit die vroeger werkte, maar is gestoord door een defecte update.
Eenheidstests maken doorgaans gebruik van het patroon arrange-act-assert:
Stap | Beschrijving |
---|---|
Rangschikken | Initialiseert objecten en stelt de waarde in van de gegevens die worden doorgegeven aan de methode die wordt getest. |
Daad | Roept de methode onder test aan met de vereiste argumenten. |
Assert | Controleert of de actie van de methode onder test werkt zoals verwacht. |
Dit patroon zorgt ervoor dat eenheidstests leesbaar zijn, zelfbeschrijfbaar en consistent zijn.
Afhankelijkheidsinjectie en eenheidstests
Een van de redenen voor het aannemen van een losjes gekoppelde architectuur is dat het testen van eenheden vergemakkelijkt. Een van de typen die zijn geregistreerd bij de afhankelijkheidsinjectieservice is de IAppEnvironmentService
interface. In het volgende codevoorbeeld ziet u een overzicht van deze klasse:
public class OrderDetailViewModel : ViewModelBase
{
private IAppEnvironmentService _appEnvironmentService;
public OrderDetailViewModel(
IAppEnvironmentService appEnvironmentService,
IDialogService dialogService, INavigationService navigationService, ISettingsService settingsService)
: base(dialogService, navigationService, settingsService)
{
_appEnvironmentService = appEnvironmentService;
}
}
De OrderDetailViewModel
klasse heeft een afhankelijkheid van het IAppEnvironmentService
type, die de container voor afhankelijkheidsinjectie oplost wanneer er een OrderDetailViewModel
object wordt geïnstitueert. In plaats van een IAppEnvironmentService
object te maken dat gebruikmaakt van echte servers, apparaten en configuraties om de OrderDetailViewModel
klasse te testen, vervangt u het IAppEnvironmentService
object echter door een mock-object voor het doel van de tests. Een mock-object is een object met dezelfde handtekening van een object of interface, maar wordt op een specifieke manier gemaakt om te helpen bij het testen van eenheden. Het wordt vaak gebruikt met afhankelijkheidsinjectie om specifieke implementaties van interfaces te bieden voor het testen van verschillende gegevens- en werkstroomscenario's.
Met deze methode kan het IAppEnvironmentService
object tijdens runtime worden doorgegeven aan de OrderDetailViewModel
klasse en in het belang van testbaarheid kan een mockklasse tijdens de testtijd in de OrderDetailViewModel
klasse worden doorgegeven. Het belangrijkste voordeel van deze aanpak is dat eenheidstests kunnen worden uitgevoerd zonder onhandige resources zoals runtimeplatformfuncties, webservices of databases.
MVVM-toepassingen testen
Het testen van modellen en het weergeven van modellen uit MVVM-toepassingen is identiek aan het testen van andere klassen en maakt gebruik van dezelfde hulpprogramma's en technieken; dit omvat functies zoals eenheidstests en mocking. Sommige patronen die typisch zijn voor het modelleren en weergeven van modelklassen, kunnen echter profiteren van specifieke technieken voor eenheidstests.
Tip
Test één ding met elke eenheidstest. Naarmate de complexiteit van een test uitbreidt, wordt de verificatie van die test moeilijker. Door een eenheidstest te beperken tot één probleem, kunnen we ervoor zorgen dat onze tests meer herhaalbaar, geïsoleerd zijn en een kleinere uitvoeringstijd hebben. Zie best practices voor het testen van eenheden met .NET voor meer aanbevolen procedures.
Wees niet geneigd om een eenheidstestoefening meer dan één aspect van het gedrag van de eenheid te maken. Dit leidt tot tests die moeilijk te lezen en bij te werken zijn. Het kan ook leiden tot verwarring bij het interpreteren van een fout.
De eShop-app voor meerdere platforms maakt gebruik van MSTest om eenheidstests uit te voeren, die ondersteuning biedt voor twee verschillende typen eenheidstests:
Testtype | Kenmerk | Beschrijving |
---|---|---|
TestMethod | TestMethod |
Hiermee definieert u de daadwerkelijke testmethode die moet worden uitgevoerd. |
DataSource | DataSource |
Tests die alleen waar zijn voor een bepaalde set gegevens. |
De eenheidstests die zijn opgenomen in de app voor meerdere platforms van eShop zijn TestMethod, dus elke eenheidstestmethode is ingericht met het TestMethod
kenmerk. Naast MSTest zijn er verschillende andere testframeworks beschikbaar, waaronder NUnit en xUnit.
Asynchrone functionaliteit testen
Wanneer u het MVVM-patroon implementeert, worden bij het weergeven van modellen meestal bewerkingen op services aangeroepen, vaak asynchroon. Tests voor code die deze bewerkingen aanroept, gebruiken doorgaans mocks als vervanging voor de werkelijke services. In het volgende codevoorbeeld ziet u hoe u asynchrone functionaliteit test door een mockservice door te geven aan een weergavemodel:
[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);
}
Met deze eenheidstest wordt gecontroleerd of de Order
eigenschap van het OrderDetailViewModel
exemplaar een waarde heeft nadat de InitializeAsync
methode is aangeroepen. De InitializeAsync
methode wordt aangeroepen wanneer de bijbehorende weergave van het weergavemodel naartoe wordt genavigeerd. Zie Navigatie voor meer informatie over navigatie.
Wanneer het OrderDetailViewModel
exemplaar wordt gemaakt, wordt verwacht dat een IOrderService
exemplaar wordt opgegeven als een argument.
OrderService
De gegevens worden echter opgehaald uit een webservice. Daarom wordt een OrderMockService
instantie, een mockversie van de OrderService
klasse, opgegeven als het argument voor de OrderDetailViewModel
constructor. Vervolgens worden mockgegevens opgehaald in plaats van te communiceren met een webservice wanneer de methode van InitializeAsync
het weergavemodel wordt aangeroepen, die bewerkingen gebruikt IOrderService
.
INotifyPropertyChanged-implementaties testen
Door de INotifyPropertyChanged
interface te implementeren, kunnen weergaven reageren op wijzigingen die afkomstig zijn van weergavemodellen en modellen. Deze wijzigingen zijn niet beperkt tot gegevens die worden weergegeven in besturingselementen. Ze worden ook gebruikt om de weergave te beheren, zoals weergavemodelstatussen waardoor animaties worden gestart of besturingselementen worden uitgeschakeld.
Eigenschappen die rechtstreeks door de eenheidstest kunnen worden bijgewerkt, kunnen worden getest door een gebeurtenishandler aan de PropertyChanged
gebeurtenis te koppelen en te controleren of de gebeurtenis wordt gegenereerd nadat een nieuwe waarde voor de eigenschap is ingesteld. In het volgende codevoorbeeld ziet u een dergelijke 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);
}
Met deze eenheidstest wordt de InitializeAsync
methode van de OrderViewModel
klasse aangeroepen, waardoor Order
de eigenschap ervan wordt bijgewerkt. De eenheidstest wordt doorstaan, mits de PropertyChanged
gebeurtenis voor de Order
eigenschap wordt gegenereerd.
Communicatie op basis van berichten testen
Bekijk modellen die gebruikmaken van de MessagingCenter
klasse om te communiceren tussen losjes gekoppelde klassen, kunnen worden getest door u te abonneren op het bericht dat wordt verzonden door de code die wordt verzonden, zoals wordt weergegeven in het volgende codevoorbeeld:
[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);
}
Met deze eenheid wordt gecontroleerd of het CatalogViewModel
AddProduct
bericht wordt gepubliceerd als reactie op AddCatalogItemCommand
de uitvoering ervan. Omdat de MessagingCenter
klasse multicastberichtabonnementen ondersteunt, kan de eenheidstest zich abonneren op het AddProduct
bericht en een callback-gemachtigde uitvoeren als reactie op het ontvangen ervan. Deze callback-gemachtigde, opgegeven als lambda-expressie, stelt een Booleaanse veld in dat door de Assert
instructie wordt gebruikt om het gedrag van de test te verifiëren.
Afhandeling van uitzonderingen testen
Eenheidstests kunnen ook worden geschreven om te controleren of er specifieke uitzonderingen worden gegenereerd voor ongeldige acties of invoer, zoals wordt weergegeven in het volgende codevoorbeeld:
[TestMethod]
public void InvalidEventNameShouldThrowArgumentExceptionText()
{
var behavior = new MockEventToCommandBehavior
{
EventName = "OnItemTapped"
};
var listView = new ListView();
Assert.Throws<ArgumentException>(() => listView.Behaviors.Add(behavior));
}
Deze eenheidstest genereert een uitzondering omdat het besturingselement geen gebeurtenis met de ListView
naam OnItemTapped
heeft. De Assert.Throws<T>
methode is een algemene methode waarbij T
het type van de verwachte uitzondering is. Het argument dat aan de Assert.Throws<T>
methode wordt doorgegeven, is een lambda-expressie waarmee de uitzondering wordt gegenereerd. Daarom wordt de eenheidstest doorgegeven op voorwaarde dat de lambda-expressie een ArgumentException
.
Tip
Vermijd het schrijven van eenheidstests die uitzonderingsberichttekenreeksen onderzoeken. Uitzonderingsberichttekenreeksen kunnen na verloop van tijd veranderen en daarom worden eenheidstests die afhankelijk zijn van hun aanwezigheid beschouwd als broos.
Validatie testen
Er zijn twee aspecten voor het testen van de validatie-implementatie: testen of eventuele validatieregels correct zijn geïmplementeerd en testen die de ValidatableObject<T>
klasse uitvoert zoals verwacht.
Validatielogica is meestal eenvoudig te testen, omdat het meestal een zelfstandig proces is waarbij de uitvoer afhankelijk is van de invoer. Er moeten tests worden uitgevoerd op de resultaten van het aanroepen van de Validate
methode voor elke eigenschap met ten minste één bijbehorende validatieregel, zoals wordt weergegeven in het volgende codevoorbeeld:
[TestMethod]
public void CheckValidationPassesWhenBothPropertiesHaveDataTest()
{
var mockViewModel = new MockViewModel();
mockViewModel.Forename.Value = "John";
mockViewModel.Surname.Value = "Smith";
var isValid = mockViewModel.Validate();
Assert.IsTrue(isValid);
}
Met deze eenheidstest wordt gecontroleerd of de validatie slaagt wanneer de twee ValidatableObject<T>
eigenschappen in het MockViewModel
exemplaar beide gegevens bevatten.
Naast het controleren of de validatie slaagt, moeten tests van validatie-eenheden ook de waarden van de Value
, IsValid
en Errors
eigenschap van elk ValidatableObject<T>
exemplaar controleren om te controleren of de klasse naar verwachting presteert. In het volgende codevoorbeeld ziet u een eenheidstest die dit doet:
[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);
}
Met deze eenheidstest wordt gecontroleerd of de validatie mislukt wanneer de eigenschap van de Surname
MockViewModel
module geen gegevens bevat en de Value
IsValid
, en Errors
eigenschap van elk ValidatableObject<T>
exemplaar correct zijn ingesteld.
Samenvatting
Een eenheidstest maakt gebruik van een kleine eenheid van de app, meestal een methode, isoleert deze van de rest van de code en controleert of deze zich gedraagt zoals verwacht. Het doel is om te controleren of elke functionaliteitseenheid naar verwachting presteert, zodat fouten niet worden doorgegeven in de hele app.
Het gedrag van een object dat wordt getest, kan worden geïsoleerd door afhankelijke objecten te vervangen door gesimuleerde objecten die het gedrag van de afhankelijke objecten simuleren. Hierdoor kunnen eenheidstests worden uitgevoerd zonder dat er onhandige resources nodig zijn, zoals runtimeplatformfuncties, webservices of databases
Het testen van modellen en het weergeven van modellen uit MVVM-toepassingen is identiek aan het testen van andere klassen, en dezelfde hulpprogramma's en technieken kunnen worden gebruikt.