Jak napisać łatwe do utrzymania, a nie kruche testy jednostkowe dla GUI?

16

Próbowałem pisać testy jednostkowe interfejsu użytkownika dla moich aplikacji GUI i mam do czynienia z problemem, że chociaż działają one dobrze, gdy je początkowo piszę, okazują się kruche i pękają przy każdej zmianie projektu (czyli dość często). Próbuję znaleźć zestaw wskazówek, które doprowadziłyby mnie do posiadania możliwych do utrzymania testów jednostkowych dla GUI.

Na razie jedną rzeczą, którą odkryłem, jest to, że testy z informacją „ten składnik powinien gdzieś pokazywać swoje dane wejściowe” są dobre (i to bardzo łatwe w przypadku HTML). Testy sprawdzające określony stan określonej części komponentu są zwykle kruche. Testy typu kliknięcie-kliknięcie-kliknięcie-oczekiwanie, które próbują śledzić zachowanie użytkownika i leżącą u jego podstaw logikę biznesową (która jest najważniejszą częścią), zwykle okazują się kruche. Jak napisać dobre testy?


Mówiąc ściślej, chciałbym poznać pewne wzorce dotyczące tego, co mogę przetestować w moim interfejsie użytkownika, a nie dokładnie, jak to przetestować. Konwencje nazewnictwa i stałe identyfikatory są dobre, ale nie rozwiązują podstawowego problemu, jakim jest to, że GUI bardzo się zmieniają. Chciałbym przetestować zachowania, które najprawdopodobniej się nie zmienią. Jak znaleźć właściwą rzecz do przetestowania?

mik01aj
źródło
1
@MichaelDurrant: Zredagowałeś pytanie, aby ogólnie dotyczyło testowania interfejsu użytkownika, a ja pierwotnie zapytałem o testy jednostkowe. Testy integracyjne są trudniejsze do utrzymania i wolę testy jednostkowe od nich, gdy tylko jest to możliwe.
mik01aj
2
Myślę, że jest to część problemu, zasadniczo nie można tak naprawdę przetestować żadnego interfejsu za pomocą testów jednostkowych, ponieważ ich racją bytu jest połączenie się z czymś. GUI nie różnią się pod tym względem.
jk.
m01, możesz to zmienić z powrotem. Myślę, że testy interfejsu użytkownika są zwykle testami zintegrowanymi. Testy zwykle polegają na obecnych danych na temat nasion i urządzeń oraz na interfejsie użytkownika z nimi współpracującym. Jeśli masz prawdziwe testy interfejsu użytkownika, które nie opierają się na żadnych innych danych, które są doskonałe. Jednak stwierdziłem, że jest to stosunkowo rzadkie.
Michael Durrant,
2
powiązane, ale nie duplikat, ponieważ dotyczy to testów GUI: programmers.stackexchange.com/questions/109703/…
k3b

Odpowiedzi:

3

Typowy problem z testami GUI ... Głównym powodem, dla którego testy te są uważane za kruche, jest to, że nie mogą one przetrwać zmiany GUI, która nie jest zmianą wymagań . Powinieneś dążyć do takiej struktury kodu testowego, aby zmiana w GUI była izolowana w jednym miejscu w testach.

Jako przykład rozważ test o treści:

Gdy użytkownik wpisze „999” w pole numeru telefonu i kliknie przycisk Zapisz, wyskakujące okienko komunikatu o błędzie powinno powiedzieć „nieprawidłowy numer telefonu”.

Tutaj jest dużo miejsca na przerwanie tego testu po przerobieniu interfejsu, nawet jeśli wymagania dotyczące sprawdzania poprawności pozostaną.

Dodajmy teraz trochę alternatywnego sformułowania:

Gdy użytkownik wprowadza „999” jako numer telefonu i zapisuje stronę profilu, powinien pokazywać błąd z informacją „nieprawidłowy numer telefonu”

Test jest taki sam, wymagania są takie same, ale ten rodzaj testu przetrwa przeróbkę interfejsu użytkownika. Oczywiście będziesz musiał zmienić kod, ale kod zostanie odizolowany. Nawet jeśli masz dziesięć lub dwadzieścia takich testów dla swojej strony profilu i przenosisz logikę sprawdzania poprawności wyświetlającą błędy z alertów javascript do wyskakujących okienek, na przykład wystarczy zmienić tylko jedną część testową, która sprawdza komunikaty o błędach.

JDT
źródło
4

