Automatyzacja testów aplikacji desktopowych - TestComplete + Python

Automatyzacja testów aplikacji desktopowych - TestComplete + Python
Wprowadzenie do aspektu automatycznego testowania aplikacji przy użyciu narzędzia TestComplete od SmartBear i z użyciem języka Python.

Wstęp

Test Complete to środowisko pełniące role w pełni funkcjonalnej platformy testowej. Pozwala na tworzenie testów automatycznych dla aplikacji webowych, mobilnych a także desktopowych. Platforma umożliwia pisanie testów automatycznych w postaci skryptowej, w której tworzymy nasze testy wykorzystując programowanie we wspieranych przez narzędzie językach programowania m.in. Python, JavaScript. Program umożliwia również tworzenie testów automatycznych w całości w sposób graficzny bez konieczności umiejętności programowania. Tworząc testy w ten sposób nakłada to na nasz framework pewne ograniczenia, uniemożliwiając integracje z narzędziami, które nie wchodzą we wspierany stack od firmy SmartBeara. W prawie, każdym przypadku optymalniejszym rozwiązaniem będzie budowa frameworku testowego opartego w całości na kodzie. Wprawdzie wybór takiego pojdejścia będzie droższe, z racji wymogu umiejętności programowania u testera, ale taki projekt będzie bardziej skalowalny oraz pozwoli na integrację z zewnętrznymi narzędziami, które nie są dostępne dla technologii TestComplete.

Tworzenie nowego projektu

Aby poznać możliwości jakie oferuje TestComplete stworzony zostanie minimalistyczny framework, zawierające testy automatyczne wbudowanej w system operacyjny Windows, aplikacji kalkulatora. Aby stworzyć nowy projekt uruchamiamy aplikacje TestComplete. Powita nas okno startowe:

main_window.jpg(Rys. 1.0) - Główne okno programu TestComplete.

Aby rozpocząć tworzenie nowego projektu klikamy przycisk umieszczony na stronie startowej o nazwie NEW PROJECT lub z paska toolbar wybieramy kolejno: File -> New -> Project. Jeśli zrobiliśmy wszystko wg. powyższych instrukcji powinno pojawić się okno tworzenia nowego projektu.

new_project.jpg(Rys. 1.1) - Okno tworzenia projektu w TC.

Wpisujemy nazwę projektu, u nas będzie to WinCalculatorTests. Dodatkowo możemy wybrać lokalizację w jakiej zapisany zostanie nasz framework (formularz Project location), którą pozostawimy jako domyślną. Bardzo ważną rzeczą będzie wybór języka programowania na bazie, którego będziemy tworzyć nasz framework testów automatycznych. Ustawiamy formularz Scripting language na wartość Python. Po opisanych krokach klikamy na przycisk Finish.

Po chwili oczekiwania na ekranie pojawi nam się okno nowego projektu. 

new_project_main_win.jpg(Rys. 1.3) - Interfejs TC po utworzeniu pustego projektu.

Interfejs środowiska TestComplete'a można podzielić na 3 główne części:

  • Workspace - główna przestrzeń robocza, na której m.in. edytujemy nowe testy w postaci kodu, bądź wybieramy kroki przy wyborze graficznej metody pisania testów. To tutaj projektujemy również nasze suite'y testowe oraz przeglądamy logi i wyniki po uruchomieniu testów.

workspace.jpg

  • Project Explorer - Obszar zawierający wszystkie pliki, jakie zawarte są w naszym projekcie. Dodajemy tu nowe pliki skryptowe, dane testowe, lub pliki innego typu potrzebne do wykonywania testów.

proj_explorer.jpg

  • Toolbar - Górny pasek z funkcjami, które mogą się zmieniać w zależności od trybu w jakim znajduję się aokurat nasz program. Zawiera m.in. takie funkcjonalności jak obsługa debuggura czy funkcjonalność mapowania kontrolek testowanej aplikacji.

toolbar.jpg

