Lesen von Konfigurationsdateien in Tests mit Selenium in Python

Hallo habr. Am Vorabend des Beginns des Python QA Engineer- Kurses haben wir eine weitere interessante Übersetzung für Sie vorbereitet.










Das Tutorial in diesem Artikel hilft Ihnen beim Testen Ihrer Webschnittstellen. Wir werden eine einfache, robuste Testlösung für Webschnittstellen mit Python , Pytest und Selenium WebDriver erstellen . Wir werden Strategien zum Erstellen guter Tests und Muster zum Schreiben guter automatisierter Tests untersuchen. Natürlich kann das entwickelte Testprojekt als gute Grundlage für die Erstellung eigener Testfälle dienen.



Welcher Browser?



Der DuckDuckGo-Suchtest aus einem der vorherigen Kapitel funktioniert einwandfrei ... aber nur in Chrome. Schauen wir uns das Gerät noch einmal an browser:



@pytest.fixture
def browser():
  driver = Chrome()
  driver.implicitly_wait(10)
  yield driver
  driver.quit()


Treibertyp und Zeitlimit sind fest codiert. Für einen Proof-of-Concept mag dies gut sein, aber Produktionstests müssen zur Laufzeit konfiguriert werden können. Tests für Webschnittstellen sollten in jedem Browser funktionieren. Die Standard-Timeout-Werte sollten angepasst werden, falls einige Umgebungen langsamer als andere ausgeführt werden. Sensible Daten wie Benutzernamen und Passwörter sollten auch niemals im Quellcode erscheinen. Wie arbeiten Sie mit solchen Testdaten ?



Alle diese Werte sind Konfigurationsdaten für das automatisierte Testsystem. Dies sind diskrete Werte, die sich systematisch auf die Funktionsweise der Automatisierung auswirken. Die Konfigurationsdaten müssen bei jedem Testlauf an den Eingang gelangen. Alles, was mit der Test- und Umgebungskonfiguration zusammenhängt, sollte als Konfigurationsdaten behandelt werden, damit der Automatisierungscode wiederverwendet werden kann.



Eingangsquellen



In einem automatisierten Testsystem gibt es verschiedene Möglichkeiten, Eingabedaten zu lesen:



  • Kommandozeilenargumente;
  • Umgebungsvariablen;
  • Eigenschaften des Systems;
  • Konfigurationsdateien;
  • API-Anfragen.


Leider unterstützen die meisten Test-Frameworks das Lesen von Daten aus Befehlszeilenargumenten nicht. Umgebungsvariablen und Systemeigenschaften sind schwierig zu verwalten und möglicherweise gefährlich zu handhaben. Service-APIs sind eine hervorragende Möglichkeit, Eingaben zu verbrauchen, insbesondere um Geheimnisse (wie Kennwörter) von einem Schlüsselverwaltungsdienst wie AWS KMS oder Azure Key Vault abzurufen . Es kann jedoch inakzeptabel sein, für solche Funktionen zu bezahlen, und es ist unklug, selbst zu schreiben. In diesem Fall sind Konfigurationsdateien die beste Option.



Eine Konfigurationsdatei ist eine reguläre Datei, die Konfigurationsdaten enthält. Automatisierte Tests können es lesen, wenn Tests ausgeführt werden, und die Eingabewerte verwenden, um Tests durchzuführen. Beispielsweise kann die Konfigurationsdatei den Browsertyp angeben, der als Browser-Fixture in unserem Beispielprojekt verwendet wird. In der Regel haben Konfigurationsdateien ein Standardformat wie JSON, YAML oder INI. Sie sollten auch flach sein, damit sie leicht von anderen Dateien unterschieden werden können.



Unsere Konfigurationsdatei



Schreiben wir eine Konfigurationsdatei für unser Testprojekt. Wir werden das JSON- Format verwenden, da es einfach zu verwenden und beliebt ist und eine klare Hierarchie aufweist. Darüber hinaus ist das json-Modul eine Python-Standardbibliothek, die JSON-Dateien problemlos in Wörterbücher konvertiert. Erstellen Sie eine neue Datei mit dem Namen tests/config.jsonund fügen Sie den folgenden Code hinzu:



{
  "browser": "chrome",
  "wait_time": 10
}


JSON verwendet Schlüssel-Wert-Paare. Wie bereits erwähnt, gibt es in unserem Projekt zwei Konfigurationswerte: Browserauswahl und Zeitüberschreitung. Hier ist "browser" eine Zeichenfolge und "wait_time" eine Ganzzahl.



