четверг, 20 июля 2017 г.

PyTest: красивые имена тестов в репортиках

Последнее время довольно много приходится работать с PyTest, и несколько месяцев назад я написал небольшой кусочек кода, который позволяет переопределять имена тест кейсов через doc strings к ним.

Зачем это нужно?

Когда показываешь отчет о проделанном тестировании человеку, который не занимается запуском и написанием тестов, то имена тестовых сценариев им не говорят ни о чем, и выглядит такой отчет очень неаккуратно (он же автосгенерирован на основе кода).

Еще одним минусом является то, что очень сложно определить по такому отчету то, какие кейсы уже покрыты, а какие еще нет - так и хочется залезть в код функции теста, чтобы в этом разобраться.
Когда же у нас есть возможность описать тестовый сценарий в подробном doc string применяя обычные предложения на английском - у нас есть полная свобода в описании тестового сценария, и это описание можно импортировать в чек листы, в отчеты, в баг репорты и куда угодно еще.

Как было - список тестовых сценариев (пример запуска pytest --collect-only):

Как стало - список тестовых сценариев (пример запуска pytest --collect-only):

Имена тестов берутся из doc strings, пример тест кейса:
def test_get_limit():
""" Сheck that we have right limit. """

  r = requests.get(ENDPOINTS['get_limit'])
data = r.json()

assert data['result']['limit'] == MAX_LIMIT
В данном случае строка """ Сheck that we have right limit. """ будет взята в качестве имени для теста, которое будет отображаться в списке тестов и в тестовом отчете.

Наверняка, будет полезно не только мне :)

Вся магия помещается в файлике conftest.py в корне папки с тестами, с таким содержимым:
import pytest


def get_test_case_docstring(item):
""" This function gets doc string from test case and format it
to show this docstring instead of the test case name in reports.
"""

param_str = 'long string ({0} characters) started from "{1}"'

if item._obj.__doc__:
# Remove extra whitespaces from the doc string:
name = item._obj.__doc__.strip()
full_name = ' '.join(name.split())

# Generate the list of parameters for parametrized test cases:
if hasattr(item, 'callspec'):
params = item.callspec.params

if params:
for key in params:
param = str(params[key])
param_len = len(param)

# If value of some parameter is too long to show it in
# reports we should replace this value with some default
# string to make sure the report will looks good:
if param_len > 50:
params[key] = param_str.format(param_len, param[:10])

# Add dict with all parameters to the name of test case:
full_name += ' Parameters: ' + str(params)

return full_name


def pytest_itemcollected(item):
""" This function modifies names of test cases "on the fly"
during the execution of test cases.
"""

if item._obj.__doc__:
item._nodeid = get_test_case_docstring(item)


def pytest_collection_finish(session):
""" This function modified names of test cases "on the fly"
when we are using --collect-only parameter for pytest
(to get the full list of all existing test cases).
"""

if session.config.option.collectonly is True:
for item in session.items:
# If test case has a doc string we need to modify it's name to
# it's doc string to show human-readable reports and to
# automatically import test cases to test management system.
if item._obj.__doc__:
full_name = get_test_case_docstring(item)
print(full_name)

pytest.exit('Done!')

Исходный код вместе с тестами можно найти здесь:
https://github.com/TimurNurlygayanov/rutracker-integration-tests