Pisanie testów interfejsu użytkownika

Ukończone

W tej sekcji pomożesz Andy'owi i Amita napisać testy Selenium, które weryfikują zachowania interfejsu użytkownika opisane przez Amitę.

Amita zwykle uruchamia testy w przeglądarce Chrome, Firefox i Microsoft Edge. W tym miejscu robisz to samo. Agent hostowany przez firmę Microsoft jest wstępnie skonfigurowany do pracy z każdą z tych przeglądarek.

Pobieranie gałęzi z repozytorium GitHub

W tej sekcji pobierzesz selenium gałąź z usługi GitHub. Następnie wyewidencjonujesz lub przełączysz się do tej gałęzi. Zawartość gałęzi pomoże Ci wykonać testy, które Andy i Amita napiszą.

Ta gałąź zawiera projekt Space Game , z którym pracowaliśmy w poprzednich modułach. Zawiera również konfigurację usługi Azure Pipelines, od których należy zacząć.

  1. W programie Visual Studio Code otwórz zintegrowany terminal.

  2. Aby pobrać gałąź o nazwie selenium z repozytorium firmy Microsoft, przejdź do tej gałęzi i uruchom następujące git fetch polecenia i git checkout :

    git fetch upstream selenium
    git checkout -B selenium upstream/selenium
    

    Napiwek

    Jeśli wykonano czynności opisane w poprzedniej lekcji wraz z ręcznym testem Amity, być może te polecenia zostały już uruchomione. Jeśli uruchomiono je już w poprzedniej lekcji, możesz uruchomić je ponownie teraz.

    Pamiętaj, że nadrzędny element odnosi się do repozytorium Microsoft GitHub. Konfiguracja usługi Git projektu rozumie nadrzędną relację zdalną, ponieważ skonfigurowana jest ta relacja. Projekt został skonfigurowany podczas rozwidlenia projektu z repozytorium Firmy Microsoft i klonowania go lokalnie.

    Wkrótce wypchniesz tę gałąź do repozytorium GitHub o nazwie origin.

  3. Opcjonalnie w programie Visual Studio Code otwórz plik azure-pipelines.yml . Zapoznaj się z początkową konfiguracją.

    Konfiguracja przypomina te, które zostały utworzone w poprzednich modułach w tej ścieżce szkoleniowej. Tworzona jest tylko konfiguracja wydania aplikacji. W celu zapewnienia zwięzłości pomija również wyzwalacze, zatwierdzenia ręczne i testy skonfigurowane w poprzednich modułach.

    Uwaga

    Bardziej niezawodna konfiguracja może określać gałęzie, które uczestniczą w procesie kompilacji. Na przykład w celu sprawdzenia jakości kodu można uruchamiać testy jednostkowe za każdym razem, gdy wypchniesz zmianę w dowolnej gałęzi. Możesz również wdrożyć aplikację w środowisku, które wykonuje bardziej wyczerpujące testowanie. Jednak to wdrożenie jest możliwe tylko wtedy, gdy masz żądanie ściągnięcia, gdy masz kandydata do wydania lub gdy scalasz kod z główną wersją.

    Aby uzyskać więcej informacji, zobacz Implementowanie przepływu pracy kodu w potoku kompilacji przy użyciu wyzwalaczy git i GitHub i potoku kompilacji.

Pisanie kodu testu jednostkowego

Amita jest podekscytowany, aby nauczyć się pisać kod, który kontroluje przeglądarkę internetową.

Ona i Andy będą współpracować, aby napisać testy Selenium. Andy skonfigurował już pusty projekt NUnit. W całym procesie odwołują się one do dokumentacji Selenium, kilku samouczków online i notatek, które wykonali, gdy Amita wykonała testy ręcznie. Na końcu tego modułu znajdziesz więcej zasobów, które pomogą Ci przejść przez ten proces.

Przejrzyjmy proces, którego Andy i Amita używają do pisania testów. Następnie możesz otworzyć HomePageTest.cs w katalogu Tailspin.SpaceGame.Web.UITests w programie Visual Studio Code.

Definiowanie klasy HomePageTest

Andy: Pierwszą rzeczą, którą musimy zrobić, jest zdefiniowanie naszej klasy testowej. Możemy wybrać jedną z kilku konwencji nazewnictwa. Wywołajmy naszą klasę HomePageTest. W tej klasie umieścimy wszystkie nasze testy odnoszące się do strony głównej.

Andy dodaje ten kod do HomePageTest.cs:

public class HomePageTest
{
}

