Python: язык программирования
Pytest: независимый, полноценный фреймворк для юнит-тестирования на Python
Selenium: инструмент для тестирования веб-приложений
Allure: отображение отчетов тестирования
DDT: управление данными
1.1 Интерпретатор Python Версия 3.8.0
1.2 Интегрированная среда разработки PyCharm Профессиональная версия
Загрузите драйвер браузера, версия драйвера должна совпадать с версией браузера.
Ссылки для загрузки:
Chrome: http://npm.taobao.org/mirrors/chromedriver/ Firefox: https://github.com/mozilla/geckodriver/releases Edge: https://developer.microsoft.com/en-us/microsoft-edge/tools/webdriver/ Safari: https://webkit.org/blog/6900/webdriver-support-in-safari-10/
Примечание: Просмотр версии драйвера браузера
1. Нажмите Ctrl + F12 для открытия окна элементов
2. Нажмите Console и введите 'navigator.userAgent'. Обратите внимание на регистр букв.
```3. Установка Allure
1) Загрузите Allure, ссылка для загрузки [https://github.com/allure-framework/allure2/releases](https://github.com/allure-framework/allure2/releases)
2) Распакуйте на локальном компьютере (этот каталог будет использоваться в дальнейшем, не трогайте его), настройте переменную окружения (добавьте каталог `bin` из каталога `allure` в переменную окружения `path` системы)
См. блог "[Обзор фреймворка Allure](https://blog.csdn.net/weixin_59868574/article/details/135502984)" [https://blog.csdn.net/weixin_59868574/article/details/135502984](https://blog.csdn.net/weixin_59868574/article/details/135502984).
4. Установка сторонних библиотек
Создайте файл `requirements.txt` в проекте, введите следующие данные, затем установите файл `requirements.txt` или установите каждую библиотеку отдельно
```python
pytest==7.4.2
selenium==3.141.0
urllib3==1.26.18
PySocks~=1.7.1
PyYAML~=6.0.1
psutil~=5.9.7
allure-pytest~=2.13.2
Включает в себя метки времени, даты и другие строки, используемые другими модулями. Время упаковано в отдельный модуль, который могут использовать другие модули.
Создайте модуль timer.py
в каталоге utils
"""
@Файл : timer.py
@Автор : TJC
@Дата : 2023/7/10
@Описание: Управление строками времени
"""
import time
import datetime
from functools import wraps
def timestamp():
"""Таймстамп"""
return time.time()
def dt_strftime(fmt="%Y%m"):
"""
Форматирование даты в datetime формате
:param fmt "%Y%m%d %H%M%S"
"""
return datetime.datetime.now().strftime(fmt)
def sleep(seconds=1.0):
"""Время ожидания"""
time.sleep(seconds)
def running_time(func):
"""Время выполнения функции"""
``````markdown
## 3.1 Функции времени time_util.py
Для тестирования скриптов требуется функциональность времени, поэтому создаем модуль `time_util` в директории `utils`. Включенный код представлен ниже.
```python
def timestamp():
"""Временная метка"""
return time.time()
def dt_strftime(fmt="%Y%m"):
"""
Форматирование времени с помощью datetime
:param fmt "%Y%m%d %H%M%S"
"""
return datetime.datetime.now().strftime(fmt)
def sleep(seconds=1.0):
"""Засыпание на время"""
time.sleep(seconds)
def running_time(func):
"""Время выполнения функции"""
@wraps(func)
def wrapper(*args, **kwargs):
start = timestamp()
res = func(*args, **kwargs)
print("Проверка элемента завершена! Время выполнения %.3f секунд!" % (timestamp() - start))
return res
return wrapper
if __name__ == '__main__':
print(dt_strftime("%Y%m%d %H%M%S"))
Для тестовых скриптов требуется записывать выполнение логов, поэтому создаем модуль управления логами logger
в директории utils
. Включенный код представлен ниже.
"""
@File : logger.py
@Author : TJC
@Date : 2023/7/10
@Desc : Модуль управления логами
"""
import logging
from logging.handlers import RotatingFileHandler, TimedRotatingFileHandler
from config.config import cm
class Log:
def __init__(self, seg):
self.seg = seg
# Создаем логгер
self.logger = logging.getLogger()
if not self.logger.handlers:
self.logger.setLevel(logging.DEBUG)
# Устанавливаем размер файла логов в 10МБ
max_bytes = 10 * 1024 * 1024
# Устанавливаем максимальное количество файлов для хранения в 50
backup_count = 50
Тестовые сценарии требуют записи логов выполнения, поэтому управление логами включено в модуль, который может быть вызван другими модулями. В каталоге utils создается новый модуль logger.py, содержащий следующий код:
python
"""
@File : logger.py
@Author : TJC
@Date : 2023/7/10
@Desc : Модуль управления логами
"""
import logging from logging.handlers import RotatingFileHandler, TimedRotatingFileHandler from config.config import cm
class Log: def init(self, seg):
self.seg = seg
# Создаем логгер
self.logger = logging.getLogger()
if not self.logger.handlers:
self.logger.setLevel(logging.DEBUG)
# Определяем размер файла логов как 10МБ
max_bytes = 10 * 1024 * 1024
# Определяем количество файлов логов для хранения как 50
backup_count = 50
# Создаем обработчик лог-файлов, правила нумерации резервных копий по умолчанию — добавление цифры в конце имени файла, чем больше цифра, тем старше время создания файла
if self.seg == "time":
fh = TimedRotatingFileHandler(cm.log_file, when='D', backupCount=backup_count, encoding='utf-8')
"""
:param: when
'S': каждую секунду
'M': каждую минуту
'H': каждый час
'D': каждый день (значение по умолчанию)
'W0' ~ 'W6': каждую неделю (0 — понедельник, 1 — вторник и т.д.)
'midnight': каждый день в полночь (равнозначен 'D')
:param: interval по умолчанию 1, 1D — один день
"""
fh.setLevel(logging.INFO)
else:
fh = RotatingFileHandler(cm.log_file, maxBytes=max_bytes, backupCount=backup_count, encoding='utf-8')
fh.setLevel(logging.INFO)
Создайте обработчик вывода в консоль
ch = logging.StreamHandler()
ch.setLevel(logging.INFO)
# Определите формат вывода
formatter = logging.Formatter(self.fmt)
fh.setFormatter(formatter)
ch.setFormatter(formatter)
```# Добавьте обработчики
self.logger.addHandler(fh)
self.logger.addHandler(ch)
@property
def fmt(self):
return '%(levelname)s\t%(asctime)s\t[%(filename)s:%(lineno)d]\t%(message)s'
# Разделение лог-файлов по времени, можно изменить параметр when, рекомендовано использовать в скриптах для тестирования производительности
# log = Log('time').logger
# Разделение лог-файлов по размеру
log = Log('size').logger
Все пути к файлам и директориям объединены в одном месте и могут быть использованы другими модулями. При добавлении новых файлов или директорий, они должны быть добавлены здесь.
Создайте новый модуль config.py
в папке config
, код следующий:
"""
@File : config.py
@Author : TJC
@Date : 2023/7/10
@Desc : Пути к файлам и директориям проекта
"""
import os
from utils.timer import dt_strftime
class ConfigManager:
"""
Управление директориями и файлами проекта
"""
DIR_CURR = os.path.abspath(__file__)
DIR_CONF = os.path.dirname(os.path.abspath(__file__))
# Путь к проекту
DIR_BASE = os.path.dirname(DIR_CONF)
DIR_COMM = os.path.join(DIR_BASE, "common")
DIR_UTIL = os.path.join(DIR_BASE, "utils")
# Путь к объектам страниц
DIR_PAGE = os.path.join(DIR_BASE, 'page_object')
# Путь к элементам страниц
DIR_ELEMENT = os.path.join(DIR_BASE, "page_element")
DIR_DRIVER = os.path.join(DIR_BASE, "driver")
@property
def web_ini_file(self):
"""Файл конфигурации web"""
ini_file = os.path.join(self.DIR_CONF, 'web_cfg.ini')
if not os.path.exists(ini_file):
raise FileNotFoundError("Конфигурационный файл %s отсутствует!" % ini_file)
return ini_file
``````python
@property
def dir_report_json(self):
"""Директория отчета Allure JSON файлы"""
report_dir = os.path.join(self.DIR_BASE, 'report')
os.makedirs(report_dir, exist_ok=True)
json_dir = os.path.join(report_dir, 'json')
os.makedirs(json_dir, exist_ok=True)
return json_dir
@property
def dir_report_html(self):
"""Директория отчета Allure в формате HTML"""
report_dir = os.path.join(self.DIR_BASE, 'report')
os.makedirs(report_dir, exist_ok=True)
html_dir = os.path.join(report_dir, 'html')
os.makedirs(html_dir, exist_ok=True)
return html_dir
@property
def dir_log(self):
"""Директория логов"""
log_dir = os.path.join(self.DIR_BASE, 'logs')
os.makedirs(log_dir, exist_ok=True)
return log_dir
@property
def log_file(self):
"""Файл логов"""
return os.path.join(self.dir_log, '{}.log'.format(dt_strftime()))
@property
def dir_img(self):
"""Директория скриншотов"""
img_dir = os.path.join(self.dir_log, 'images')
os.makedirs(img_dir, exist_ok=True)
return img_dir
@property
def img_file(self):
"""Файл скриншота"""
return os.path.join(self.dir_img, '{}.png'.format(dt_strftime('%Y%m%d%H%M%S')))
@property
def dir_testdata(self):
"""Директория тестовых данных"""
test_data_dir = os.path.join(self.DIR_BASE, 'testdata')
os.makedirs(test_data_dir, exist_ok=True)
return test_data_dir
@property
def dir_web_testdata(self):
"""Директория тестовых данных-web"""
test_data_dir = os.path.join(self.dir_testdata, 'web')
os.makedirs(test_data_dir, exist_ok=True)
return test_data_dir
@property
def test_web_yaml_file(self):
"""Файл тестовых данных YAML"""
yaml_file = os.path.join(self.dir_web_testdata, 'test_data.yaml')
if not os.path.exists(yaml_file):
raise FileNotFoundError("Файл тестовых данных %s не существует!" % yaml_file)
return yaml_file
``````markdown
### 4.2 конфигурационный файл web_cfg.ini
Создайте файл `web_cfg.ini` в директории `config` (название файла должно совпадать с названием, используемым в `config.py`). Этот файл предназначен для хранения параметров для веб-тестирования, пример представлен ниже:
[host] HOST = http://IP/index.action [driver]
DRIVER_PROCESS = chromedriver.exe
## 5. Общие тестовые библиотеки common
### 5.1 Общие методы common.py
Передовые и последующие действия pytest-фреймворка, автоматически вызываются при выполнении. В директории `common` создайте модуль `common.py`, здесь передовые и последующие действия представляют собой только логирование, что облегчает просмотр логов, код представлен ниже:
""" @File : common.py @Author : TJC @Date : 2023/7/10 @Desc : Передовые и последующие действия для выполнения тестовых случаев, pytest-фреймворк """ from utils.logger import log
class Common:
@staticmethod
def setup_class():
log.info('------- начало модуля')
```python
@property
def test_web_json_file(self):
"""файл тестовых данных.json"""
json_file = os.path.join(self.dir_web_testdata, 'test_data.json')
if not os.path.exists(json_file):
raise FileNotFoundError("Файл тестовых данных %s не существует!" % json_file)
return json_file
@property
def web_driver(self):
"""драйвер браузера"""
os.makedirs(self.DIR_DRIVER, exist_ok=True)
driver = os.path.join(self.DIR_DRIVER, 'chromedriver.exe')
if not os.path.exists(driver):
raise FileNotFoundError("Драйвер браузера %s не существует!" % driver)
return driver
``````markdown
### 5.2 Чтение конфигурационных файлов read_cfg.py
Конфигурационные файлы используются в формате ini. Предоставляется метод для чтения ini-файлов. В директории `common` создайте файл `read_cfg.py`. Код представлен ниже:
```python
"""
@File :read_cfg.py
@Author : TJC
@Date : 2023/7/10
@Desc :Обертка для чтения конфигурационных файлов ini
"""
import configparser
from config.config import cm
# Определите фактические значения
HOST = 'host'
DRIVER = 'driver'
class ReadConfig:
"""Класс для работы с ini-конфигурационным файлом"""
def __init__(self, ini_file):
self.file = ini_file
self.config = configparser.RawConfigParser() # При наличии символов % используйте Raw для чтения, чтобы не анализировать значения в конфигурационном файле
self.config.read(self.file, encoding='utf-8')
def _get(self, section, option):
"""Получение значения из конфигурационного файла"""
return self.config.get(section, option)
def _set(self, section, option, value):
"""Обновление значения в конфигурационном файле"""
self.config.set(section, option, value)
with open(cm.ini_file, 'w') as f:
self.config.write(f)
@property
def web_url(self):
return self._get(HOST, 'HOST')
@property
def web_driver_process(self):
return self._get(DRIVER, 'DRIVER_PROCESS')
web_cfg = ReadConfig(cm.web_ini_file)
Дополнительные конфигурации и данные для драйвера будут добавлены позже.
### 5.2 Чтение конфигурационных файлов read_cfg.py
Конфигурационные файлы используются в формате ini. Предоставляется метод для чтения ini-файлов. В директории `common` создайте файл `read_cfg.py`. Код представлен ниже:
```python
"""
@File :read_cfg.py
@Author : TJC
@Date : 2023/7/10
@Desc :Обертка для чтения конфигурационных файлов ini
"""
import configparser
from config.config import cm
# Определите фактические значения
HOST = 'host'
DRIVER = 'driver'
class ReadConfig:
"""Класс для работы с ini-конфигурационным файлом"""
def __init__(self, ini_file):
self.file = ini_file
self.config = configparser.RawConfigParser() # При наличии символов % используйте Raw для чтения, чтобы не анализировать значения в конфигурационном файле
self.config.read(self.file, encoding='utf-8')
def _get(self, section, option):
"""Получение значения из конфигурационного файла"""
return self.config.get(section, option)
def _set(self, section, option, value):
"""Обновление значения в конфигурационном файле"""
self.config.set(section, option, value)
with open(cm.ini_file, 'w') as f:
self.config.write(f)
@property
def web_url(self):
return self._get(HOST, 'HOST')
@property
def web_driver_process(self):
return self._get(DRIVER, 'DRIVER_PROCESS')
web_cfg = ReadConfig(cm.web_ini_file)
Дополнительные конфигурации и данные для драйвера будут добавлены позже.
```### 6.1. Структура``` базовый класс (base): общие методы для страниц page
страница (page): одна страница упакована в объект
элемент (element): элементы страницы, в данном проекте используются .py файлы для хранения элементов страницы, что облегчает написание кода
тест-кейс (testcase): хранение тестовых сценариев
### 6.2 Базовый класс page_base
В папке page_base создайте файл page_base.py со следующим кодом:
```python
"""
@File :page_base.py
@Author :TJC
@Date :2023-07-21
@Desc :Базовый класс: хранение всех общих методов для страниц
"""
from selenium.webdriver.support.select import Select
from selenium.webdriver.support.wait import WebDriverWait
from utils.logger import log
from utils.timer import sleep
from selenium.webdriver.common.action_chains import ActionChains
class Base:
def __init__(self, driver):
self.driver = driver
def base_find(self, loc, timeout=30, poll=0.5):
"""
Поиск элемента
timeout: время ожидания, обычно 30 секунд, если элемент не найден за это время, генерируется ошибка
poll: интервал проверки, по умолчанию 0.5 секунды, если есть мигающее уведомление, можно установить значение 0.1 секунды
"""
return WebDriverWait(self.driver, timeout, poll).until(lambda x: x.find_element(*loc))
def base_input(self, loc, text):
"""Ввод текста"""
el = self.base_find(loc)
el.clear()
if text is not None:
el.send_keys(text)
log.info(f"{el} ввод текста: {text}")
def base_click(self, loc):
"""Клик"""
self.base_find(loc).click()
sleep()
log.info(f'Клик по кнопке: {loc}')
def base_get_text(self, loc):
"""Получение текущего текста элемента"""
_text = self.base_find(loc).text
log.info(f"Получение текста: {_text}")
return _text
``````python
def base_get_title(self):
"""Получение текущего заголовка страницы"""
title = self.driver.title
log.info(f"Текущий заголовок страницы: {title}")
return title
def base_alert_confirm(self):
"""Автоматическое подтверждение алерта для продолжения тестирования"""
self.driver.switch_to().alert().accept()
def base_is_dis(self, loc):
"""Проверка видимости элемента"""
state = self.base_find(loc).is_displayed()
log.info(f"Получение состояния видимости элемента: {state}")
return state
## Основные методы
```python
def base_keep_press(self, loc, time):
"""Задержка нажатия"""
ActionChains(self.driver).click_and_hold(self.base_find(loc)).perform()
log.info(f"Задержка нажатия: {loc}")
sleep(time)
ActionChains(self.driver).release(self.base_find(loc)).perform()
log.info(f"Отпускание: {loc}")
def base_select(self, loc, text):
"""
Выбор элемента из выпадающего списка
:param loc: элемент селектора, родительский элемент, а не option
:param text: выбор по видимому тексту
"""
Select(self.base_find(loc)).select_by_visible_text(text)
log.info(f"Выбор элемента из выпадающего списка {loc}: {text}")
def base_tick_checkbox(self, loc, num):
"""Галочка в чекбоксе"""
checkbox_list = self.base_find(loc)
action = ActionChains(self.driver)
for i in range(0, num):
action.move_to_element(checkbox_list[i])
action.click(checkbox_list[i]).perform()
sleep()
def base_invisible_element(self, loc, num, stop):
"""Выполнение действий на динамически изменяющихся элементах"""
msg = self.base_find(loc)
for i in range(0, num):
action = ActionChains(self.driver)
action.move_to_element(msg[i])
action.click(msg[i])
action.perform()
sleep(stop)
### 6.3 Элементы страницы page_element
В директории `page_element` создайте файл `el_xxx.py`, где каждый `.py` файл будет содержать информацию о элементах конкретной страницы.
Например, создайте файл `el_login.py`, который будет содержать информацию о элементах страницы входа:
```python
"""
@File :el_login.py
@Author :TJC
@Date :2023-07-21
@Desc :Элементы страницы входа
"""
from selenium.webdriver.common.by import By
by_id = By.ID
by_name = By.NAME
by_class_name = By.CLASS_NAME
by_tag_name = By.TAG_NAME
by_link_text = By.LINK_TEXT
by_css_selector = By.CSS_SELECTOR
by_xpath = By.XPATH
# Элементы страницы входа
login_window = by_xpath, '//*[@id="login"]/div/div[1]/div'
login_username = by_xpath, '//*[@placeholder="Имя пользователя"]'
login_pwd = by_id, 'password'
login_err_info = by_css_selector, '.el-message__content'
login_btn = by_xpath, '//*[text()="Войти"]'
logout_btn = by_xpath, '//*[@id="login"]/div/div[2]/div/form/div[4]/div/span[3]'
logout_cancel = by_xpath, '/html/body/div[2]/div/div[3]/button[1]'
logout_confirm = by_xpath, '/html/body/div[2]/div/div[3]/button[2]'
```
### 6.4 Объекты страниц
В директории `page_object` создайте файл `page_xxx.py`. Каждый `.py` файл хранит информацию о одной странице.
Например, создайте файл `page_login.py`, который будет содержать информацию о странице входа, как показано ниже:
```python
"""
@File :page_login.py
@Author :TJC
@Date :2cq3-07-21
@Desc :Пакет для страницы входа
"""
from allure import step
from utils.timer import sleep
from page_base.page_base import Base
from page_element import el_login
class PageLogin(Base):
"""Страница входа"""
```
``````markdown
## 6.5 Структура тестовых случаев в директории testcases
В директории `testcases` создайте пакеты Python для хранения тестовых случаев различных модулей. Например, создайте папки `web` и `app`, чтобы хранить тестовые случаи для веб-приложений и мобильных приложений соответственно.
В папке `web` создайте файлы `test_login`, `test_group_call` и другие.
```
```python
def page_input_username(self, username):
"""Ввод имени пользователя"""
self.base_input(el_login.login_username, username)
sleep()
def page_input_passwd(self, password):
"""Ввод пароля"""
self.base_input(el_login.login_pwd, password)
sleep()
def page_input_verify_code(self, verify_code):
"""Ввод кода подтверждения"""
pass
def page_click_login_btn(self):
"""Нажатие кнопки входа"""
self.base_click(el_login.login_btn)
sleep()
def page_get_error_info(self):
"""Получение информации об ошибке"""
return self.base_get_text(el_login.login_err_info)
def page_click_err_btn_ok(self):
pass
def page_click_quit_btn(self):
"""Нажатие кнопки выхода"""
self.base_click(el_login.login_btn)
sleep()
def page_click_quit_cancel(self):
"""Отмена выхода"""
self.base_click(el_login.logout_cancel)
sleep()
def page_click_quit_confirm(self):
"""Подтверждение выхода"""
self.base_click(el_login.logout_confirm)
sleep()
def page_login(self, username, password):
sleep(2)
with allure.step(f"Ввод имени пользователя: {username}"):
self.page_input_username(username)
with allure.step(f"Ввод пароля: {password}"):
self.page_input_passwd(password)
sleep(5)
with allure.step("Нажатие кнопки входа"):
self.page_click_login_btn()
```Если разделение директорий не требуется, его можно не выполнять. Разделение директорий помогает организовать тестовые случаи и упрощает автоматическое выполнение файла `conftest.py`.
### 6.6. conftest.py
В директории `testcases` создайте файл `conftest.py`, в котором будут содержаться все необходимые для тестовых сценариев фикстуры. Описание файла `conftest.py` можно найти в статье «Введение в pytest»: https://blog.csdn.net/weixin_59868574/article/details/135500904.
Файл `conftest.py` содержит веб-драйвер, получение скриншотов для отчетов Allure при неудачном выполнении тестов, вывод названия тестового случая. Код представлен ниже:
```markdown
"""
@File :testcases\conftest.py
@Author :TJC
@Date :2023-07-21
@Desc :предварительная настройка для веб-тестовых сценариев
"""
import pytest
import psutil
import allure
from selenium import webdriver
from utils.timer import dt_strftime
from utils.logger import log
from common.read_cfg import web_cfg
from config.config import cm
web_driver = None
@allure.title("Веб-драйвер")
@pytest.fixture(scope='session', autouse=True)
def drivers(request):
global web_driver
if web_driver is None:
option = webdriver.ChromeOptions()
option.binary_location = r'D:\HBFEC\AcroDCYJ\现场应急调度平台.exe' # Измените путь в соответствии с вашими требованиями
web_driver = webdriver.Chrome(executable_path=cm.web_driver, options=option)
@allure.step("Закрытие веб-драйвера")
def fn():
web_driver.quit()
``` @pytest.fixture(scope='function', autouse=True)
def close_quit(self):
process_name = web_cfg.web_driver_process
# Поиск всех процессов ChromeDriver
process_list = [process for process in psutil.process_iter() if process.name() == process_name]
if len(process_list) > 0:
# Если есть несколько запущенных процессов ChromeDriver, завершаем все
for process in process_list:
process.kill()
log.info('Обнаружены процессы ChromeDriver, все они были завершены')
else:
log.info('Процессов ChromeDriver нет')```markdown
request.addfinalizer(fn)
with allure.step("Возврат веб-драйвера"):
return web_driver
@pytest.hookimpl(tryfirst=True, hookwrapper=True)
def pytest_runtest_makereport(item, call):
# Пост-обработка, получение результатов теста
outcome = yield
result = outcome.get_result()
if result.when == 'call' and result.failed:
# При неудаче теста производится скриншот, добавляем его в отчет Allure
screenshot = web_driver.get_screenshot_as_png()
# Название файла для скриншота
filename = '_'.join([result.nodeid.replace('testcases/', '').replace('::', '_'), datetime.strftime(datetime.now(), '%Y%m%d_%H%M%S')])
allure.attach(screenshot, name=filename, attachment_type=allure.attachment_type.PNG)
@pytest.hookimpl(tryfirst=True, hookwrapper=True)
def pytest_runtest_call(item):
# Запись запущенного тест-кейса
case_name = item.nodeid.replace('testcases/', '').replace('::', '_')
log.info('case:%s', case_name)
yield
```
### 6.7 Тестовый сценарий test_xxx.py
Создайте файл `test_xxx.py` в директории `testcases` или её поддиректории, чтобы написать тестовые сценарии, используя методы, определённые в страницах.
Пример:
Создайте файл `test_login.py` в директории `testcases`, содержащий следующий код:
```python
"""
@File :test_login.py
@Author :TJC
@Date :2023-07-21
@Desc :Тестовые сценарии для модуля входа
"""
``````markdown
import allure
import pytest
from common.read_yaml import login_para
from utils.logger import log
from common.common import Common
from page_object.login import PageLogin
@allure.feature("Функциональность входа")
class TestLogin(Common):
"""Тестирование входа"""
@allure.story("Неудачный вход")
@allure.title("Проверка входа -{login_case}")
@allure.description("Тестирование различных ситуаций при входе")
@allure.severity("minor")
@pytest.mark.parametrize("login_case", login_para)
def test_login(self, drivers, login_case):
username = login_case['username']
password = login_case['password']
expect_text = login_case['expect']
log.info(f"Имя пользователя: {username}")
log.info(f"Пароль: {password}")
log.info(f"Ожидаемый текст: {expect_text}")
PageLogin(drivers).page_login(username, password)
with allure.step("Получение сообщения об ошибке"):
error_info = PageLogin(drivers).page_get_error_info()
with allure.step("Проведение проверки"):
assert expect_text in error_info
@allure.story("Успешный вход")
@allure.title("Успешный вход")
@allure.description("Ввод корректного и легального имени пользователя и пароля для успешного входа")
@allure.severity("критический")
def test_login_suc(self, drivers):
"""Необходимо добавить шаги тестирования"""
log.info(drivers.title)
log.info("Ввод корректного имени пользователя и пароля для успешного входа")
### 7 (1), Данные, используемые для привязки, JSON
Формат данных для тестирования не имеет единого стандарта и может использовать формат JSON.
Возвращаемое значение JSON может быть однослойным или многослойным параметром. В этом разделе рассматривается многослойное параметрирование.
```### 7.1 Тестовые данные test_data.json
Создайте файл тестовых данных test_data.json в директории testdata. Пример представлен ниже:
```json
{
"login": [
{
"desc": "Неудачный вход (пользователя нет)",
"username": "700001",
"password": "700001@mcx",
"expect": "Не удалось войти в систему"
},
{
"desc": "Неудачный вход (пустой пароль)",
"username": "700002",
"password": "",
"expect": "Неудача"
},
{
"desc": "Неудачный вход",
"username": "700003",
"password": "700003",
"expect": "Неудача"
}
],
"group_call": [
{
"desc": "Успешный групповой вызов",
"group_id": "80001",
"time": "10",
"expect": "Вызов успешен"
},
{
"desc": "Успешный групповой вызов",
"group_id": "80002",
"time": "10",
"expect": "Вызов успешен"
},
{
"desc": "Успешный групповой вызов",
"group_id": "80003",
"time": "10",
"expect": "Вызов успешен"
}
]
}
```
### 7.2 Чтение файла JSON read_json.py (список кортежей)
Создайте файл read_json.py в директории common для чтения тестовых данных, которые будут использоваться в тестовых случаях. Код представлен ниже:
```python
"""
@File :read_json.py
@Author :TJC
@Date :2023-07-21
@Desc :Чтение тестовых данных в формате JSON
"""
import json
from config.config import cm
class ReadJson:
def __init__(self, json_file):
self.json_file = json_file
``````python
def read_json(self, key):
"""Возвращает список кортежей"""
arr = []
with open(self.json_file, "r", encoding="utf-8") as f:
datas = json.load(f)
value = datas.get(key)
for data in value:
arr.append(tuple(data.values())[1:])
return arr
```
```markdown
json_para = ReadJson(cm.test_json_file)
# Чтение соответствующих модулей из JSON файла, каждый модуль — один экземпляр
login_para = json_para.read_json('login')
group_call_para = json_para.read_json('group_call')
```# Получаем список кортежей в следующем формате:
# [('700001', '700001', 'Успешный вход'), ('700002', '', 'Неудачный вход, пароль не может быть пустым'), ('700003', '700003', 'Успешный вход')]
```
### 7.3. Тестовый пример test_xxx.py (множественные параметры)
Вышеупомянутый метод чтения данных из JSON возвращает список кортежей, используемый для множественного параметризованного тестирования.
Пример:
Создайте файл `test_login.py` в директории `testcases`, содержащий следующий код:
```python
"""
@File :test_login.py
@Author :TJC
@Date :2023-07-21
@Desc :Тестовые случаи для модуля входа
"""
import allure
import pytest
from common.read_json import login_para
from utils.logger import log
from common.common import Common
from utils.timer import sleep
from page_object.page_login import PageLogin
@allure.feature("Функционал входа")
class TestLogin(Common):
"""Тестирование входа"""
@allure.story("Неудачный вход")
@allure.title("Обнаружение неудачного входа - {username} - {password} - {expect_text}")
@allure.description("Тестирование различных ситуаций при неудачном входе")
@allure.severity("minor")
@pytest.mark.parametrize("username, password, expect_text", login_para)
# Множественное параметризованное тестирование, параметры можно использовать напрямую,
# но все параметры должны быть указаны в parametrize и тестовой функции, а также должны совпадать по порядку
def test_login(self, drivers, username, password, expect_text):
PageLogin(drivers).page_login(username, password)
with allure.step("Получение сообщения об ошибке"):
error_info = PageLogin(drivers).page_get_error_info()
with allure.step("Начало проверки"):
assert expect_text in error_info
```## 7.2 Данные для тестирования yaml
Формат тестовых данных не стандартизирован, можно использовать YAML.
В этом разделе рассматриваются однопараметрическое и многопараметрическое параметризованное тестирование.
### 7.1 Тестовые данные test_data.yaml
Создайте файл с тестовыми данными `test_data.yaml` в директории `testdata`, как показано ниже:
```yaml
login:
- case1: Успешный вход 1
username: 70001
password: 7001@mcx
expect: Неверный пароль
- case2: Успешный вход 2
username: 70002
password:
expect: Пароль не может быть пустым
- case3: Успешный вход 3
username: 70003
password: 70003
expect: Успешный вход
```
### 7.2 Чтение файла YAML read_yaml.py — одиночный параметр - список словарей
В директории common создайте новый файл read_yaml.py для чтения тестовых данных, используемых в тестовых сценариях. Код представлен ниже:
```python
"""
@File :read_yaml.py
@Author :TJC
@Date :2023-07-21
@Desc :Чтение тестовых данных в формате YAML, возвращаемое значение — список словарей
"""
import yaml
from config.config import cm
class ReadYaml:
def __init__(self, yaml_file):
self.yaml_file = yaml_file
def read_yaml(self, key):
with open(self.yaml_file, 'r', encoding='utf-8') as f:
datas = yaml.safe_load(f) # Предотвратите вывод предупреждений, добавив Loader=yaml.FullLoader
value = datas[key]
return value
yaml_para = ReadYaml(cm.test_yaml_file)
login_para = yaml_para.read_yaml('login')
"""
Возвращает список словарей
[
{'case1': 'Успешный вход 1', 'username': 70001, 'password': '7001@mcx', 'expect': 'Неверный пароль'},
{'case2': 'Успешный вход 2', 'username': 70002, 'password': None, 'expect': 'Пароль не может быть пустым'},
{'case3': 'Успешный вход 3', 'username': 70003, 'password': 70003, 'expect': 'Успешный вход'}
]
"""
```### 7.3 Тестовый сценарий test_xxx.py — одиночный параметр параметризации
Вышеупомянутый метод чтения данных из YAML возвращает список словарей. Используется одиночный параметр для управления.
Пример:
Создайте файл test_login.py в директории testcases, код представлен ниже:
```python
"""
@File :test_login.py
@Author :TJC
@Date :2023-07-21
@Desc :Тестовые сценарии для модуля входа
"""
import allure
import pytest
from common.read_yaml import login_para
from utils.logger import log
from common.common import Common
from utils.timer import sleep
from page_object.page_login import PageLogin
@allure.feature("Функция входа")
class TestLogin(Common):
"""Тест входа"""
@allure.story("Неудачный вход")
@allure.title("Проверка входа -{login_case}")
@allure.description("Тестирование различных исключений при входе")
@allure.severity("minor")
@pytest.mark.parametrize("login_case", login_para)
def test_login(self, drivers, login_case):
username = login_case['username']
password = login_case['password']
expect_text = login_case['expect']
log.info(f"Имя пользователя: {username}")
log.info(f"Пароль: {password}")
log.info(f"Ожидаемый текст: {expect_text}")
```
```python
PageLogin(drivers).page_login(username, password)
with allure.step("Получение сообщения об ошибке"):
error_info = PageLogin(drivers).page_get_error_info()
with allure.step("Начало проверки"):
assert expect_text in error_info
```
### Yöntem YAML dosyasından veri okuma read_yaml.py (çoklu parametreler - liste tuple'ları)
Yukarıda belirtilen dosya `read_yaml.py` oluşturun ve bu dosya JSON ile benzer şekilde liste tuple'ları döndürmelidir. Kod aşağıdaki gibidir:```python
"""
@File :read_yaml.py
@Author :TJC
@Date :2023-07-21
@Desc :Чтение данных тестов в формате YAML
"""
import yaml
from config.config import cm
class ReadYaml:
def __init__(self, yaml_file):
self.yaml_file = yaml_file
def read_yaml(self, key):
arr = []
with open(self.yaml_file, 'r', encoding='utf-8') as f:
datas = yaml.safe_load(f) # Избегаем вывода предупреждений, используя yaml.safe_load
value = datas[key]
for data in value:
arr.append(tuple(data.values())[1:])
return arr
yaml_para = ReadYaml(cm.test_yaml_file)
login_para = yaml_para.read_yaml('login')
```
### 7.5 Тестовый случай test_xxx.py (множественные параметры - параметризация)
Аналогично разделу 7.3, вам нужно будет изменить импорт:
```python
from common.read_yaml import login_para
```
### 7.6 Сравнение одиночных и множественных параметров
**1. Декоратор @pytest.mark.parametrize**
Одиночный параметр, использует один параметр и передает одну переменную.
```python
@pytest.mark.parametrize("login_case", login_para)
```
Множественные параметры, используют несколько параметров и должны гарантировать последовательность параметров, соответствующую порядку значений в переменной `login_para`.
```python
@pytest.mark.parametrize("username, password, expect_text", login_para)
```
**2. Функция def test_login параметры**
Одиночный параметр, использует параметризацию parametrize с одним параметром.
```python
def test_login(self, drivers, login_case):
```
Множественные параметры, используют параметризацию parametrize с несколькими параметрами.
```python
def test_login(self, drivers, username, password, expect_text):
```**3. Декоратор allure @allure.title**
Для отображения параметризованных данных в Allure, параметризованные данные должны быть помещены в заголовок.
Одиночный параметр использует параметризацию parametrize с одним параметром.
```python
@allure.title("Проверка входа - {login_case}")
```
Множественные параметры используют параметризацию parametrize с несколькими параметрами.
```python
@allure.title("Проверка входа - {username}, {password}, {expect_text}")
```
```python
@allure.title("Логин异常检测-{username}-{password}-{expect_text}")
```
**4. Параметры использования**
Однопараметрические требуют считывания значения каждого параметра в функции и, так как логи не показывают параметры, требуется выводить параметры в лог.
```python
username = login_case['username']
password = login_case['password']
expect_text = login_case['expect']
log.info(f"username:{username}")
log.info(f"password: {password}")
log.info(f"expect text: {expect_text}")
```
Многопараметрические используются прямо в виде параметров, без необходимости считывания или вывода.
**5. Логирование**
Два способа различаются при печати имени тестового случая.
Однопараметрические, примеры отпечатанных данных:
```python
case: test_login.py_TestLogin_test_login[login_case0]
username:70001
password: 7001@mcx
expect text: неудача
case: test_login.py_TestLogin_test_login[login_case1]
username:70002
password: None
expect text: пароль
```
Имя тестового случая в логах состоит из имени функции, значения параметров и номера, начиная с нуля.
Поэтому рекомендуется использовать этот метод и выводить соответствующие параметры в лог для удобства отладки.Многопараметрические, примеры отпечатанных данных:
```python
случай: test_login.py_TestLogin_test_login[700001-700001@mcx-неудачный вход и регистрация]
случай: test_login.py_TestLogin_test_login[700002-неудача]
случай: test_login.py_TestLogin_test_login[700003-700003-неудача]
```
Имя тестового случая в логах состоит из имени функции и значений параметров, поэтому нет необходимости выводить параметры в лог.
**6. Отображение Allure**
Однопараметрические, нерегулярные заголовки тестовых случаев, трудночитаемые параметры, как показано ниже:
Скриншоты неудачных тестовых случаев не показывают детали параметров, как показано ниже:
Многопараметрические, регулярные заголовки тестовых случаев, четко читаемые параметры, как показано ниже:
Скриншоты неудачных тестовых случаев показывают тестовые параметры, как показано ниже:
На основе вышеуказанного сравнения выберите подходящий формат тестовых данных и метод параметризации для реализации данных-драйвинга.
## 8. Allure отчет
### 8.1. Содержание отчета allure_des
В папке allure_des создайте файл allure_des.py, используемый для создания информации об окружении, генераторе и статистике отчета, код следующий:
```
"""
@File :allure_des.py
@Author :TJC
@Date :2023-07-21
@Desc :информация описания отчета Allure, каждый запуск создает заново
"""
``````python
import json
import os
import platform
import pytest
from config.config import cm
```
```markdown
def set_report_env_on_results():
"""
Создает файл с информацией о среде выполнения: environment.properties (обратите внимание: в файле не должно быть кириллицы, чтобы избежать проблем с отображением).
"""
# Ваши данные о среде выполнения, заполните их согласно вашему проекту
allure_env = {
'OperatingEnvironment': 'mcx DC',
'PythonVersion': platform.python_version(),
'PytestVersion': pytest.__version__,
'Platform': platform.platform(),
'selenium': '3.141.0',
'Browser': 'Chrome',
'BrowserVersion': '59.0.3071.115',
'DriverVersion': '2.32 MCX-driver'
}
```
allure_env_file = os.path.join(cm.dir_report_json, 'environment.properties')
with open(allure_env_file, 'w', encoding='utf-8') as f:
for _k, _v in allure_env.items():
f.write(f'{_k}={_v}\n')
```def set_report_executer_on_results():
"""
Создает файл executor.json с информацией о выполнителе в директории json-отчета.
"""
# Информация о выполнителе
allure_executor = {
"name": "Иванов Иван",
"type": "jenkins",
"url": "http://helloqa.com", # адрес отчета Allure
"buildOrder": 3,
"buildName": "allure-report_deploy#1",
"buildUrl": "http://helloqa.com#1",
"reportUrl": "http://helloqa.com#1/AllureReport",
"reportName": "Отчет Allure Иванова Ивана"
}
allure_env_file = os.path.join(cm.dir_report_json, 'executor.json')
with open(allure_env_file, 'w', encoding='utf-8') as f:
f.write(str(json.dumps(allure_executor, ensure_ascii=False, indent=4)))
def set_report_categories_on_results():
"""
Создает файл categories.json с информацией о категориях тестов в директории json-отчета.
"""
# Информация о категориях тестов
allure_categories = [
{
"name": "Пропущенные тесты",
"matchedStatuses": ["skipped"]
},
{
"name": "Проблемы инфраструктуры",
"matchedStatuses": ["broken", "failed"],
"messageRegex": ".*bye-bye.*"
},
{
"name": "Устаревшие тесты",
"matchedStatuses": ["broken"],
"traceRegex": ".*FileNotFoundException.*"
},
{
"name": "Ошибки продукта",
"matchedStatuses": ["failed"]
},
{
"name": "Ошибки тестирования",
"matchedStatuses": ["broken"]
}
]
allure_cat_file = os.path.join(cm.dir_report_json, 'categories.json')
with open(allure_cat_file, 'w', encoding='utf-8') as f:
f.write(str(json.dumps(allure_categories, ensure_ascii=False, indent=4)))## 9. Запуск pytest
### 9.1. Файл pytest.ini
В корневой директории проекта создайте файл pytest.ini для хранения параметров выполнения тестов. Содержимое файла:
```ini
[pytest]
addopts = --tb=short -v
```
### 9.2. Запуск тестов
Для запуска тестов используйте команду:
```bash
pytest
```
Это запустит все тесты, найденные в текущем каталоге и всех его подкаталогах.```markdown
# pytest.ini
[pytest]
disable_test_id_escaping_and_forfeit_all_rights_to_community_support = True
addopts = -vs --clean-alluredir
testpaths = ./testcases
python_files = test*.py
python_classes = Test*
python_functions = test_*
```
### 9.2. Файл runtest.py
В корневой директории проекта создайте файл runtest.py, который будет служить точкой входа для запуска тестов. Код файла:
```python
"""
@File : runtest.py
@Author : TJC
@Date : 2023-07-21
@Desc : Точка входа для запуска тестовых случаев
"""
import os
import pytest
from allure_des.allure_des import set_report_env_on_results, \
set_report_executer_on_results, \
set_report_categories_on_results
from config.config import cm
from utils.timer import sleep
def run():
pytest.main(['--allure-stories=Успешный вход,Неудачный вход', '--alluredir=%s' % cm.dir_report_json])
# Создание файла categories.json в директории json
set_report_categories_on_results()
# Создание файла environment.properties в директории json
set_report_env_on_results()
# Создание файла executor.json в директории json
set_report_executer_on_results()
sleep(3)
os.system("allure generate %s -o %s --clean" % (cm.dir_report_json, cm.dir_report_html))
if __name__ == '__main__':
run()
```
---------------------- Конец. Удачи!
```
Вы можете оставить комментарий после Вход в систему
Неприемлемый контент может быть отображен здесь и не будет показан на странице. Вы можете проверить и изменить его с помощью соответствующей функции редактирования.
Если вы подтверждаете, что содержание не содержит непристойной лексики/перенаправления на рекламу/насилия/вульгарной порнографии/нарушений/пиратства/ложного/незначительного или незаконного контента, связанного с национальными законами и предписаниями, вы можете нажать «Отправить» для подачи апелляции, и мы обработаем ее как можно скорее.
Комментарии ( 0 )