Столкнулся с задачей модификации html-отчета при работе с pytest, в результате чего нашёл удобное для своей задачи решение, хочу им поделиться — возможно кому-то пригодится.


* Все картинки кликабельны

Для работы необходимы следующие компоненты:
* python3 ( установка Linux: apt-get install python3 ) — поддержка языка Python
* pip3 ( установка Linux: apt-get install python3-pip ) — менеджер пакетов для Python (можно использовать любой другой)
* pytest ( установка Linux: pip3 install pytest ) — framework для тестирования
* pytest-html ( установка Linux: pip3 install pytest-html ) — плагин pytest для генерации html-отчетов

Структура проекта:
|- test.py ( Основной тестовый сценарий )
|- conftest.py ( Локальный плагин в котором реализуются hook-сценарии )
|- test-1.jpg ( Файл с картинкой )
|- test-2.jpg ( Файл с картинкой )

Содержимое test.py:
import pytest
import base64

# глобальные переменные
log = { 'a':'none', 'b':'none', 'sum':'none' }
log_html = 'none'
log_img = 'none'
log_img_url = 'none'

# Функция переназначения глобальных переменных
def set_global_var(a='none',b='none',sum_='none',html='none',img='none',img_url='none'):
	global log, log_html, log_img, log_img_url
	log = { 'a': a, 'b': b, 'sum': sum_ }
	log_html = html
	log_img = img
	log_img_url = img_url

# Фикстура, которая автоматически, перед каждым тестом будет сбрасывать значения глобальных переменных
@pytest.fixture(scope="function", autouse=True)
def default_global_var():
	set_global_var()

def test_default():
	'''Тест с параметрами по умолчанию'''
	assert True

def test_1():
	'''Первый тест'''
	# Расчет суммы
	a, b = 2, 2
	sum_ = a + b
	# Присваивание значений глобальным переменным
	set_global_var( a = a, b = b, sum_ = sum_ )
	# Проверка
	assert sum_ == 4

def test_2():
	'''Второй тест'''
	# Расчет суммы
	a, b = 3, 3
	sum_ = a + b
	# Добавляем html блок
	html = '''
	<p>
	  <table border="3" width="100%">
	  <tbody>
	    <tr>
	      <td bgcolor="#D3D3D3"><font color="black"><strong>a</strong></font></td>
	      <td bgcolor="#D3D3D3"><font color="black"><strong>b</strong></font></td>
	      <td bgcolor="#D3D3D3"><font color="black"><strong>sum</strong></font></td>
	    </tr>
	    <tr>
	      <td><font color="black">{0}</font></td>
	      <td><font color="black">{1}</font></td>
	      <td><font color="black">{2}</font></td>
	    </tr>
	  </tbody>
	  </table>
	  </p>
	  '''.format( a, b, sum_ )
	# Присваивание значений глобальным переменным
	set_global_var( a = a, b = b, sum_ = sum_, html = html )
	# Проверка
	assert sum_ == 6

def test_3():
	'''Третий тест'''
	# Расчет суммы
	a, b = 4, 4
	sum_ = a + b
	# Добавляем картинку в формате base64
	image = open('test-1.jpg', 'rb')
	image_read = image.read()
	img = base64.encodebytes( image_read ).decode("utf-8")
	# Присваивание значений глобальным переменным
	set_global_var( a = a, b = b, sum_ = sum_, img = img )
	# Проверка
	assert sum_ == 8

def test_4():
	'''Четвертый тест'''
	# Расчет суммы
	a, b = 5, 5
	sum_ = a + b
	# Добавляем картинку по url
	img_url = 'test-2.jpg'
	# Присваивание значений глобальным переменным
	set_global_var( a = a, b = b, sum_ = sum_, img_url = img_url )
	# Проверка
	assert sum_ == 10

def test_5():
	'''Пятый тест'''
	# Расчет суммы
	a, b = 5, 1
	sum_ = a + b
	# Добавляем html блок
	html = '<h1>Произвольный HTML-блок</h1>'
	# Добавляем картинку в формате base64
	image = open('test-1.jpg', 'rb')
	image_read = image.read()
	img = base64.encodebytes( image_read ).decode("utf-8")
	# Добавляем картинку по url
	img_url = 'test-2.jpg'
	# Присваивание значений глобальным переменным
	set_global_var( a = a, b = b, sum_ = sum_, html = html, img = img, img_url = img_url )
	# Проверка
	print ('Дополнительная информация о выполнении теста...')
	assert sum_ == 6