Andy: Musimy oznaczyć tę klasę jako public dostępną dla struktury NUnit.

Dodawanie zmiennej składowej IWebDriver

Andy: Następnie potrzebujemy zmiennej składowej IWebDriver . IWebDriver to interfejs programowania używany do uruchamiania przeglądarki internetowej i interakcji z zawartością strony internetowej.

Amita: Słyszałem o interfejsach w programowaniu. Czy możesz mi powiedzieć więcej?

Andy: Pomyśl o interfejsie jako specyfikacji lub strategii, aby dowiedzieć się, jak składnik powinien zachowywać się. Interfejs udostępnia metody lub zachowania tego składnika. Interfejs nie udostępnia jednak żadnych podstawowych szczegółów. Ty lub ktoś inny utworzy jedną lub więcej konkretnych klas , które implementują ten interfejs. Selenium udostępnia klasy betonowe, których potrzebujemy.

Na tym diagramie przedstawiono IWebDriver interfejs i kilka klas, które implementują ten interfejs:

Diagram interfejsu IWebDriver, jego metod i konkretnych klas.

Na diagramie przedstawiono trzy metody, które udostępniają IWebDriver : Navigate, FindElementi Close.

Trzy klasy pokazane tutaj, ChromeDriver, FirefoxDriveri EdgeDriver, każdy implementuje IWebDriver i jego metody. Istnieją inne klasy, takie jak SafariDriver, które również implementują IWebDriver. Każda klasa sterownika może kontrolować przeglądarkę internetową, którą reprezentuje.

Andy dodaje zmienną składową o nazwie driver do HomePageTest klasy, podobnie jak w tym kodzie:

public class HomePageTest
{
    private IWebDriver driver;
}

Definiowanie urządzeń testowych

Andy: Chcemy uruchomić cały zestaw testów w przeglądarce Chrome, Firefox i Edge. W NUnit możemy użyć urządzeń testowych , aby uruchomić cały zestaw testów wiele razy, po jednym raz dla każdej przeglądarki, na której chcemy przetestować.

W NUnit użyj atrybutu TestFixture , aby zdefiniować urządzenia testowe. Andy dodaje te trzy urządzenia testowe do HomePageTest klasy:

[TestFixture("Chrome")]
[TestFixture("Firefox")]
[TestFixture("Edge")]
public class HomePageTest
{
    private IWebDriver driver;
}

Andy: Następnie musimy zdefiniować konstruktor dla naszej klasy testowej. Konstruktor jest wywoływany, gdy NUnit tworzy wystąpienie tej klasy. Jako argument konstruktor przyjmuje ciąg dołączony do naszych urządzeń testowych. Oto jak wygląda kod:

[TestFixture("Chrome")]
[TestFixture("Firefox")]
[TestFixture("Edge")]
public class HomePageTest
{
    private string browser;
    private IWebDriver driver;

    public HomePageTest(string browser)
    {
        this.browser = browser;
    }
}

Andy: Dodaliśmy zmienną browser składową, abyśmy mogli użyć bieżącej nazwy przeglądarki w kodzie konfiguracji. Teraz napiszmy kod konfiguracji.

Definiowanie metody Setup

Andy: Następnie musimy przypisać naszą IWebDriver zmienną składową do wystąpienia klasy, które implementuje ten interfejs dla przeglądarki, na którą testujemy. Klasy ChromeDriver, FirefoxDriveri EdgeDriver implementują ten interfejs odpowiednio dla przeglądarek Chrome, Firefox i Edge.

Utwórzmy metodę o nazwie Setup , która ustawia zmienną driver . Używamy atrybutu OneTimeSetUp , aby poinformować NUnit, aby uruchomić tę metodę jednorazowo na urządzenie testowe.

[OneTimeSetUp]
public void Setup()
{
}

W metodzie Setup możemy użyć instrukcji , aby przypisać driver zmienną switch składową do odpowiedniej konkretnej implementacji na podstawie nazwy przeglądarki. Teraz dodajmy ten kod.

// Create the driver for the current browser.
switch(browser)
{
    case "Chrome":
    driver = new ChromeDriver(
        Environment.GetEnvironmentVariable("ChromeWebDriver")
    );
    break;
    case "Firefox":
    driver = new FirefoxDriver(
        Environment.GetEnvironmentVariable("GeckoWebDriver")
    );
    break;
    case "Edge":
    driver = new EdgeDriver(
        Environment.GetEnvironmentVariable("EdgeWebDriver"),
        new EdgeOptions
        {
            UseChromium = true
        }
    );
    break;
    default:
    throw new ArgumentException($"'{browser}': Unknown browser");
}

