سيساعدك البرنامج التعليمي في هذه المقالة على اختبار واجهات الويب الخاصة بك. سوف نقوم بإنشاء قوة اجهة ويب اختبار الحل بسيط باستخدام بيثون ، pytest و السيلينيوم WebDriver . سننظر في استراتيجيات بناء اختبارات وأنماط جيدة لكتابة اختبارات آلية جيدة. بالطبع ، يمكن أن يكون مشروع الاختبار المطور أساسًا جيدًا لإنشاء حالات الاختبار الخاصة بك.
أي متصفح؟
يعمل اختبار بحث DuckDuckGo من أحد الفصول السابقة بشكل جيد ... ولكن فقط في Chrome. دعنا نلقي نظرة
browserأخرى على المباراة :
@pytest.fixture
def browser():
driver = Chrome()
driver.implicitly_wait(10)
yield driver
driver.quit()
نوع برنامج التشغيل والمهلة غير مضمنة. لإثبات صحة المفهوم ، قد يكون هذا جيدًا ، ولكن يجب أن تكون اختبارات الإنتاج قادرة على التهيئة في وقت التشغيل. يجب أن تعمل اختبارات واجهات الويب في أي متصفح. يجب ضبط قيم المهلة الافتراضية في حالة تشغيل بعض البيئات بشكل أبطأ من غيرها. يجب ألا تظهر البيانات الحساسة مثل أسماء المستخدمين وكلمات المرور في شفرة المصدر مطلقًا. كيف تتعامل مع بيانات الاختبار هذه ؟
كل هذه القيم هي بيانات التكوين لنظام الاختبار الآلي. إنها قيم منفصلة تؤثر بشكل منهجي على كيفية عمل الأتمتة. يجب أن تأتي بيانات التكوين إلى الإدخال مع كل تشغيل اختباري. يجب التعامل مع أي شيء متعلق بالاختبار وتكوين البيئة كبيانات تكوين بحيث يمكن إعادة استخدام رمز التشغيل الآلي.
مصادر المدخلات
في نظام الاختبار الآلي ، توجد عدة طرق لقراءة بيانات الإدخال:
- وسائط سطر الأوامر؛
- متغيرات البيئة؛
- خصائص النظام
- ملفات التكوين؛
- طلبات API.
لسوء الحظ ، لا تدعم معظم أطر عمل الاختبار قراءة البيانات من وسيطات سطر الأوامر. من الصعب إدارة متغيرات البيئة وخصائص النظام ومن المحتمل أن تكون خطرة في التعامل معها. تعد واجهات برمجة التطبيقات للخدمات طريقة رائعة لاستهلاك المدخلات ، وخاصة الحصول على الأسرار (مثل كلمات المرور) من خدمة إدارة المفاتيح مثل AWS KMS أو Azure Key Vault . ومع ذلك ، قد يكون الدفع مقابل هذه الوظيفة غير مقبول ، والكتابة بنفسك أمر غير حكيم. في هذه الحالة ، تكون ملفات التكوين هي الخيار الأفضل.
ملف التكوين هو ملف عادي يحتوي على بيانات التكوين. يمكن للاختبار الآلي قراءته عند تشغيل الاختبارات واستخدام قيم الإدخال لإجراء الاختبارات. على سبيل المثال ، قد يحدد ملف التكوين نوع المتصفح المستخدم كأداة تثبيت للمتصفح في مشروعنا النموذجي. عادةً ما تكون ملفات التكوين بتنسيق قياسي مثل JSON أو YAML أو INI. يجب أيضًا أن تكون مسطحة بحيث يمكن تمييزها بسهولة عن الملفات الأخرى.
ملف التكوين الخاص بنا
لنكتب ملف تكوين لمشروع الاختبار الخاص بنا. سنستخدم تنسيق JSON لأنه سهل الاستخدام وشائع وله تسلسل هرمي واضح. بالإضافة إلى ذلك ، وحدة json هي مكتبة Python القياسية التي تحول ملفات JSON إلى قواميس بسهولة. قم بإنشاء ملف جديد باسم
tests/config.jsonوإضافة الكود التالي:
{
"browser": "chrome",
"wait_time": 10
}
يستخدم JSON أزواج المفتاح والقيمة. كما قلنا ، هناك قيمتان للتهيئة في مشروعنا: اختيار المتصفح و timeout. هنا "المتصفح" عبارة عن سلسلة و "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
تقوم
configالأداة بقراءة الملف tests/config.jsonوتحليله في قاموس باستخدام وحدة json. تعد مسارات الملفات ذات الترميز الثابت ممارسة شائعة إلى حد ما. في الواقع ، ستتحقق العديد من أدوات وأنظمة التشغيل الآلي من الملفات الموجودة في أدلة متعددة أو ضد أنماط التسمية. يتم تعيين نطاق المباراة على "جلسة" ، لذلك سيتم تشغيلها مرة واحدة لكل جلسة اختبار. ليس من الضروري قراءة نفس ملف التكوين في كل مرة في اختبار جديد - هذا غير فعال!
يلزم إدخال التكوين عند تهيئة 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تشغيله مرة واحدة لكل جلسة اختبار ، فسيستمر الاتصال بالمتصفح قبل كل اختبار. الآن لدي browserتسلسل if-elseلتحديد نوع WebDriver الذي يجب استخدامه. في الوقت الحالي ، يتم دعم Chrome فقط ، لكننا سنضيف بعض الأنواع الأخرى قريبًا. إذا لم يتم اكتشاف المتصفح ، فسيتم طرح استثناء. ستأخذ المهلة الضمنية أيضًا قيمتها من ملف التكوين.
نظرًا
browserلأنه لا يزال يعرض مثيل WebDriver ، لا تحتاج الاختبارات التي تستخدمه إلى إعادة هيكلة! لنجري اختبارات للتأكد من عمل ملف التكوين:
$ 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 ===========================
إضافة متصفحات جديدة
الآن بعد أن أصبح مشروعنا يحتوي على ملف تهيئة ، يمكننا استخدامه لتغيير المتصفح. لنجري الاختبار على Mozilla Firefox بدلاً من Google Chrome. للقيام بذلك ، قم بتنزيل أحدث إصدار من Firefox وتثبيته ، ثم قم بتنزيل أحدث برنامج geckodriver (برنامج تشغيل Firefox). تأكد من وجوده
geckodriverأيضًا في مسار النظام.
قم بتحديث رمز التثبيت
browserللعمل مع Firefox:
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
}
أعد تشغيل الاختبار الآن وسترى نافذة Firefox بدلاً من Chrome!
التحقق من الصحة
على الرغم من حقيقة أن ملف التكوين يعمل ، إلا أن هناك عيبًا كبيرًا في منطق معالجته: لا يتم فحص البيانات قبل إجراء الاختبارات. المباراة
browserسوف بطرح استثناء إذا لم يتم تحديد المتصفح بشكل صحيح، ولكن ذلك سيحدث لكل اختبار. سيكون أكثر فاعلية إذا تم طرح استثناء من هذا النوع مرة واحدة لكل جلسة اختبار. بالإضافة إلى ذلك ، سيفشل الاختبار إذا كان مفتاح "المتصفح" أو "وقت الانتظار" مفقودًا في ملف التكوين . دعونا نصلح هذا.
أضف عنصرًا جديدًا للتحقق من اختيار المتصفح:
@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يعتمد
التثبيت على تجهيزات التكوين. أيضًا ، مثل التكوين ، له نطاق = "جلسة". سنحصل على استثناء إذا لم يكن هناك مفتاح "متصفح" في ملف التكوين أو إذا كان المستعرض المحدد غير مدعوم. أخيرًا ، يقوم بإرجاع المتصفح المحدد حتى تتمكن الاختبارات والتركيبات الأخرى من الوصول إلى هذه القيمة بأمان.
التالي هو التثبيت التالي للتحقق من المهلة:
@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 ===========================
وهذا رائع! ومع ذلك ، لجعل التحقق أكثر واقعية ، يجب أن تكون خادعًا. دعنا نغير قيمة "المتصفح" إلى "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 ============================
ممتاز! رسالة خطأ مفيدة أخرى. بالنسبة للاختبار الأخير ، دعنا نضيف تحديد متصفح ، ولكن قم بإزالة المهلة:
$ 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 ===========================
يجب تشغيل الاختبار لأن المهلة اختيارية. حسنًا ، لقد كانت التغييرات التي أجريناها مفيدة! تذكر أنك تحتاج أحيانًا إلى اختبار اختباراتك أيضًا .
الاختبار النهائي
هناك شيئان صغيران يمكننا القيام بهما لجعل كود الاختبار أكثر نظافة. أولاً ، دعنا ننقل تركيبات الويب الخاصة بنا إلى ملف
conftest.pyبحيث يمكن لجميع الاختبارات استخدامها ، وليس فقط الاختبارات في / test_web.py. ثانيًا ، دعنا نسحب بعض القيم الحرفية في متغيرات الوحدة.
أنشئ ملفًا جديدًا باسم
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
حسنًا ، هذا بالفعل أسلوب بايثون!
ماذا بعد؟
لذلك ، اكتمل نموذج الكود الخاص بمشروع الاختبار لدينا. يمكنك استخدامه كأساس لإنشاء اختبارات جديدة. يمكنك أيضًا العثور على المثال الأخير للمشروع على GitHub . ومع ذلك ، فإن حقيقة أننا انتهينا من كتابة الكود لا تعني أننا انتهينا من التدريب. في المقالات المستقبلية ، سنتحدث عن كيفية الانتقال بأتمتة اختبار Python إلى المستوى التالي!