Py.test - Применение переменных к декораторам из CSV?
Пожалуйста, потерпите меня, пока я пытаюсь объяснить свое затруднительное положение, я все еще новичок в Python, поэтому моя терминология может быть неверной. Также я прошу прощения за неизбежность многословности этого поста, но я постараюсь изложить как можно больше соответствующих деталей.
Краткое краткое изложение:
В настоящее время я разрабатываю набор тестов Selenium для набора веб-сайтов, которые по сути одинаковы по функциональности, используя py.test
Результаты тестов загружаются в TestRail с использованием плагина https://pypi.org/project/pytest-testrail/.
Тесты помечаются с помощью decorator @ pytestrail.case (id) с уникальным идентификатором дела
Типичный мой тест выглядит так:
@pytestrail.case('C100123') # associates the function with the relevant TR case
@pytest.mark.usefixtures()
def test_login():
# test code goes here
Как я упоминал ранее, я намерен создать один набор кода, который будет обрабатывать несколько наших веб-сайтов с (практически) идентичной функциональностью, поэтому жестко закодированный декоратор в приведенном выше примере не будет работать.
Я попробовал управляемый данными подход с csv и списком тестов и их идентификаторами в TestRail.
Пример:
website1.csv:
Case ID | Test name
C100123 | test_login
website2.csv:
Case ID | Test name
C222123 | test_login
Код, который я написал, будет использовать модуль inspect, чтобы найти имя выполняемого теста, найти соответствующий идентификатор теста и поместить его в переменную с именем test_id:
import csv
import inspect
class trp(object):
def __init__(self):
pass
with open(testcsv) as f: # testcsv could be website1.csv or website2.csv
reader = csv.reader(f)
next(reader) # skip header
tests = [r for r in reader]
def gettestcase(self):
self.current_test = inspect.stack()[3][3]
for row in trp.tests:
if self.current_test == row[2]:
self.test_id = (row[0])
print(self.test_id)
return self.test_id, self.current_test
def gettestid(self):
self.gettestcase()
Идея заключалась в том, что декоратор будет динамически меняться в зависимости от CSV, который я использовал в то время.
@pytestrail.case(test_id) # now a variable
@pytest.mark.usefixtures()
def test_login():
trp.gettestid()
# test code goes here
Так что если бы я запустил test_login для website1, декоратор выглядел бы так:
@pytestrail.case('C100123')
и если бы я запустил test_login для website2, декоратор был бы:
@pytestrail.case('C222123')
Я очень гордился тем, что смог придумать это решение сам, и попробовал его... оно не сработало. Хотя код работает сам по себе, я бы получил исключение, потому что test_id не определен (я понимаю, почему - gettestcase
выполняется после декоратора, поэтому, конечно, он потерпит крах.
Единственный способ справиться с этим - применить csv и testID перед выполнением любого тестового кода. У меня вопрос - откуда мне знать, как связать тесты с их идентификаторами? Каким было бы элегантное, минимальное решение для этого?
Извините за длинный вопрос. Я буду внимательно следить, чтобы ответить на любые вопросы, если вам нужно больше объяснений.
2 ответа
pytest
очень хорошо выполняет все виды метапрограммирования для тестов. Если я правильно понимаю ваш вопрос, приведенный ниже код выполнит динамические тесты, помеченные pytestrail.case
маркер. В корневом каталоге проекта создайте файл с именем conftest.py
и поместите в него этот код:
import csv
from pytest_testrail.plugin import pytestrail
with open('website1.csv') as f:
reader = csv.reader(f)
next(reader)
tests = [r for r in reader]
def pytest_collection_modifyitems(items):
for item in items:
for testid, testname in tests:
if item.name == testname:
item.add_marker(pytestrail.case(testid))
Теперь вам не нужно отмечать тест @pytestrail.case()
на всех - просто напишите остаток кода и pytest
позаботится о маркировке:
def test_login():
assert True
когда pytest
начинается, код выше будет читать website1.csv
и сохраните идентификаторы и имена тестов так же, как вы делали это в своем коде. Перед началом тестового прогона, pytest_collection_modifyitems
hook выполнится, проанализировав собранные тесты - если тест имеет то же имя, что и в CSV-файле, pytest
добавит pytestrail.case
маркер с идентификатором теста к нему.
Я полагаю, что причина, по которой это не работает, как вы ожидаете, связана с тем, как python читает и выполняет файлы. Когда python начинает выполняться, он считывает связанные файлы Python и выполняет каждую строку по очереди, по очереди. Для вещей на уровне корневого отступа (определения функций / классов, декораторы, назначения переменных и т. Д.) Это означает, что они запускаются ровно один раз при загрузке и никогда больше. В вашем случае интерпретатор python считывает в декораторе pytest-testrail, затем в декораторе pytest и, наконец, в определении функции, выполняя каждое из них раз и навсегда.
(Заметьте, именно поэтому вы никогда не должны использовать изменяемые объекты в качестве значений по умолчанию для аргументов функции: общие ошибки)
Учитывая, что вы хотите сначала определить текущее имя теста, затем связать его с идентификатором теста и, наконец, использовать этот идентификатор с декоратором, я не уверен, что это возможно с текущей функциональностью pytest-testrail. По крайней мере, невозможно без некоторого эзотерического и трудного для отладки / поддержки взлома с использованием дескрипторов или тому подобного.
Я думаю, что у вас есть один вариант: использовать другой клиент TestRail и обновить структуру pytest, чтобы использовать новый клиент. Два клиента Python, которые я могу порекомендовать, это testrail-python и TRAW (TestRail Api Wrapper)(*)
С вашей стороны потребуется больше усилий для создания приспособлений для запуска прогона, обновления результатов и закрытия прогона, но я думаю, что в итоге у вас будет более переносимый набор тестов и улучшенная отчетность о результатах.
(*) полное раскрытие: я являюсь создателем / сопровождающим TRAW, а также внес значительный вклад в testrail-python