pyTest – DDT w praktyce #3
Data Driven Testing czyli DDT w praktyce, przy użyciu Python oraz pyTesta. W części trzeciej: conftest i warianty.
W związku z pytaniami dotyczącymi uruchamiania testów z wykorzystaniem DDT oraz pyTesta, a także uciążliwej edycji kodu w celu zmiany pliku parametryzacyjnego do testów, postanowiłem rozwinąć ten temat w tym artykule. Dodatkowo odpowiem na pytanie, w którym miejscu najlepiej umieścić konfigurację odpowiedniego wariantu w urządzeniu do testów. W przytoczonych poniżej przykładach wykorzystam testy oraz parametry stworzone w poprzednim artykule z serii Data Driven Testing, czyli Testowaniu Sterowanym Danymi.
Przygotowanie importu parametrów
Aby móc wykorzystać automatyczną zmianę parametrów, niezbędne jest wyedytowanie stworzonego wcześniej kodu importu parametrów. Można to wykonać z wykorzystaniem dodatkowej biblioteki importlib. Zamiast wcześniej używanego importu parametrów:
from input_c import input_operation, input_power_function
wykorzystamy:
import importlib
params = importlib.import_module("input_a")
Następnie musimy dodać params w parametryzacji testów:
@pytest.mark.parametrize('numA, operation, numB, result', params.input_operation)
(...)
@pytest.mark.parametrize('number, result', params.input_power_function)
(...)
W tym momencie, podając jako argument dla import_module, dowolny plik z parametrami, będzie on wykorzystany w testach.
Dodanie argumentu dla komendy uruchamiającej testy
Idąc dalej, jesteśmy w stanie stworzyć argument dla pyTesta, który pozwoli nam uruchomić testy z dowolnym plikiem parametryzacyjnym. Możemy to wyjonać korzystając jedynie z konsoli. Posłużymy się tutaj plikiem conftest.py. Jest to plik, który konfiguruje testy dla konkretnego katalogu. Pliku tego nie ma domyślnie w naszym katalogu z testami, dlatego zaczniemy od jego stworzenia:
qabrio@test:~$ touch conftest.py
Następnie z wykorzystaniem wbudowanego inicjalizującego pytests hook (wbudowane pytestowe funkcje umożliwiające ingerencję w uruchamianie, zbieranie czy raportowanie testów) – pytest_addoption, tworzymy dodatkowy atrybut variantName, wykorzystywany podczas uruchamiania testów w pyTest:
import pytest
def pytest_addoption(parser):
parser.addoption("--variantName", action="store", default="input_a")
W powyższym przykładzie, jako pierwszy argument w wykorzystanej funkcji pytest_addoption, podajemy jego nazwę, następnie akcję do wykonania oraz wartość domyślną, która będzie wykonana w sytuacji, gdy nie wykorzystamy atrybutu podczas uruchomienia testów. Z racji, że wariant A uznajemy za bazowy, umieściłem plik z jego parametrami jako domyślny.
Przekazanie atrybutu do testów
Mając już możliwość podania dodatkowego argumentu, dobrze byłoby go wykorzystać :). Z pomocą przyjdzie nam kolejny pytestowy hook, czyli pytest_configure. Dzięki niemu dodamy do przestrzeni nazw pytest, nazwę testowanego wariantu:
def pytest_configure(config):
pytest.variantName = config.getoption('--variantName')
Mając już dostęp do podanej nazwy pliku parametryzacyjego, na chwilę wrócimy do pliku zawierającego testy, zmieniając argument w import_module na pytest.variantName. Aktualnie import wygląda tak:
params = importlib.import_module(pytest.variantName)
Konfiguracja urządzenia
Oczywiście każde urządzenia posiada swoją konfigurację, dlatego też posłużę się tutaj lekkim uproszczeniem. Załóżmy, że konfiguracja naszego środowiska znajduje się w pliku device_config.py, który wygląda jak poniżej:
def set_device(deviceName):
if deviceName == "input_b":
_set_variant_b()
elif deviceName == "input_c":
_set_variant_c()
else:
_set_variant_a()
def _set_variant_a():
print("<<< KONFIGURUJĘ WARIANT A!!! >>>")
def _set_variant_b():
print("<<< KONFIGURUJĘ WARIANT B!!! >>>")
def _set_variant_c():
print("<<< KONFIGURUJĘ WARIANT C!!! >>>")
Teraz wystarczy zaimportować powyższy plik w pliku conftest.py, a następnie wykorzystać w nim funkcję wykonującą wstępną konfigurację:
import pytest
from device_config import set_device
def pytest_addoption(parser):
parser.addoption("--variantName", action="store", default="input_b")
def pytest_configure(config):
pytest.variantName = config.getoption('--variantName')
set_device(pytest.variantName)
Prostsze uruchamianie testów?
Mając już odpowiednio przygotowane środowisko, możemy w prosty sposób wybierać testy dla wariantu C, bezpośrednio z linii komend:
qabrio@test:~/ddt$ pytest -v test_calculator.py --variantName input_c
<<< KONFIGURUJĘ WARIANT C!!! >>>
================================================== test session starts ==================================================
(...)
collected 6 items
test_calculator.py::test_operation[1-*-255-255] PASSED [ 16%]
test_calculator.py::test_operation[255-+-0-255] PASSED [ 33%]
test_calculator.py::test_operation[100---255--155] PASSED [ 50%]
test_calculator.py::test_operation[170---32-5.31] PASSED [ 66%]
test_calculator.py::test_power_function[0-0] PASSED [ 83%]
test_calculator.py::test_power_function[255-65025] PASSED [100%]
=============================================== 6 passed in 0.01 seconds ================================================
Analogicznie dla wariantu B:
qabrio@test:~/ddt$ pytest -v test_calculator.py --variantName input_b
<<< KONFIGURUJĘ WARIANT B!!! >>>
================================================== test session starts ==================================================
(...)
collected 4 items
test_calculator.py::test_operation[1-*-255-255] PASSED [ 25%]
test_calculator.py::test_operation[255-+-0-255] PASSED [ 50%]
test_calculator.py::test_operation[100---255--155] PASSED [ 75%]
test_calculator.py::test_power_function[number0-result0] SKIPPED [100%]
========================================== 3 passed, 1 skipped in 0.01 seconds ==========================================
Natomiast uruchamiając testy bez wykorzystania opcji –variantName, zostaną wykorzystane domyślne ustawienia, czyli w naszym przypadku wariant A:
qabrio@test:~/ddt2$ pytest -v test_calculator.py
<<< KONFIGURUJĘ WARIANT A!!! >>>
============================================================================================================ test session starts ============================================================================================================
(...)
collected 5 items
test_calculator.py::test_operation[1-*-10-10] PASSED [ 20%]
test_calculator.py::test_operation[10-+-0-10] PASSED [ 40%]
test_calculator.py::test_operation[5---10--5] PASSED [ 60%]
test_calculator.py::test_power_function[1-1] PASSED [ 80%]
test_calculator.py::test_power_function[255-65025] PASSED [100%]
========================================================================================================= 5 passed in 0.01 seconds ==========================================================================================================
Jak widać na powyższych przykładach, uruchamianie testów uprościło się w znacznym stopniu. Dzięki temu nie jesteśmy juz skazani na jakiekolwiek edytowanie przygotowanego kodu. Dodatkowo możemy w łatwy sposób uruchomić testy z innych narzędzi testowych, czy choćby z wykorzystaniem tabeli linuksowego crona.