Lesen einer Konfigurationsdatei mit pytest



Fixtures sind der beste Weg, um Konfigurationsdateien mit pytest zu lesen. Sie können verwendet werden, um Konfigurationsdateien vor dem Starten von Tests zu lesen und dann Werte in Tests oder sogar andere Geräte einzufügen. Fügen Sie das folgende Gerät hinzu tests/test_web.py:



import json

@pytest.fixture(scope='session')
def config():
  with open('tests/config.json') as config_file:
    data = json.load(config_file)
  return data


Das Gerät configliest und analysiert die Datei tests/config.jsonmithilfe des json-Moduls in ein Wörterbuch. Fest codierte Dateipfade sind eine weit verbreitete Praxis. Tatsächlich suchen viele Automatisierungstools und -systeme in mehreren Verzeichnissen oder anhand von Namensmustern nach Dateien. Der Umfang des Geräts ist auf "Sitzung" eingestellt, sodass das Gerät einmal pro Testsitzung ausgeführt wird. Es ist nicht erforderlich, bei jedem neuen Test jedes Mal dieselbe Konfigurationsdatei zu lesen - dies ist ineffizient!



Bei der Initialisierung des WebDriver sind Konfigurationseingaben erforderlich. Aktualisieren Sie das Gerät browserwie folgt:



@pytest.fixture
def browser(config):
  if config['browser'] == 'chrome':
    driver = Chrome()
  else:
    raise Exception(f'"{config["browser"]}" is not a supported browser')

  driver.implicitly_wait(config['wait_time'])
  yield driver
  driver.quit()


Das Fixture browserhat jetzt eine Fixture- Abhängigkeit config. Selbst wenn es configeinmal pro Testsitzung gestartet wird, wird der Browser vor jedem Test aufgerufen. Jetzt habe ich browsereine Verkettung if-else, um zu bestimmen, welcher WebDriver-Typ verwendet werden soll. Derzeit wird nur Chrome unterstützt, aber wir werden in Kürze einige weitere Typen hinzufügen. Wenn der Browser nicht erkannt wird, wird eine Ausnahme ausgelöst. Das implizite Zeitlimit wird auch aus der Konfigurationsdatei übernommen.



Da browserimmer noch eine WebDriver-Instanz zurückgegeben wird, müssen Tests, die sie verwenden, nicht überarbeitet werden! Lassen Sie uns Tests ausführen, um sicherzustellen, dass die Konfigurationsdatei funktioniert:



$ pipenv run python -m pytest tests/test_web.py 
============================= test session starts ==============================
platform darwin -- Python 3.7.3, pytest-4.5.0, py-1.8.0, pluggy-0.12.0
rootdir: /Users/andylpk247/Programming/automation-panda/python-webui-testing
collected 1 item                                                               

tests/test_web.py .                                                      [100%]

=========================== 1 passed in 5.00 seconds ===========================


Hinzufügen neuer Browser



Nachdem unser Projekt eine Konfigurationsdatei hat, können wir damit den Browser ändern. Lassen Sie uns den Test unter Mozilla Firefox anstelle von Google Chrome ausführen. Laden Sie dazu den neuesten Firefox herunter und installieren Sie ihn. Laden Sie anschließend den neuesten Geckodriver (Firefox-Treiber) herunter . Stellen Sie sicher, dass es sich geckodriverauch im Systempfad befindet.



Aktualisieren Sie den Fixture Code browser, um mit Firefox zu arbeiten:



from selenium.webdriver import Chrome, Firefox

@pytest.fixture
def browser(config):
  if config['browser'] == 'chrome':
    driver = Chrome()
  elif config['browser'] == 'firefox':
    driver = Firefox()
  else:
    raise Exception(f'"{config["browser"]}" is not a supported browser')

  driver.implicitly_wait(config['wait_time'])
  yield driver
  driver.quit()


Fügen Sie dann der Konfigurationsdatei eine Option hinzu «firefox»:



{
  "browser": "firefox",
  "wait_time": 10
}


Starten Sie nun den Test neu und Sie sehen ein Firefox-Fenster anstelle von Chrome!







Validierung



Trotz der Tatsache, dass die Konfigurationsdatei funktioniert, gibt es einen erheblichen Fehler in der Logik ihrer Verarbeitung: Die Daten werden vor dem Ausführen von Tests nicht überprüft. Das Gerät browserlöst eine Ausnahme aus, wenn der Browser nicht richtig ausgewählt ist, dies geschieht jedoch bei jedem Test. Es ist viel effektiver, wenn eine Ausnahme dieses Typs einmal pro Testsitzung ausgelöst wird. Außerdem schlägt der Test fehl, wenn die Schlüssel "browser" oder "wait_time" in der Konfigurationsdatei fehlen . Lassen Sie uns das beheben.