# Запускаем серию тестов
@pytest.mark.parametrize("test_a, test_b, test_sum", [(1,2,3), (2,3,5), (4,5,8)])
def test_6(test_a, test_b, test_sum):
	'''Серия шестого теста'''
	# Расчет суммы
	a, b = test_a, test_b
	sum_ = a + b
	# Присваивание значений глобальным переменным
	set_global_var( a = a, b = b, sum_ = sum_ )
	# Проверка
	assert sum_ == test_sum
 


Содержимое conftest.py:
import pytest
from py.xml import html

# Создание дополнительных столбцов
def pytest_html_results_table_header(cells):
	cells.insert(1, html.th('description'))		# Заголовок 1-го столбца
	cells.insert(2, html.th('a'))			# Заголовок 2-го столбца
	cells.insert(3, html.th('b'))			# Заголовок 3-го столбца
	cells.insert(4, html.th('sum'))			# Заголовок 4-го столбца
	cells.pop()

def pytest_html_results_table_row(report, cells):
	cells.insert(1, html.td( report.description ))	# Содержимое 1-го столбца для конкретного теста
	cells.insert(2, html.td( report.a ))		# Содержимое 2-го столбца для конкретного теста
	cells.insert(3, html.td( report.b ))		# Содержимое 3-го столбца для конкретного теста
	cells.insert(4, html.td( report.sum ))		# Содержимое 4-го столбца для конкретного теста
	cells.pop()

# hook для перехвата и модификации данных результатов тестов
@pytest.hookimpl(hookwrapper=True)
def pytest_runtest_makereport(item, call):
	pytest_html = item.config.pluginmanager.getplugin('html')
	outcome = yield
	report = outcome.get_result()
	# Добавление значений в таблицу - значения берем из атрибута __doc__ функции и глобальных переменных
	report.description = str( item.function.__doc__ )
	report.a = str( item.module.log['a'] )
	report.b = str( item.module.log['b'] )
	report.sum = str( item.module.log['sum'] )
	# Добавление html и image блоков в результат - значения берем из глобальных переменных
	extra = getattr(report, 'extra', [])
	if report.when == 'call':
		# вставляем html-блок
		if item.module.log_html != 'none':
			extra.append(pytest_html.extras.html( item.module.log_html ))
		# вставляем img-блок (картинка будет встроена в отчет)
		if item.module.log_img != 'none':
			extra.append(pytest_html.extras.image( item.module.log_img, mime_type='image/jpg', extension='jpg' ))
		# вставляем img-блок с url-ссылкой
		if item.module.log_img_url != 'none':
			extra.append(pytest_html.extras.image( item.module.log_img_url ))
		report.extra = extra


Модификация отчета производиться в файле conftest.py


Создание новых столбцов выполняется через функции: pytest_html_results_table_header(cells) и pytest_html_results_table_row(report, cells):

Код
# Создание дополнительных столбцов
def pytest_html_results_table_header(cells):
	cells.insert(1, html.th('description'))		# Заголовок 1-го столбца
	cells.insert(2, html.th('a'))			# Заголовок 2-го столбца
	cells.insert(3, html.th('b'))			# Заголовок 3-го столбца
	cells.insert(4, html.th('sum'))			# Заголовок 4-го столбца
	cells.pop()

def pytest_html_results_table_row(report, cells):
	cells.insert(1, html.td( report.description ))	# Содержимое 1-го столбца для конкретного теста
	cells.insert(2, html.td( report.a ))		# Содержимое 2-го столбца для конкретного теста
	cells.insert(3, html.td( report.b ))		# Содержимое 3-го столбца для конкретного теста
	cells.insert(4, html.td( report.sum ))		# Содержимое 4-го столбца для конкретного теста
	cells.pop()


  • cells.insert( _number, html.th( _name )) — производится вставка столбца в таблицу под _number с содержимым _name

Перехват выполнения теста и заполнения отчета по тесту, производится через функцию, hook:
def pytest_runtest_makereport(item, call) с декоратором: @pytest.hookimpl(hookwrapper=True).

