この記事のチュートリアルは、Webインターフェイスのテストに役立ちます。Python、pytest、Selenium WebDriverを使用して、シンプルで堅牢なWebインターフェイステストソリューションを作成します。優れたテストを構築するための戦略と、優れた自動テストを作成するためのパターンについて見ていきます。もちろん、開発されたテストプロジェクトは、独自のテストケースを作成するための優れた基盤として役立ちます。
どのブラウザ?
前の章の1つからのDuckDuckGo検索テストは問題なく機能します...ただしChromeでのみです。フィクスチャを
browser
もう一度見てみましょう:
@pytest.fixture
def browser():
driver = Chrome()
driver.implicitly_wait(10)
yield driver
driver.quit()
ドライバーの種類とタイムアウトはハードコーディングされています。概念を証明するために、これは良いことかもしれませんが、実稼働テストは実行時に構成できる必要があります。 Webインターフェイスのテストは、どのブラウザでも機能するはずです。一部の環境の実行速度が他の環境よりも遅い場合に備えて、デフォルトのタイムアウト値を調整する必要があります。ユーザー名やパスワードなどの機密データもソースコードに表示されないようにする必要があります。このようなテストデータをどのように処理しますか?
これらの値はすべて、自動テストシステムの構成データです。これらは、自動化の動作に体系的に影響を与える個別の値です。構成データは、テストを実行するたびに入力に入力する必要があります。自動化コードを再利用できるように、テストおよび環境構成に関連するものはすべて構成データとして扱う必要があります。
入力のソース
自動テストシステムでは、入力データを読み取る方法がいくつかあります。
- コマンドライン引数;
- 環境変数;
- システムのプロパティ。
- 構成ファイル;
- APIリクエスト。
残念ながら、ほとんどのテストフレームワークは、コマンドライン引数からのデータの読み取りをサポートしていません。環境変数とシステムプロパティは管理が難しく、処理が危険である可能性があります。サービスAPIは、特にAWSKMSやAzureKey Vaultなどのキー管理サービスからシークレット(パスワードなど)を取得するための優れた方法です。ただし、そのような機能にお金を払うことは受け入れられない可能性があり、自分で書くことは賢明ではありません。この場合、構成ファイルが最適なオプションです。
構成ファイルは、構成データを含む通常のファイルです。自動テストでは、テストの実行時にそれを読み取り、入力値を使用してテストを実行できます。たとえば、構成ファイルは、サンプルプロジェクトでブラウザフィクスチャとして使用されるブラウザのタイプを指定する場合があります。通常、構成ファイルは、JSON、YAML、INIなどの標準形式です。また、他のファイルと簡単に区別できるように、フラットにする必要があります。
設定ファイル
テストプロジェクトの構成ファイルを作成しましょう。JSON形式を使用するのは、使いやすく、人気があり、階層が明確だからです。さらに、jsonモジュールは、JSONファイルを辞書に簡単に変換するPython標準ライブラリです。名前の付いた新しいファイルを作成し
tests/config.json
、次のコードを追加します。
{
"browser": "chrome",
"wait_time": 10
}
JSONはキーと値のペアを使用します。私たちが言ったように、私たちのプロジェクトには2つの構成値があります:ブラウザの選択とタイムアウトです。ここで、「browser」は文字列で、「wait_time」は整数です。
pytestを使用して構成ファイルを読み取る
フィクスチャは、pytestを使用して構成ファイルを読み取るための最良の方法です。これらを使用して、テストを開始する前に構成ファイルを読み取り、テストやその他のフィクスチャに値を挿入できます。次のフィクスチャを追加します
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
フィクスチャは、jsonモジュールを使用
config
してファイルtests/config.json
を読み取り、辞書に解析します。ハードコードされたファイルパスは、かなり一般的な方法です。実際、多くの自動化ツールおよびシステムは、複数のディレクトリ内のファイルをチェックするか、命名パターンと照合します。フィクスチャのスコープは「セッション」に設定されているため、フィクスチャはテストセッションごとに1回実行されます。新しいテストで毎回同じ構成ファイルを読み取る必要はありません-これは非効率的です!
WebDriverを初期化するときは、構成入力が必要です。
browser
次のようにフィクスチャを更新します。
@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()
これで
browser
、フィクスチャにフィクスチャの依存関係ができconfig
ます。それがされた場合でもconfig
、テストセッションごとに一度立ち上げ、ブラウザは、まだ各テストの前に呼び出されます。これで、使用するWebDriverのタイプを決定するbrowser
ためのチェーンif-else
ができました。現在、サポートされているのはChromeのみですが、間もなくさらにいくつかのタイプを追加する予定です。ブラウザが検出されない場合、例外がスローされます。暗黙的なタイムアウトも、構成ファイルからその値を取得します。それでもWebDriverインスタンスが返される
ため、
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 . [100%]
=========================== 1 passed in 5.00 seconds ===========================
新しいブラウザの追加
プロジェクトに構成ファイルができたので、それを使用してブラウザーを変更できます。GoogleChromeではなくMozillaFirefoxでテストを実行してみましょう。これを行うには、Firefoxの最新バージョンをダウンロードしてインストールしてから、最新のgeckodriver(Firefoxドライバー)をダウンロードします。
geckodriver
システムパスにもあることを確認してください。Firefoxで動作
するようにフィクスチャコード
browser
を更新します。
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()
次に、構成ファイルにオプションを追加します
«firefox»
。
{
"browser": "firefox",
"wait_time": 10
}
テストを再開すると、ChromeではなくFirefoxウィンドウが表示されます。
検証
構成ファイルが機能するという事実にもかかわらず、その処理のロジックには重大な欠陥があります。テストを実行する前にデータがチェックされません。
browser
ブラウザが正しく選択されていない場合、フィクスチャは例外をスローしますが、すべてのテストで発生します。このタイプの例外がテストセッションごとに1回スローされると、はるかに効果的です。さらに、構成ファイルに「browser」または「wait_time」キーがない場合、テストは失敗します。これを修正しましょう。
ブラウザの選択を検証するための新しいフィクスチャを追加します。
@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']
フィクスチャ
config_browser
は、構成フィクスチャによって異なります。また、configと同様に、scope = "session"があります。設定ファイルに「ブラウザ」キーがない場合、または選択したブラウザがサポートされていない場合は、例外が発生します。最後に、選択したブラウザを返し、テストやその他のフィクスチャがこの値に安全にアクセスできるようにします。
次は、タイムアウト検証用の次のフィクスチャです。
@pytest.fixture(scope='session')
def config_wait_time(config):
return config['wait_time'] if 'wait_time' in config else 10
構成ファイルでタイムアウトが指定されている場合、フィクスチャ
config_wait_time
はそれを返します。それ以外の場合は、デフォルトで10秒を返します。新しい検証フィクスチャを使用するには
、フィクスチャを
browser
再度更新します。
@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()
構成データ値ごとに個別のフィクスチャ関数を作成すると、それらが単純、明確、かつ具体的になります。また、リクエストの送信に必要な値のみを宣言することもできます。
テストを実行し、すべてが機能することを確認します。
$ 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 ===========================
そして、それはクールです!ただし、検証をより現実的にするには、注意が必要です。「browser」の値を「safari」(サポートされていないブラウザ)に変更してみましょう。
$ 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 ============================
うわー!エラーは、それが表示された理由を明確に示しています。では、設定ファイルからブラウザの選択を削除するとどうなりますか?
$ 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 ============================
優れた!もう1つの役立つエラーメッセージ。最後のテストでは、ブラウザの選択を追加しますが、タイムアウトを削除します。
$ 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 ===========================
タイムアウトはオプションであるため、テストを実行する必要があります。さて、私たちが行った変更は有益でした!テストもテストする必要がある場合があることを忘れないでください。
最終試験
テストコードをよりクリーンにするためにできることは、さらに2つあります。まず、
conftest.py
tests / test_web.pyのテストだけでなく、すべてのテストで使用できるように、Webフィクスチャをファイルに移動しましょう。次に、いくつかのリテラル値をモジュール変数にプルしましょう。次のコードで
名前
tests/conftest.py
を付けた新しいファイルを作成します。
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()
これで、完全なコンテンツ
tests/test_web.py
がよりシンプルでクリーンになります。
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
さて、これはすでにPythonスタイルです!
次は何ですか?
これで、テストプロジェクトのサンプルコードは完成です。新しいテストを作成するためのベースとして使用できます。プロジェクトの最後の例はGitHubにもあります。ただし、コードの記述が終了したからといって、トレーニングが終了したわけではありません。今後の記事では、Pythonテストの自動化を次のレベルに引き上げる方法について説明します。