To jest powszechny problem. Chciałbym zwrócić uwagę na:

  • Jak nazywasz elementy

    Użyj identyfikatora css lub klasy, aby zidentyfikować elementy. Preferuj używanie identyfikatora CSS, gdy obiekt jest unikalny. Zastanów się, jakiej ramy używasz, na przykład w Ruby on Rails nameatrybut ten jest przypisywany automatycznie i może (nie intuicyjnie) być lepszy niż użycie css id lub klasy

  • Jak rozpoznajesz elementy.

    Unikaj identyfikatorów pozycyjnych, takich jak table/tr/td/tdna przykład formy td[id="main_vehicle"lub td[class='alternates']. W razie potrzeby rozważ użycie atrybutów danych. Jeszcze lepiej staraj się unikać znaczników układu, takich jak w <td>ogóle, więc w powyższym przypadku możesz dodać zakres i użyć go, np. <span id="main_vehicle">Lub selektora symboli wieloznacznych, takiego jak *[id="main_vehicle"]gdzie *może być teraz div, span, td itp.

  • Korzystanie z atrybutów danych specyficznych dla testu które są używane tylko do qa i testowania.

  • Unikaj niepotrzebnych kwalifikacji do elementów. Możesz znaleźć się przy użyciu następujących czynności:

    body.main div#vehicles > form#vehicle input#primary_vehicle_name

    Wymaga to jednak, aby pole wejściowe pozostało w formie z dokładnym identyfikatorem pojazdu i na stronie z treścią, która ma klasę main i div z identyfikatorem pojazdów, które mają bezpośrednie potomek formularza o identyfikatorze pojazd. Wszelkie zmiany w dowolnej z tych struktur i test są przerywane. W takim przypadku możesz to znaleźć

    input#primary_vehicle_name

    wystarczy, aby jednoznacznie zidentyfikować element.

  • Unikaj testów, które odnoszą się do widocznego tekstu. Tekst na stronie wyświetlany użytkownikowi zwykle zmienia się w czasie, gdy witryna jest utrzymywana i aktualizowana, więc używaj identyfikatorów, takich jak identyfikator css i klasa css lub atrybuty danych. Elementy takie jak form, inputi selectstosowane w formach są również dobre części elementów identyfikujących, zwykle w połączeniu z identyfikatorem lub klasy, np li.vehiclealbo input#first-vehicle Można również dodawać własne identyfikatory, np<div data-vehicle='dodge'> . W ten sposób można uniknąć używania identyfikatorów elementów lub klas, które mogą zostać zmienione przez programistów i projektantów. Z czasem odkryłem, że lepiej jest po prostu współpracować z programistami i projektantami i dojść do porozumienia co do nazw i zakresów. To jest trudne.

  • Jak utrzymywane są stałe dane.

    Podobnie jak w przypadku identyfikowania rzeczywistych elementów, staraj się unikać wbudowanych na stałe selektorów identyfikujących wartości na korzyść obiektów strony - małych fragmentów tekstu, które są przechowywane w zmiennych lub metodach, a zatem mogą być ponownie użyte, a także przechowywane centralnie. Przykłady zmiennych javascript zgodnie z tym wzorem dla wartości zakodowanych na stałe:

    storedVars["eqv_auto_year"] = "2015";
    storedVars["eqv_auto_make_1"] = "ALFA ROMEO";
    storedVars["eqv_auto_make_2"] = "HONDA";`  
    

    Więcej na temat obiektów stron na selenium wiki i dokumentach selenium

  • Komunikacja z programistami.

    Niezależnie od technicznego podejścia w zakresie „programiści wprowadzają zmiany i łamią automatyzację kontroli jakości”, to jest problem z przepływem pracy. Musisz upewnić się, że: każdy jest jednym zespołem; programista przeprowadza te same zintegrowane testy; standardy są uzgadniane i przestrzegane przez obie grupy; definicja gotowego obejmuje uruchomienie i ewentualnie aktualizację testów interfejsu użytkownika; programiści i testerzy łączą plany testowe i oboje uczestniczą w pielęgnacji biletów (jeśli wykonują Agile) i rozmawiają o testowaniu interfejsu użytkownika w ramach pielęgnacji. Powinieneś upewnić się, że wszelkie podejście i strategia używane do nazewnictwa są skoordynowane z twórcami aplikacji. Jeśli nie znajdziesz się na tej samej stronie, polubisz nazewnictwo obiektów. Kilka przykładów metod obiektów strony, które niedawno stworzyłem dla projektu ruby:

    def css_email_opt_in_true
      'auto_policy[email_opt_in][value=1]'
    end 
    
    def css_phone_opt_in
      '*[name="auto_policy[phone_opt_in]"]'
    end 
    
    def css_phone_opt_in_true
      'input[name=phone_opt_in][value=true]'
    end 
    
    def css_credit_rating
      'auto_policy[credit_rating]'
    end
    

    Oto te same obiekty stron, co zmienne javascript:

    storedVars["css_email_opt_in"] = "css=*[name='auto_policy[email_opt_in]']";
    storedVars["css_phone_opt_in"]="css=*[name='auto_policy[phone_opt_in]']";
    storedVars["css_phone_opt_in_true"]="css=input[name='phone_opt_in'][value=true]";
    storedVars["css_credit_rating"]="css=select[name='auto_policy[credit_rating]']";
    
Michael Durrant
źródło
2
To są użyteczne wskazówki i właściwie już większość z nich przestrzegam. Mój problem polegał na tym, że piszę testy dla jakiegoś przycisku, a następnie ten przycisk jest usuwany, gdy ta sama akcja jest obsługiwana z innego miejsca. Lub przycisk pozostaje tam, ale etykieta się zmienia, a przycisk uruchamia także dodatkowe czynności.
mik01aj
1
Ahh, dobrze wiedzieć. Tak, jeśli chodzi o te rzeczy, skoncentrowałbym się na przepływie pracy i interakcji qa-programistów
Michael Durrant,
1
Publikuję również informacje dla innych osób, które znajdą Twoje pytanie i mogą nie mieć na sobie wszystkich elementów, które masz lub nawet o nich nie wiedzieć.
Michael Durrant,
1
Rozszerzyłem swój ostatni punkt kuli na podstawie waszych opinii.
Michael Durrant
1
Zaktualizowałem części dotyczące nazywania i identyfikacji elementów oraz o nieużywaniu widocznego tekstu.
Michael Durrant
3

Powodem, dla którego ludzie opracowali takie rzeczy jak MVC, MVP i prezenter oraz podobne wzorce projektowe, było oddzielenie logiki biznesowej od interfejsu użytkownika.

Oczywiście część widokową można przetestować tylko poprzez uruchomienie programu i sprawdzenie, co pokazuje - innymi słowy, można ją przetestować tylko w testach akceptacyjnych.

Z drugiej strony testowanie logiki biznesowej może odbywać się w testach jednostkowych. I to jest odpowiedź na twoje pytanie. Przetestuj wszystko w modelu, a jeśli możesz i chcesz, możesz również przetestować kod kontrolera.


GUI bardzo się zmieniają

Może się to zdarzyć tylko wtedy, gdy zmienisz wymagania. Gdy wymaganie się zmienia, nie można go obejść, poza modyfikacją kodu. Jeśli uda Ci się stworzyć dobry projekt i architekturę, zmiana nie pojawi się w wielu miejscach.

BЈовић
źródło
2
Myślę, że to dobra uwaga. Może po prostu źle robię MVC :) Btw, uważam, że zmieniających się wymagań nie da się uniknąć, gdy tworzysz platformę internetową dla wielu użytkowników. Nie wiesz, jak zachowają się Twoi użytkownicy, dopóki nie zaczną używać GUI.
mik01aj
2

Testy interakcji GUI nie powinny być mniej lub bardziej kruche niż jakiekolwiek inne testy. To jest; jeśli aplikacja zmienia się w jakiś sposób, testy muszą zostać zaktualizowane, aby to odzwierciedlić.

Dla porównania:

Test jednostkowy

Oryginał : validateEmail()powinien zgłosić InvalidDatawyjątek. Co zostało poprawnie uwzględnione w teście jednostkowym.

Zmień : validateEmail()powinien zgłosić InvalidEmailwyjątek. Teraz twój test jest niepoprawny, aktualizujesz go, a wszystko znów jest zielone.

Test GUI

Oryginał : Podanie nieprawidłowego adresu e-mail spowoduje wyświetlenie wyskakującego okna błędu zawierającego „Wprowadzono nieprawidłowe dane”. Prawidłowo wykryty przez twoje testy.

Zmiana : Podanie nieprawidłowego adresu e-mail spowoduje powstanie błędu wbudowanego zawierającego „Wprowadzono nieprawidłowy adres e-mail”. Teraz twój test jest niepoprawny, aktualizujesz go, a wszystko znów jest zielone.


Pamiętaj, że testujesz dane wejściowe i wyjściowe - niektóre dobrze określone zachowania. Niezależnie od tego, czy jest to test GUI, test jednostkowy czy test integracji itp.

Jess Telford
źródło