Kiedy tworzymy nowy projekt w TestComplete, program domyślnie tworzy strukturę frameworku, gdzie zawarte są elementy pozwalające na pisanie testów w sposób graficznych tzw. KeywordTests, patrz _(Rys.1.3)_. Aby z tego zrezygnować należy usunąć element KeywordTests z obszaru Project Explorer'a naszego projektu. W tym celu należy najechać kursorem na wymieniony element, a następnie klikając na niego prawym klawiszem myszki wybrać Remove.

Tworzenie pierwszego testu

Do pisania nowych modułów pythonowych nie jest wymagana osobna instalacja Python'a. TestComplete ma swój własny interpreter tego języka. Spowodowane to jest tym, że oprócz standardowych instrukcji pythonowych TestComplete posiada, również swój własny zestaw instrukcji/funkcji/słów kluczowych, których standardowy Python nie będzie w stanie zinterpretować. Jako przykład można tu wskazać wbudowane funkcje służące do logowania poszczególnych kroków testów. Więcej pod linkiem.

Aby napisać pierwszy test w TestComplete używając do tego Python'a należy dwa razy kliknąć na nowo powstały plik Script/Unit1, co otworzy nam edytor pozwalający pisać kod naszego testu. Z racji, że cały nasz framework tworzymy w oparciu o Pythona będziemy korzystać ze Style Guide'a [PEP-8], który zawiera reguły dotyczące nazewnictwa oraz pisania poprawnie sformatowanego kodu. Zmieńmy nazwę naszego pliku, na poprawną wg. konwencji PEP-8, klikając prawy przyciskiem myszy na plik Unit1 i wybierając opcję rename oraz wpisując nową nazwę pliku. Unit1 -> test_win_calculator.

Poniżej stworzona została pierwsza funkcja testowa, którą należy przepisać do utworzonego edytora.


def test_hello_test_complete():
    Log.Message("Hello from first test written in TestComplete")

W celu uruchomienia powyższego kodu testu, należy kliknąć prawy przyciskiem myszy na nazwę funkcji oraz wybrać Run This Routine. Po uruchomieniu napisanego testu, w naszym WorkSpace'ie otworzy się okno z wynikiem i logami naszego skryptu 

first_test_result.jpg(Rys. 2.1) - Widok prezentujący wynik oraz dodatkowe informacje o wykonanym teście.

W podsumowaniu naszego testu możemy wyczytać takie informacje, jak wynik naszego testu, czas uruchomienia oraz logi, które zostały zawarte w ciele naszej funkcji testowej.

Pierwszym krokiem testu automatycznego kalkulatora, będzie uruchomienie aplikacji. Najprostszym sposobem na obsłużenie tego wymagania, będzie skorzystanie z wbudowanego mechanizmu przechowywania testowanych aplikacji w TestComplete (TestedApps)

Dodanie aplikacji do TestedApps można wykonać na kilka sposobów. Jednym z nich będzie uruchomienie kalkulatora ręcznie, a następnie wykorzystanie TestComplete'owego ObjectBrowsera. Jest to wbudowana w środowisko funkcjonalność pozwalająca na podglądanie aktualnie działających procesów. Można nazwać to SmartBear'owym Process Explorerem. Oprócz samego widoku aktualnie uruchomionych aplikacji ObjectBrowser daje nam możliwość poglądu w głąb strukturę graficzną aplikacji czyli jej kontrolek oraz elementów. 

Aby dodać kalkulator do testowanych aplikacji wyszukujemy go na liście procesów w ObjectBrowserze, klikamy prawym przyciskiem myszy i wybieramy Add Process to Tested Apps, potwierdzamy wybór klikając Yes. Możemy zauważyć, że na liście elementów projektu pojawił nam się nowy item o nazwie Tested Apps

test_apps_calc_list.jpg(Rys. 3.1) - Lista elementów w projekcie po dodaniu aplikacji kalkulatora.

Napiszmy więc nowy test, który rozpocznie się od uruchomienia aplikacji kalkulatora. Stwórzmy pomocniczą funkcję start_calculator, którą będziemy mogli reużywać w kolejnych testach.


def start_calculator():
    TestedApps.WindowsCalculator.Run()
    Log.Checkpoint("Calc.exe started.")

def test_calculator_run():
    start_calculator()

Uruchamiając test test_calculator_run uruchomimy tym samym aplikację kalkulatora oraz otrzymamy następujące podsumowanie testu.