Код
# hook для перехвата и модификации данных результатов тестов
@pytest.hookimpl(hookwrapper=True)
def pytest_runtest_makereport(item, call):
	pytest_html = item.config.pluginmanager.getplugin('html')
	outcome = yield
	report = outcome.get_result()
	# Добавление значений в таблицу - значения берем из атрибута __doc__ функции и глобальных переменных
	report.description = str( item.function.__doc__ )
	report.a = str( item.module.log['a'] )
	report.b = str( item.module.log['b'] )
	report.sum = str( item.module.log['sum'] )
	# Добавление html и image блоков в результат - значения берем из глобальных переменных
	extra = getattr(report, 'extra', [])
	if report.when == 'call':
		# вставляем html-блок
		if item.module.log_html != 'none':
			extra.append(pytest_html.extras.html( item.module.log_html ))
		# вставляем img-блок (картинка будет встроена в отчет)
		if item.module.log_img != 'none':
			extra.append(pytest_html.extras.image( item.module.log_img, mime_type='image/jpg', extension='jpg' ))
		# вставляем img-блок с url-ссылкой
		if item.module.log_img_url != 'none':
			extra.append(pytest_html.extras.image( item.module.log_img_url ))
		report.extra = extra


Т.е. при выполнении тестового сценария будет выполняться функция pytest_runtest_makereport(item, call), которая в свою очередь:

  • report.description = str( item.function.__doc__ ) — столбцу report.description будет присвоено значение атрибута __doc__ тестовой функции;
  • report.a = str( item.module.log['a'] ) — столбцу report.a будет присвоено значение глобальной переменной log['a'];
  • report.b = str( item.module.log['b'] ) — столбцу report.b будет присвоено значение глобальной переменной log['b'];
  • report.sum = str( item.module.log['sum'] ) — столбцу report.sum будет присвоено значение глобальной переменной log['sum'];
  • extra.append(pytest_html.extras.html( item.module.log_html )) — внутри отчета по тесту будет создан html-блок, значение которого будет взято из глобальной переменной log_html;
  • extra.append(pytest_html.extras.image( item.module.log_img, mime_type='image/jpg', extension='jpg' )) — внутри отчета по тесту будет создан img-блок, значение которого будет взято из глобальной переменной log_img;
  • extra.append(pytest_html.extras.image( item.module.log_img_url )) — внутри отчета по тесту будет создан img-блок, значение которого будет взято из глобальной переменной log_img_url.

Сами тесты располагаются в файле test.py.


Передача параметров в отчет осуществляется через глобальные переменные и атрибут функции __doc__, при выполнении каждого теста мы должны переопределять глобальные переменные.

Код
# глобальные переменные
log = { 'a':'none', 'b':'none', 'sum':'none' }
log_html = 'none'
log_img = 'none'
log_img_url = 'none'


Присваивать значения глобальным переменным будем через функцию:

Код
# Функция переназначения глобальных переменных
def set_global_var(a='none',b='none',sum_='none',html='none',img='none',img_url='none'):
	global log, log_html, log_img, log_img_url
	log = { 'a': a, 'b': b, 'sum': sum_ }
	log_html = html
	log_img = img
	log_img_url = img_url


Что бы автоматически сбрасывать значения глобальных переменных и не контролировать их в каждом тесте, используем фикстуру.

Код
@pytest.fixture(scope="function", autouse=True)
def default_global_var():
	set_global_var()


Для запуска теста, переходим в каталог с тестовым сценарием и выполняем команду:

pytest test.py -v --html=report.html --self-contained-html

В результате выполнения теста будет создан файл report.html.

