Zoeken…


De weergavemodellen testen

Voor we beginnen...

Qua applicatielagen is uw ViewModel een klasse met alle bedrijfslogica en regels die ervoor zorgen dat de app doet wat hij moet volgens de vereisten. Het is ook belangrijk om het zo veel mogelijk onafhankelijk te maken door het verminderen van verwijzingen naar UI, gegevenslaag, native functies en API-aanroepen enz. Al deze maakt uw VM testbaar.
Kortom, uw ViewModel:

  • Mag niet afhangen van UI-klassen (weergaven, pagina's, stijlen, evenementen);
  • Mag geen statische gegevens van andere klassen gebruiken (zoveel als je kunt);
  • Moet de bedrijfslogica implementeren en gegevens voorbereiden zoals zou moeten op UI;
  • Moeten andere componenten (database, HTTP, UI-specifiek) gebruiken via interfaces die worden opgelost met behulp van Dependency Injection.

Uw ViewModel kan ook eigenschappen van andere VM's-typen hebben. ContactsPageViewModel heeft bijvoorbeeld een verzameling van het type verzameling zoals ObservableCollection<ContactListItemViewModel>

Zakelijke vereisten

Laten we zeggen dat we de volgende functionaliteit moeten implementeren:

As an unauthorized user
I want to log into the app
So that I will access the authorized features

Na het gebruikersverhaal te hebben verduidelijkt, hebben we de volgende scenario's gedefinieerd:

Scenario: trying to log in with valid non-empty creds
  Given the user is on Login screen
   When the user enters 'user' as username
    And the user enters 'pass' as password
    And the user taps the Login button
   Then the app shows the loading indicator
    And the app makes an API call for authentication

Scenario: trying to log in empty username
  Given the user is on Login screen
   When the user enters '  ' as username
    And the user enters 'pass' as password
    And the user taps the Login button
   Then the app shows an error message saying 'Please, enter correct username and password'
    And the app doesn't make an API call for authentication

We zullen alleen bij deze twee scenario's blijven. Natuurlijk zouden er nog veel meer gevallen moeten zijn en je zou ze allemaal moeten definiëren voordat je daadwerkelijk codeert, maar het is voor ons nu voldoende om vertrouwd te raken met het testen van kijkmodellen.

Laten we de klassieke TDD-aanpak volgen en beginnen met het schrijven van een lege klasse die wordt getest. Vervolgens zullen we tests schrijven en ze groen maken door de zakelijke functionaliteit te implementeren.

Gemeenschappelijke klassen

public abstract class BaseViewModel : INotifyPropertyChanged
{
    public event PropertyChangedEventHandler PropertyChanged;

    protected virtual void OnPropertyChanged([CallerMemberName] string propertyName = null)
    {
        PropertyChanged?.Invoke(this, new PropertyChangedEventArgs(propertyName));
    }
}

Diensten

Weet je nog dat ons weergavemodel de UI- en HTTP-klassen niet rechtstreeks mag gebruiken? U moet ze in plaats daarvan definiëren als abstracties en niet afhankelijk zijn van implementatiedetails .

/// <summary>
/// Provides authentication functionality.
/// </summary>
public interface IAuthenticationService
{
    /// <summary>
    /// Tries to authenticate the user with the given credentials.
    /// </summary>
    /// <param name="userName">UserName</param>
    /// <param name="password">User's password</param>
    /// <returns>true if the user has been successfully authenticated</returns>
    Task<bool> Login(string userName, string password);
}

/// <summary>
/// UI-specific service providing abilities to show alert messages.
/// </summary>
public interface IAlertService
{
    /// <summary>
    /// Show an alert message to the user.
    /// </summary>
    /// <param name="title">Alert message title</param>
    /// <param name="message">Alert message text</param>
    Task ShowAlert(string title, string message);
}

De ViewModel-stub bouwen

Oké, we hebben de paginaklasse voor het inlogscherm, maar laten we beginnen met ViewModel:

public class LoginPageViewModel : BaseViewModel
{
    private readonly IAuthenticationService authenticationService;
    private readonly IAlertService alertService;

    private string userName;
    private string password;
    private bool isLoading;

    private ICommand loginCommand;

    public LoginPageViewModel(IAuthenticationService authenticationService, IAlertService alertService)
    {
        this.authenticationService = authenticationService;
        this.alertService = alertService;
    }

    public string UserName
    {
        get
        {
            return userName;
        }
        set
        {
            if (userName!= value)
            {
                userName= value;
                OnPropertyChanged();
            }
        }
    }
    
    public string Password
    {
        get
        {
            return password;
        }
        set
        {
            if (password != value)
            {
                password = value;
                OnPropertyChanged();
            }
        }
    }

    public bool IsLoading
    {
        get
        {
            return isLoading;
        }
        set
        {
            if (isLoading != value)
            {
                isLoading = value;
                OnPropertyChanged();
            }
        }
    }

    public ICommand LoginCommand => loginCommand ?? (loginCommand = new Command(Login));

    private void Login()
    {
        authenticationService.Login(UserName, Password);
    }
}

We hebben twee string gedefinieerd en een opdracht die moet worden gebonden aan de gebruikersinterface. We zullen in dit onderwerp niet beschrijven hoe u een paginaklasse, XAML-opmaak kunt maken en ViewModel hieraan kunt binden, omdat ze niets specifieks hebben.

Hoe maak je een LoginPageViewModel-instantie?

Ik denk dat je de VM's waarschijnlijk alleen met constructor maakte. Zoals u kunt zien, is onze VM afhankelijk van 2 services die als constructorparameters worden geïnjecteerd, dus kan niet alleen var viewModel = new LoginPageViewModel() . Als u niet bekend bent met Dependency Injection , is dit het beste moment om er meer over te weten te komen. Juiste unit-testen is onmogelijk zonder dit principe te kennen en te volgen.

Tests

Laten we nu enkele tests schrijven op basis van de hierboven genoemde gebruikssituaties. Allereerst moet u een nieuwe assembly maken (alleen een klassenbibliotheek of een speciaal testproject selecteren als u testtools van Microsoft wilt gebruiken). Noem het zoiets als ProjectName.Tests en voeg een referentie toe aan uw oorspronkelijke PCL-project.

In dit voorbeeld ga ik NUnit en Moq gebruiken, maar je kunt doorgaan met het testen van bibliotheken naar keuze. Er zal niets speciaals met hen zijn.

Ok, dat is de testklasse:

[TestFixture]
public class LoginPageViewModelTest
{
}

Schrijftests

Hier zijn de testmethoden voor de eerste twee scenario's. Probeer 1 testmethode per 1 verwacht resultaat te houden en niet alles in één test te controleren. Dat zal je helpen om duidelijkere rapporten te ontvangen over wat er in de code is mislukt.

[TestFixture]
public class LoginPageViewModelTest
{
    private readonly Mock<IAuthenticationService> authenticationServiceMock =
        new Mock<IAuthenticationService>();
    private readonly Mock<IAlertService> alertServiceMock =
        new Mock<IAlertService>();
    
    [TestCase("user", "pass")]
    public void LogInWithValidCreds_LoadingIndicatorShown(string userName, string password)
    {
        LoginPageViewModel model = CreateViewModelAndLogin(userName, password);

        Assert.IsTrue(model.IsLoading);
    }

    [TestCase("user", "pass")]
    public void LogInWithValidCreds_AuthenticationRequested(string userName, string password)
    {
        CreateViewModelAndLogin(userName, password);

        authenticationServiceMock.Verify(x => x.Login(userName, password), Times.Once);
    }

    [TestCase("", "pass")]
    [TestCase("   ", "pass")]
    [TestCase(null, "pass")]
    public void LogInWithEmptyuserName_AuthenticationNotRequested(string userName, string password)
    {
        CreateViewModelAndLogin(userName, password);

        authenticationServiceMock.Verify(x => x.Login(It.IsAny<string>(), It.IsAny<string>()), Times.Never);
    }

    [TestCase("", "pass", "Please, enter correct username and password")]
    [TestCase("   ", "pass", "Please, enter correct username and password")]
    [TestCase(null, "pass", "Please, enter correct username and password")]
    public void LogInWithEmptyUserName_AlertMessageShown(string userName, string password, string message)
    {
        CreateViewModelAndLogin(userName, password);

        alertServiceMock.Verify(x => x.ShowAlert(It.IsAny<string>(), message));
    }

    private LoginPageViewModel CreateViewModelAndLogin(string userName, string password)
    {
        var model = new LoginPageViewModel(
            authenticationServiceMock.Object,
            alertServiceMock.Object);

        model.UserName = userName;
        model.Password = password;

        model.LoginCommand.Execute(null);

        return model;
    }
}

En hier gaan we:

voer hier de afbeeldingsbeschrijving in

Nu het doel is om de juiste implementatie voor ViewModel te schrijven Login methode en dat is het.

Implementatie van bedrijfslogica

private async void Login()
{
    if (String.IsNullOrWhiteSpace(UserName) || String.IsNullOrWhiteSpace(Password))
    {
        await alertService.ShowAlert("Warning", "Please, enter correct username and password");
    }
    else
    {
        IsLoading = true;
        bool isAuthenticated = await authenticationService.Login(UserName, Password);
    }
}

En na het opnieuw uitvoeren van de tests:

voer hier de afbeeldingsbeschrijving in

Nu kunt u uw code blijven afdekken met nieuwe tests, waardoor deze stabieler en regressieveiliger wordt.



Modified text is an extract of the original Stack Overflow Documentation
Licentie onder CC BY-SA 3.0
Niet aangesloten bij Stack Overflow