start_calc_test.jpg(Rys. 3.2) - Wynik testu uruchamiającego kalkulator.

Mapowanie kontrolek - Object Spy

Automatyzując aplikacje desktopowe podobnie jak przy aplikacjach webowych, aby wejść w interakcje z jakąś kontrolką aplikacji należy mieć o niej jakąś informację. TestComplete posiada wbudowany mechanizm o nazwie ObjectSpy, który pozwala dobrać się do atrybutów wybranego elementu aplikacji desktopowej. 

Z paska Toolbar wybieramy zaznaczoną na poniższym zdjęciu ikonę, która uruchamia nam narzędzie ObjectSpy

object_spy_red.jpg(Rys. 3.3) - Przycisk uruchamiający ObjectSpy.

Uruchamia się okno przedstawione na poniższym zdjęciu:

object_spy.jpg(Rys. 3.4) - Okno ObjectSpy.

Mając uruchomioną testowaną aplikację oraz ObjectSpy'a, przeciągamy zaznaczoną na _Rys. 3.4_ ikonkę na wybrany element aplikacji kalkulatora. Po umieszczeniu kursora na wybranym elemencie aplikacji, element ten powinien podświetlić się na czerwono. 

kalkulator_object_spy.jpg(Rys. 3.5) - Kalkulator podczas mapowania kontrolek.

Po puszczeniu przycisku myszki w oknie ObjectSpy pojawią się informacje dotyczące wybranej kontrolki aplikacji kalkulatora takie jak pełna ścieżka, przez którą możemy wejść w interakcje z zaznaczoną kontrolką z poziomu kodu oraz dostępne metody i atrybuty TestComplete'a, które możemy wywołać na wybranej kontrolce.

object_spy_kalk_main.jpg(Rys. 3.6) - Zmapowane gówne okno aplikacji kalkulatora.

Dodajmy 3 kolejne kontrolki potrzebne do zautomatyzowania scenariusza dodawania 2+2. Potrzebujemy do tego klawiszy 2, +, = oraz okienko w którym wypisywany jest wynik działania.

result.jpg(Rys. 3.6) - Kontrolka, w której pokazywany jest wynik operacji na kalkulatorze.

Każdą ze zmapowanych ścieżek umieścimy w osobnych funkcjach. Zwiększy to czytelność oraz reużywalność kodu.


def get_two_calc_key():
    return NameMapping.Sys.Process("Microsoft.WindowsCalculator").UIAObject("Kalkulator").UIAObject("LandmarkTarget").UIAObject("Klawiatura_numeryczna").UIAObject("Dwa")

def get_plus_calc_key():
    return NameMapping.Sys.Process("Microsoft.WindowsCalculator").UIAObject("Kalkulator").UIAObject("LandmarkTarget").UIAObject("Operatory_standardowe").UIAObject("Plus")

def get_equals_calc_key():
    return NameMapping.Sys.Process("Microsoft.WindowsCalculator").UIAObject("Kalkulator").UIAObject("LandmarkTarget").UIAObject("Operatory_standardowe").UIAObject("Równa_się")

def get_calc_result_form():
    return NameMapping.Sys.Process("Microsoft.WindowsCalculator").UIAObject("Kalkulator").UIAObject("LandmarkTarget").UIAObject("Wyświetlana_wartość_to_0").UIAObject("TextContainer").UIAObject("NormalOutput")

Mając dostęp do powyższych kontrolek stworzymy test sprawdzający poprawność działania dodawania posiłkując się wbudowanymi obiektami TestComplete pozwalającymi na wybraną interakcję z kontrolkami: 

  • Click() - imituje kliknięcie lewym klawiszem myszy w wybraną kontrolkę
  • Text - atrybut zwracający wartość tekstową zapisaną w danej kontrolce

def test_calcultor_sum():
    start_calculator()
    two_key = get_two_calc_key()
    two_key.Click()

    plus_key = get_plus_calc_key()
    plus_key.Click()

    two_key = get_two_calc_key()
    two_key.Click()

    equals_key = get_equals_calc_key()
    equals.Click()

    result = get_calc_result_form().Text
    assert result == "4"