Konstruktor dla każdej klasy sterowników przyjmuje opcjonalną ścieżkę do oprogramowania sterownika Selenium musi kontrolować przeglądarkę internetową. Później omówimy rolę zmiennych środowiskowych pokazanych tutaj.

W tym przykładzie EdgeDriver konstruktor wymaga również dodatkowych opcji, aby określić, że chcemy użyć wersji Chromium przeglądarki Edge.

Definiowanie metod pomocnika

Andy: Wiem, że będziemy musieli powtórzyć dwie akcje w ramach testów:

  • Znajdowanie elementów na stronie, takich jak linki, które klikniemy, i modalne okna, które spodziewamy się wyświetlić
  • Kliknięcie elementów na stronie, takich jak linki, które ujawniają modalne okna i przycisk zamykający poszczególne modalne okna

Napiszmy dwie metody pomocnicze: jedną dla każdej akcji. Zaczniemy od metody, która znajduje element na stronie.

Pisanie metody pomocnika FindElement

Po zlokalizowaniu elementu na stronie zazwyczaj jest on odpowiedzią na inne zdarzenie, takie jak ładowanie strony lub wprowadzanie informacji przez użytkownika. Selenium udostępnia klasę WebDriverWait , która pozwala czekać na warunek, aby był prawdziwy. Jeśli warunek nie jest spełniony w danym przedziale czasu, WebDriverWait zgłasza wyjątek lub błąd. Możemy użyć WebDriverWait klasy , aby poczekać na wyświetlenie danego elementu i przygotować się do odbierania danych wejściowych użytkownika.

Aby zlokalizować element na stronie, użyj By klasy . Klasa By udostępnia metody, które umożliwiają znalezienie elementu według jego nazwy, nazwy klasy CSS, tagu HTML lub w naszym przypadku za pomocą atrybutu id .

Andy i Amita kodują metodę FindElement pomocnika. Wygląda na to, że ten kod:

private IWebElement FindElement(By locator, IWebElement parent = null, int timeoutSeconds = 10)
{
    // WebDriverWait enables us to wait for the specified condition to be true
    // within a given time period.
    return new WebDriverWait(driver, TimeSpan.FromSeconds(timeoutSeconds))
        .Until(c => {
            IWebElement element = null;
            // If a parent was provided, find its child element.
            if (parent != null)
            {
                element = parent.FindElement(locator);
            }
            // Otherwise, locate the element from the root of the DOM.
            else
            {
                element = driver.FindElement(locator);
            }
            // Return true after the element is displayed and is able to receive user input.
            return (element != null && element.Displayed && element.Enabled) ? element : null;
        });
}

Pisanie metody pomocnika ClickElement

Andy: Następnie napiszmy metodę pomocnika, która klika linki. Selenium udostępnia kilka sposobów na napisanie tej metody. Jednym z nich jest IJavaScriptExecutor interfejs. Za jego pomocą możemy programowo klikać linki przy użyciu języka JavaScript. Takie podejście działa dobrze, ponieważ może klikać łącza bez uprzedniego przewijania ich do widoku.

ChromeDriver, FirefoxDriveri EdgeDriver każdy implementuje IJavaScriptExecutor. Musimy rzutować sterownik do tego interfejsu, a następnie wywołać metodę ExecuteScript JavaScript click() w bazowym obiekcie HTML.

Andy i Amita kodują metodę ClickElement pomocnika. Wygląda na to, że ten kod:

private void ClickElement(IWebElement element)
{
    // We expect the driver to implement IJavaScriptExecutor.
    // IJavaScriptExecutor enables us to execute JavaScript code during the tests.
    IJavaScriptExecutor js = driver as IJavaScriptExecutor;

    // Through JavaScript, run the click() method on the underlying HTML object.
    js.ExecuteScript("arguments[0].click();", element);
}

Amita: Lubię pomysł dodawania tych metod pomocnika. Wydają się one wystarczająco ogólne, aby używać w prawie każdym teście. Możemy dodać więcej metod pomocnika później, ponieważ ich potrzebujemy.

Definiowanie metody testowej

Andy: Teraz możemy zdefiniować metodę testową. Na podstawie testów ręcznych, które uruchomiliśmy wcześniej, wywołajmy tę metodę ClickLinkById_ShouldDisplayModalById. Dobrym rozwiązaniem jest nadanie metodom testowym nazw opisowych, które definiują dokładnie to, co wykonuje test. W tym miejscu chcemy kliknąć link zdefiniowany przez jego id atrybut. Następnie chcemy sprawdzić, czy zostanie wyświetlone odpowiednie okno modalne, również przy użyciu jego id atrybutu.