Fügen Sie ein neues Gerät hinzu, um die Browserauswahl zu überprüfen:



@pytest.fixture(scope='session')
def config_browser(config):
  if 'browser' not in config:
    raise Exception('The config file does not contain "browser"')
  elif config['browser'] not in ['chrome', 'firefox']:
    raise Exception(f'"{config["browser"]}" is not a supported browser')
  return config['browser']


Das Gerät config_browserhängt vom Konfigurationsgerät ab. Ebenso wie config hat es scope = "session". Wir erhalten eine Ausnahme, wenn die Konfigurationsdatei keinen "Browser" -Schlüssel enthält oder wenn der ausgewählte Browser nicht unterstützt wird. Schließlich wird der ausgewählte Browser zurückgegeben, damit Tests und andere Geräte sicher auf diesen Wert zugreifen können.



Als nächstes folgt das folgende Gerät für die Timeout-Validierung:



@pytest.fixture(scope='session')
def config_wait_time(config):
  return config['wait_time'] if 'wait_time' in config else 10


Wenn in der Konfigurationsdatei eine Zeitüberschreitung angegeben ist, gibt das Gerät config_wait_timediese zurück. Andernfalls wird standardmäßig 10 Sekunden zurückgegeben.



Aktualisieren Sie das Gerät browsererneut, um die neuen Validierungsgeräte zu verwenden:



@pytest.fixture
def browser(config_browser, config_wait_time):
  if config_browser == 'chrome':
    driver = Chrome()
  elif config_browser == 'firefox':
    driver = Firefox()
  else:
    raise Exception(f'"{config_browser}" is not a supported browser')

  driver.implicitly_wait(config_wait_time)
  yield driver
  driver.quit()


Durch das Schreiben separater Fixture-Funktionen für jeden Wert der Konfigurationsdaten werden diese einfach, klar und spezifisch. Sie können auch nur die Werte deklarieren, die zum Senden von Anforderungen erforderlich sind.



Führen Sie den Test aus und stellen Sie sicher, dass alles funktioniert:



$ pipenv run python -m pytest tests/test_web.py 
============================= test session starts ==============================
platform darwin -- Python 3.7.3, pytest-4.5.0, py-1.8.0, pluggy-0.12.0
rootdir: /Users/andylpk247/Programming/automation-panda/python-webui-testing
collected 1 item                                                               

tests/test_web.py .                                                      [100%]

=========================== 1 passed in 4.58 seconds ===========================


Und das ist cool! Sie müssen jedoch schwierig sein, um die Validierung realistischer zu gestalten. Lassen Sie uns den Wert von "browser" in "safari" ändern - einen nicht unterstützten Browser.



$ pipenv run python -m pytest tests/test_web.py 
============================= test session starts ==============================
platform darwin -- Python 3.7.3, pytest-4.5.0, py-1.8.0, pluggy-0.12.0
rootdir: /Users/andylpk247/Programming/automation-panda/python-webui-testing
collected 1 item                                                               

tests/test_web.py E                                                      [100%]

==================================== ERRORS ====================================
________________ ERROR at setup of test_basic_duckduckgo_search ________________

config = {'browser': 'safari', 'wait_time': 10}

    @pytest.fixture(scope='session')
    def config_browser(config):
      # Validate and return the browser choice from the config data
      if 'browser' not in config:
        raise Exception('The config file does not contain "browser"')
      elif config['browser'] not in SUPPORTED_BROWSERS:
>       raise Exception(f'"{config["browser"]}" is not a supported browser')
E       Exception: "safari" is not a supported browser

tests/conftest.py:30: Exception
=========================== 1 error in 0.09 seconds ============================


Beeindruckend! Der Fehler zeigte deutlich, warum er auftrat. Was passiert nun, wenn wir die Browserauswahl aus der Konfigurationsdatei entfernen?



$ pipenv run python -m pytest tests/test_web.py 
============================= test session starts ==============================
platform darwin -- Python 3.7.3, pytest-4.5.0, py-1.8.0, pluggy-0.12.0
rootdir: /Users/andylpk247/Programming/automation-panda/python-webui-testing
collected 1 item                                                               

tests/test_web.py E                                                      [100%]

