-
Notifications
You must be signed in to change notification settings - Fork 17
Description
Описываемый в данном баг репорте недостаток системы и вытекающие из него эксплуатационные сложности будут продемонстрированы на примере использования адаптера к пайтест https://github.com/testit-tms/adapters-python/tree/main/testit-adapter-pytest. При этом следует учитывать, что проблема затрагивает как бекэнд, так и все имеющиеся адаптеры.
В общем случае связь автотестов с тесткейсами (далее ТК) соответствует соотношению многие к многим. Соотвественно для реализации такой схемы используются два признака, уникальный для ТК workitemId и уникальный для автотеста externalId. У одного автотеста может быть указан список из произвольного количества workitemId, у ТК - список из externalId. При создании нового автотеста, покрывающего проверки уже имеющегося в системе ТК с известным значением workitemId, необходимо руками навесить этот признак на автотест для определения желаемой связи с ТК, то есть никакой подкапотной машинерии в данном случае адаптером не предусмотрено. Далее чтобы позволить системе отличать один автотест от других, одновременно прилинкованных к конкретному ТК, необходимо чтобы на каждом автотесте присутствовал признак externalId с уникальным значением Аналогично workitemId имеется возможно навесить произвольный externalId на автотест руками, а так же в адаптере имеется машинерия автоматически расставляющая externalId на те автотесты, где данный признак не установлен вручную. Данная функция реализована следующим образом:
for item in items:
if hasattr(item.function, 'test_external_id'):
item.test_external_id = item.function.test_external_id
else:
item.test_external_id = utils.get_hash(item.parent.nodeid + item.function.__name__)Из данного кода видно, что для системы ТестИТ финальной атомарной сущностью автотеста является тестовая функция. Я считаю, что это неверно, потому как тестовая функция - это всего лишь синтаксическая конструкция языка. Пайтест никак не ограничивает тестописателя соотношением 1 тестовая функция <-> 1 автотест. Более того, пайтест мотивирует создавать параметризованные автотесты с помощью встроенного и чрезвычайно популярного декоратора @pytest.mark.parametrize https://docs.pytest.org/en/stable/how-to/parametrize.html. То есть совершенно нормально, обыденно и даже желательно описывать близкую по смыслу группу автотестов одной тестовой функцией.
Давайте теперь рассмотрим на примерах, как ТестИТ работает с параметризованными тестами и какие проблемы возникают в виду его ограничения.
class TestDebug:
@workItemIds("228185")
@pytest.mark.parametrize(
"param1, param2, display_name",
[
(
"value1_1",
"value2_1",
"Отображаемое имя 1",
),
(
"value1_2",
"value2_2",
"Отображаемое имя 2",
),
],
)
@displayName("{display_name}")
def test_debug(
self,
param1,
param2,
display_name
):
with step("тестовый шаг 1"):
passС точки зрения пайтеста, это 2 отдельных автотеста, которые при запуске будут выполнены порознь. На этапе сбора {коллектинга} айтемов (автотестов в терминологии пайтеста) в список items будут помещены два дубликата тестовой функции test_debug c различными значениями аргументов, согласно наборам тестовых данных из агрументов @pytest.mark.parametrize.
При этом обоим айтемам адаптер ТестИТ навесит один и тот же test_external_id, в следствии чего мы можем наблюдать следующие побочные эффекты:
- В прогоне у всех автотестов будет один и тот же display_name, в частности соотвествующий последнему выполненному автотесту, то есть в данном примере "Отображаемое имя 2".

(Влияет на читабельность репорта, уже были жалобы от ручников)
- На странице "Автотесты" (/autotests) будет неправильное количество автотестов (1 вместо 2)

(Влияет на всю статистику самого тест айти и запусков)
Мы попробовали исправить ситуацию на своей стороне. Навесим на каждый айтем отдельный test_external_id в обход адаптера. Для этого в конфтесте определим хук-функцию:
@hookimpl(tryfirst=True)
def pytest_collection_modifyitems(session, config, items):
for item in items:
__hash = get_sha256(item.nodeid)
# parameterized tests
if hasattr(item, "callspec"):
if not hasattr(item.function, "test_external_id"):
item.function.test_external_id = "{testit_external_id}"
item.callspec.params["testit_external_id"] = __hash
# non-parameterized tests
else:
if not hasattr(item.function, "test_external_id"):
item.function.test_external_id = __hash
logger.debug('externalId applied for node "%s": "%s".', item.nodeid, __hash)- пояснение, данный хук вместо хеша от
item.parent.nodeid + item.function.__name__, как делает под капотом адаптер, навестит на айтемы хеш отitem.nodeid, в который входит постфиксом набор уникальных параметров, указанных в декораторе параметрайз.
Удаляем автотест привязанный к ТК на прошлом запуске и запускаем автотесты с проливкой репорта заново:
Получаем в результате пофишеными обе проблемы, приведенные выше.
- В прогоне у автотестов правильные отображаемые имена
- На станице автотестов имеем в списке 2 автотеста и соотвественно правильное количество в счетчике
И казалось бы можно праздновать победу, но не стоит спешить - к сожалению, мы поломали внутреннюю логику работы системы, что привело к новым еще более серьезным последствиям.
Рассмотрим такой момент времени в процессе тестирования, когда тесткейсы для проекта уже написаны, а часть ТК даже автоматизирована и тест-менеджер приступает к планирования предстоящего регресса с помощью ТМС ТестИТ. На этом этапе он создает новый тесплан в разделе "Тест-планы" (/test-plans).
Добавляет в него набор тесты, которые требуется выполнить в данной итерации.
На этом месте следует сделать оговорку, что для правильной работы данного функционала, как задумано проектировщиками ТестИТ, мы предварительно внесли в ТК информацию о его наборах параметров. (Кстати, чтобы избавиться от ручной рутины, можно заполнять тесткейсы в ТМС параметрами из автотестов с помощью плагина https://pypi.org/project/pytest-testit-parametrize/)
Благодаря таблице параметров в ТК с двумя наборами тестовых данных, после добавления одного ТК (с номером 228185) в набор, там оказалось 2 теста.
Оба теста автоматизированные (помеченные иконкой ракеты), поэтому тест-менеджер решает во вкладке "Выполнение" не подключать для их прогона ручных тестировщиков, а выполнить соотвествующие им автотесты, для чего выделяет оба теста и нажимает кнопку "Запустить автотесты".
После запуска ТестИТ создал прогон
Перейдя в прогон, мы можем наблюдать, что произошло на беке:

Их стало 4. Ожидаемое количество автотестов размножилось.
Откроим каждый из автотестов, чтобы сверить значения параметров:
Во вкладке "Запуски автотестов" раздела "Тест-планы":
Если развернуть запуск и все его дропдаун листы тоже видим, что ожидается 4 автотеста:
После выполнения прогона только часть, в данном случае половина, ожидаемых тестов получит результат прохождения. Остальные останутся "крутиться". Соответственно все параметризованные тесты в наборе остануться в статусе результата "В процессе" навечно, равно как и сам запуск. Запуск можно принудительно завершить. Но тогда при большом количестве тестов в наборе, как обычно бывает в реальной жизни, получается такая мешанина дублирующихся результатов, что подобная попытка использовать функционал ТМС не валидна.
При учете данных проблем, использование ТМС для автоматизированного тестирования является очень ограниченным в функционале. В наших глазах, исправление данных проблем повысит ценность данной ТМС.