Andy dodaje kod początkowy dla metody testowej:

public void ClickLinkById_ShouldDisplayModalById(string linkId, string modalId)
{
}

Andy: Zanim dodamy więcej kodu, zdefiniujmy, co powinien zrobić ten test.

Amita: Mogę sobie z tym poradzić. Chcemy:

  1. Znajdź link według jego id atrybutu, a następnie kliknij link.
  2. Znajdź wynikowy modalny.
  3. Zamknij modalne.
  4. Sprawdź, czy modalny element został wyświetlony pomyślnie.

Andy: Wielki. Będziemy również musieli poradzić sobie z kilkoma innymi rzeczami. Na przykład musimy zignorować test, jeśli nie można załadować sterownika i musimy zamknąć modalne tylko wtedy, gdy modalny był wyświetlany pomyślnie.

Po wypełnieniu kubków do kawy, Andy i Amita dodaj kod do swojej metody testowej. Używają metod pomocnika, które napisali, aby zlokalizować elementy strony i klikać linki i przyciski. Oto wynik:

public void ClickLinkById_ShouldDisplayModalById(string linkId, string modalId)
{
    // Skip the test if the driver could not be loaded.
    // This happens when the underlying browser is not installed.
    if (driver == null)
    {
        Assert.Ignore();
        return;
    }

    // Locate the link by its ID and then click the link.
    ClickElement(FindElement(By.Id(linkId)));

    // Locate the resulting modal.
    IWebElement modal = FindElement(By.Id(modalId));

    // Record whether the modal was successfully displayed.
    bool modalWasDisplayed = (modal != null && modal.Displayed);

    // Close the modal if it was displayed.
    if (modalWasDisplayed)
    {
        // Click the close button that's part of the modal.
        ClickElement(FindElement(By.ClassName("close"), modal));

        // Wait for the modal to close and for the main page to again be clickable.
        FindElement(By.TagName("body"));
    }

    // Assert that the modal was displayed successfully.
    // If it wasn't, this test will be recorded as failed.
    Assert.That(modalWasDisplayed, Is.True);
}

Amita: Kodowanie wygląda świetnie do tej pory. Ale jak połączyć ten test z id atrybutami, które zebraliśmy wcześniej?

Andy: Wielkie pytanie. Zajmiemy się tym dalej.

Definiowanie danych przypadku testowego

Andy: W NUnit możesz dostarczyć dane do testów na kilka sposobów. W tym miejscu użyjemy atrybutu TestCase . Ten atrybut przyjmuje argumenty, które później przekazuje z powrotem do metody testowej po uruchomieniu. Możemy mieć wiele TestCase atrybutów, które każdy testuje inną funkcję naszej aplikacji. Każdy TestCase atrybut tworzy przypadek testowy uwzględniony w raporcie, który pojawia się na końcu uruchomienia potoku.

Andy dodaje te TestCase atrybuty do metody testowej. Te atrybuty opisują przycisk Pobierz grę , jeden z ekranów gry i najlepszego gracza w rankingu. Każdy atrybut określa dwa id atrybuty: jeden dla linku do kliknięcia i jeden dla odpowiedniego okna modalnego.

// Download game
[TestCase("download-btn", "pretend-modal")]
// Screen image
[TestCase("screen-01", "screen-modal")]
// // Top player on the leaderboard
[TestCase("profile-1", "profile-modal-1")]
public void ClickLinkById_ShouldDisplayModalById(string linkId, string modalId)
{

...

Andy: Dla każdego TestCase atrybutu pierwszy parametr jest atrybutem id linku do kliknięcia. Drugi parametr to id atrybut okna modalnego, którego spodziewamy się wyświetlić. W naszej metodzie testowej można zobaczyć, jak te parametry odpowiadają argumentom dwuciągowym.

Amita: Widzę to. Z pewną praktyką, myślę, że mogę dodać własne testy. Kiedy można zobaczyć te testy uruchomione w naszym potoku?

Andy: Zanim wypchniemy zmiany w potoku, najpierw zweryfikujmy, czy kod jest kompilowany i uruchamiany lokalnie. Zatwierdźmy i wypchniemy zmiany do usługi GitHub i zobaczymy, jak przechodzą przez potok dopiero po sprawdzeniu, czy wszystko działa. Teraz uruchomimy testy lokalnie.