==================================== ERRORS ====================================
________________ ERROR at setup of test_basic_duckduckgo_search ________________

config = {'wait_time': 10}

    @pytest.fixture(scope='session')
    def config_browser(config):
      # Validate and return the browser choice from the config data
      if 'browser' not in config:
>       raise Exception('The config file does not contain "browser"')
E       Exception: The config file does not contain "browser"

tests/conftest.py:28: Exception
=========================== 1 error in 0.10 seconds ============================


Ausgezeichnet! Eine weitere hilfreiche Fehlermeldung. Fügen Sie für den letzten Test eine Browserauswahl hinzu, entfernen Sie jedoch das Zeitlimit:



$ pipenv run python -m pytest tests/test_web.py 
============================= test session starts ==============================
platform darwin -- Python 3.7.3, pytest-4.5.0, py-1.8.0, pluggy-0.12.0
rootdir: /Users/andylpk247/Programming/automation-panda/python-webui-testing
collected 1 item                                                               

tests/test_web.py .                                                      [100%]

=========================== 1 passed in 4.64 seconds ===========================


Der Test sollte ausgeführt werden, da das Zeitlimit optional ist. Nun, die Änderungen, die wir vorgenommen haben, waren von Vorteil! Denken Sie daran, dass Sie manchmal auch Ihre Tests testen müssen .



Abschlussprüfung



Wir können noch zwei kleine Dinge tun, um den Testcode sauberer zu machen. Verschieben wir zunächst unsere Web-Fixtures in eine Datei, conftest.pydamit alle Tests sie verwenden können, nicht nur die Tests in tests / test_web.py. Zweitens ziehen wir einige Literalwerte in Modulvariablen.



Erstellen Sie eine neue Datei tests/conftest.pymit dem folgenden Code:



import json
import pytest

from selenium.webdriver import Chrome, Firefox


CONFIG_PATH = 'tests/config.json'
DEFAULT_WAIT_TIME = 10
SUPPORTED_BROWSERS = ['chrome', 'firefox']


@pytest.fixture(scope='session')
def config():
  # Read the JSON config file and returns it as a parsed dict
  with open(CONFIG_PATH) as config_file:
    data = json.load(config_file)
  return data


@pytest.fixture(scope='session')
def config_browser(config):
  # Validate and return the browser choice from the config data
  if 'browser' not in config:
    raise Exception('The config file does not contain "browser"')
  elif config['browser'] not in SUPPORTED_BROWSERS:
    raise Exception(f'"{config["browser"]}" is not a supported browser')
  return config['browser']


@pytest.fixture(scope='session')
def config_wait_time(config):
  # Validate and return the wait time from the config data
  return config['wait_time'] if 'wait_time' in config else DEFAULT_WAIT_TIME


@pytest.fixture
def browser(config_browser, config_wait_time):
  # Initialize WebDriver
  if config_browser == 'chrome':
    driver = Chrome()
  elif config_browser == 'firefox':
    driver = Firefox()
  else:
    raise Exception(f'"{config_browser}" is not a supported browser')

  # Wait implicitly for elements to be ready before attempting interactions
  driver.implicitly_wait(config_wait_time)
  
  # Return the driver object at the end of setup
  yield driver
  
  # For cleanup, quit the driver
  driver.quit()


Der gesamte Inhalt tests/test_web.pysollte jetzt einfacher und sauberer sein:



import pytest

from pages.result import DuckDuckGoResultPage
from pages.search import DuckDuckGoSearchPage


def test_basic_duckduckgo_search(browser):
  # Set up test case data
  PHRASE = 'panda'

  # Search for the phrase
  search_page = DuckDuckGoSearchPage(browser)
  search_page.load()
  search_page.search(PHRASE)

  # Verify that results appear
  result_page = DuckDuckGoResultPage(browser)
  assert result_page.link_div_count() > 0
  assert result_page.phrase_result_count(PHRASE) > 0
  assert result_page.search_input_value() == PHRASE


Nun, das ist schon Python-Stil!



Was weiter?



Der Beispielcode für unser Testprojekt ist also vollständig. Sie können es als Basis für die Erstellung neuer Tests verwenden. Das letzte Beispiel des Projekts finden Sie auch auf GitHub . Die Tatsache, dass wir den Code fertig geschrieben haben, bedeutet jedoch nicht, dass wir das Training beendet haben. In zukünftigen Artikeln werden wir darüber sprechen, wie Sie die Python-Testautomatisierung auf die nächste Stufe bringen können!






All Articles