Ścieżka mapująca kontrolkę wyniku działania ma charakter dynamiczny to znaczy, że jej wartość jest zależna od przechowywanej w niej wartości, fragment ścieżki: UIAObject("Wyświetlana_wartość_to_0"). Żeby zoptymalizować nasz kod zmieńmy ciało funkcji get_calc_result_form().

        
def get_calc_result_form(expected_result_value: str):
    return NameMapping.Sys.Process("Microsoft.WindowsCalculator").UIAObject("Kalkulator").UIAObject("LandmarkTarget").UIAObject(f"Wyświetlana_wartość_to_{expected_result_value}").UIAObject("TextContainer").UIAObject("NormalOutput")
        
    

Kod testu po zmianach:

    
def test_calcultor_sum():
    start_calculator()
    two_key = get_two_calc_key()
    two_key.Click()

    plus_key = get_plus_calc_key()
    plus_key.Click()

    two_key = get_two_calc_key()
    two_key.Click()

    equals_key = get_equals_calc_key()
    equals.Click()

    exp_result = "4"
    result = get_calc_result_form(exp_result).Text
    assert result == exp_result

Co dalej?

Platforma testowa od SmartBear'a to potężne narzędzie, której wszystkie funkcjonalności nie sposób zawrzeć w pojedynczym artykule. Poniżej wypisałem kolejne kroki opisujące potencjalny rozwój naszego frameworku testowego. 

  • Implementacja wzorca projektowego Page Object Pattern w oparciu o programowanie obiektowe lub podejście funkcyjne.
  • Przechowywanie zmapowanych kontrolek w strukturze TestComplete'a [NameMapping].
  • Dopisanie nowych testów end 2 end sprawdzających wszystkich funkcjonalności kalkulatora.

TestComplete nie jest rozwiązaniem open source'owym, ale wyróżnia się bardzo dobrym Community, na którym można uzyskać odpowiedź na pytanie czysto techniczno-projektowe.

Listing kodu po zmianach:

        
def test_hello_world():
    Log.Message(f"Hello from first test written in TestComplete")  
    

def start_calculator():
    TestedApps.WindowsCalculator.Run()
    Log.Checkpoint("Calc.exe started.")

def get_two_calc_key():
    return NameMapping.Sys.Process("Microsoft.WindowsCalculator").UIAObject("Kalkulator").UIAObject("LandmarkTarget").UIAObject("Klawiatura_numeryczna").UIAObject("Dwa")

def get_plus_calc_key():
    return NameMapping.Sys.Process("Microsoft.WindowsCalculator").UIAObject("Kalkulator").UIAObject("LandmarkTarget").UIAObject("Operatory_standardowe").UIAObject("Plus")

def get_equals_calc_key():
    return NameMapping.Sys.Process("Microsoft.WindowsCalculator").UIAObject("Kalkulator").UIAObject("LandmarkTarget").UIAObject("Operatory_standardowe").UIAObject("Równa_się")

def get_calc_result_form(expected_result_value: str):
    return NameMapping.Sys.Process("Microsoft.WindowsCalculator").UIAObject("Kalkulator").UIAObject("LandmarkTarget").UIAObject(f"Wyświetlana_wartość_to_{expected_result_value}").UIAObject("TextContainer").UIAObject("NormalOutput")

def test_calculator_run():
    start_calculator()

def test_calcultor_sum():
    start_calculator()
    two_key = get_two_calc_key()
    two_key.Click()

    plus_key = get_plus_calc_key()
    plus_key.Click()

    two_key = get_two_calc_key()
    two_key.Click()

    equals_key = get_equals_calc_key()
    equals.Click()

    exp_result = "4"
    result = get_calc_result_form(exp_result).Text
    assert result == exp_result
        
    
Źródła:
https://support.smartbear.com/testcomplete/docs/reference/project-objects/test-log/log/index.html
https://peps.python.org/pep-0008/
https://support.smartbear.com/testcomplete/docs/testing-with/object-identification/name-mapping/index.html
https://community.smartbear.com/t5/TestComplete/ct-p/TestComplete_forum

To powinno Cię zainteresować