Результат выполнения тестов:


  1. test_default()
    Тест с параметрами по умолчанию:

    Код
    def test_default():
    	'''Тест с параметрами по умолчанию'''
    	assert True
    



  2. test_1()
    Тест присваивает значения глобальным переменным и проверяет результат суммирования.

    Код
    def test_1():
    	'''Первый тест'''
    	# Расчет суммы
    	a, b = 2, 2
    	sum_ = a + b
    	# Присваивание значений глобальным переменным
    	set_global_var( a = a, b = b, sum_ = sum_ )
    	# Проверка
    	assert sum_ == 4
    



  3. test_2()

    Тест присваивает значения глобальным переменным (в том числе и для html-блока) и проверяет результат суммирования.

    Код
    def test_2():
    	'''Второй тест'''
    	# Расчет суммы
    	a, b = 3, 3
    	sum_ = a + b
    	# Добавляем html блок
    	html = '''
    	<p>
    	  <table border="3" width="100%">
    	  <tbody>
    	    <tr>
    	      <td bgcolor="#D3D3D3"><font color="black"><strong>a</strong></font></td>
    	      <td bgcolor="#D3D3D3"><font color="black"><strong>b</strong></font></td>
    	      <td bgcolor="#D3D3D3"><font color="black"><strong>sum</strong></font></td>
    	    </tr>
    	    <tr>
    	      <td><font color="black">{0}</font></td>
    	      <td><font color="black">{1}</font></td>
    	      <td><font color="black">{2}</font></td>
    	    </tr>
    	  </tbody>
    	  </table>
    	  </p>
    	  '''.format( a, b, sum_ )
    	# Присваивание значений глобальным переменным
    	set_global_var( a = a, b = b, sum_ = sum_, html = html )
    	# Проверка
    	assert sum_ == 6
    



  4. test_3()
    Тест присваивает значения глобальным переменным (в том числе и для img-блока) и проверяет результат суммирования.

    Код
    def test_3():
    	'''Третий тест'''
    	# Расчет суммы
    	a, b = 4, 4
    	sum_ = a + b
    	# Добавляем картинку в формате base64
    	image = open('test-1.jpg', 'rb')
    	image_read = image.read()
    	img = base64.encodebytes( image_read ).decode("utf-8")
    	# Присваивание значений глобальным переменным
    	set_global_var( a = a, b = b, sum_ = sum_, img = img )
    	# Проверка
    	assert sum_ == 8
    




    Картинка полностью встроена в страницу:


  5. test_4()

    Тест присваивает значения глобальным переменным (в том числе и для img-блока) и проверяет результат суммирования.

    Код
    def test_4():
    	'''Четвертый тест'''
    	# Расчет суммы
    	a, b = 5, 5
    	sum_ = a + b
    	# Добавляем картинку по url
    	img_url = 'test-2.jpg'
    	# Присваивание значений глобальным переменным
    	set_global_var( a = a, b = b, sum_ = sum_, img_url = img_url )
    	# Проверка
    	assert sum_ == 10
    




    Картинка встроена в страницу через гиперссылку:


  6. test_5()

    Тест присваивает значения глобальным переменным (в том числе и для html-блока, img-блока) и проверяет результат суммирования.

    Код
    def test_5():
    	'''Пятый тест'''
    	# Расчет суммы
    	a, b = 5, 1
    	sum_ = a + b
    	# Добавляем html блок
    	html = '<h1>Произвольный HTML-блок</h1>'
    	# Добавляем картинку в формате base64
    	image = open('test-1.jpg', 'rb')
    	image_read = image.read()
    	img = base64.encodebytes( image_read ).decode("utf-8")
    	# Добавляем картинку по url
    	img_url = 'test-2.jpg'
    	# Присваивание значений глобальным переменным
    	set_global_var( a = a, b = b, sum_ = sum_, html = html, img = img, img_url = img_url )
    	# Проверка
    	print ('Дополнительная информация о выполнении теста...')
    	assert sum_ == 6
    



  7. test_6()

    Серия тестов, которые присваивают значения глобальным переменным и проверяют результат суммирования.

    Код
    # Запускаем серию тестов

    @pytest.mark.parametrize("test_a, test_b, test_sum", [(1,2,3), (2,3,5), (4,5,8)])
    def test_6(test_a, test_b, test_sum):
    	'''Серия шестого теста'''
    	# Расчет суммы
    	a, b = test_a, test_b
    	sum_ = a + b
    	# Присваивание значений глобальным переменным
    	set_global_var( a = a, b = b, sum_ = sum_ )
    	# Проверка
    	assert sum_ == test_sum
    




P.S.:


В данном случае разбирался пример передачи параметров в отчет через глобальные переменные, для моей задачи это вполне приемлемо. Но использование глобальных переменных накладывает известные сложности и проблемы, в связи с этим будьте осторожны.

Надеюсь данная статья будет Вам полезна.

Еще один пример отчета