Генерация динамического плана тестирования Python - PullRequest
0 голосов
/ 21 февраля 2019

Я использую Sphinx для документации и pytest для тестирования.Мне нужно создать план тестирования, но я действительно не хочу создавать его вручную.

Мне пришло в голову, что изящным решением было бы на самом деле внедрить тестовые метаданные в сами тесты, в их соответствующие строки документации.Эти метаданные будут включать в себя такие вещи, как% завершения, оставшееся время и т. Д. Затем я могу выполнить все тесты (которые на этом этапе будут включать в основном заполнители) и сгенерировать из них план тестирования.Это будет гарантировать, что план тестирования и сами тесты будут синхронизированы.

Я думал сделать плагин Pytest или плагин Sphinx, чтобы справиться с этим.

Используя pytest, ближайший хук, который я вижу, выглядит как pytest_collection_modifyitems, который вызывается после того, как собраны все тесты.

В качестве альтернативы я думал об использовании Sphinx и, возможно, копировании / модификацииплагин todolist, как кажется, наиболее близкий к этой идее.Вывод этого будет более полезен, так как вывод будет хорошо вписываться в существующие документы на основе Sphinx, которые есть у меня, хотя в этом плагине много чего происходит, и у меня нет времени вкладывать деньги в его понимание.

Строки документов могут содержать что-то вроде этого:

:plan_complete: 50 #% indicator of how complete this test is
:plan_remaining: 2 #the number of hours estimated to complete this test
:plan_focus: something #what is the test focused on testing

Идея состоит в том, чтобы затем создать простую уценку / первую или аналогичную таблицу на основе имени функции, строки документации и встроенной информации о плане.и использовать это в качестве плана тестирования.

Что-то подобное уже существует?

1 Ответ

0 голосов
/ 22 февраля 2019

В конце концов, я выбрал плагин на основе pytest, поскольку его было намного проще кодировать.

Если кому-то еще интересно, плагин ниже:

"""Module to generate a test plan table based upon metadata extracted from test
docstrings. The test description is extracted from the first sentence or up to
the first blank line. The data which is extracted from the docstrings are of the
format:

    :test_remaining: 10 #number of hours remaining for this test to be complete. If
                     not present, assumed to be 0
    :test_complete: #the percentage of the test that is complete. If not
                    present, assumed to be 100
    :test_focus: The item the test is focusing on such as a DLL call.

"""
import pytest
import re
from functools import partial
from operator import itemgetter
from pathlib import Path

whitespace_re = re.compile(r'\s+')
cut_whitespace = partial(whitespace_re.sub, ' ')
plan_re = re.compile(r':plan_(\w+?):')
plan_handlers = {
        'remaining': lambda x:int(x.split('#')[0]),
        'complete': lambda x:int(x.strip().split('#')[0]),
        'focus': lambda x:x.strip().split('#')[0]
}
csv_template = """.. csv-table:: Test Plan
   :header: "Name", "Focus", "% Complete", "Hours remaining", "description", "path"
   :widths: 20, 20, 10, 10, 60, 100

{tests}

Overall hours remaining: {hours_remaining:.2f}
Overall % complete: {complete:.2f}

"""

class GeneratePlan:
    def __init__(self, output_file=Path('test_plan.rst')):
        self.output_file = output_file

    def pytest_collection_modifyitems(self, session, config, items):
        #breakpoint()
        items_to_parse = {i.nodeid.split('[')[0]:i for i in self.item_filter(items)}
        #parsed = map(parse_item, items_to_parse.items())
        parsed = [self.parse_item(n,i) for (n,i) in items_to_parse.items()]

        complete, hours_remaining = self.get_summary_data(parsed)

        self.output_file.write_text(csv_template.format(
                    tests = '\n'.join(self.generate_rst_table(parsed)),
                    complete=complete,
                    hours_remaining=hours_remaining))

    def item_filter(self, items):
        return items #override me

    def get_summary_data(self, parsed):
        completes = [p['complete'] for p in parsed]
        overall_complete = sum(completes)/len(completes)
        overall_hours_remaining = sum(p['remaining'] for p in parsed)
        return overall_complete, overall_hours_remaining


    def generate_rst_table(self, items):
        "Use CSV type for simplicity"
        sorted_items = sorted(items, key=lambda x:x['name'])
        quoter = lambda x:'"{}"'.format(x)
        getter = itemgetter(*'name focus complete remaining description path'.split())
        for item in sorted_items:
            yield 3*' ' + ', '.join(map(quoter, getter(item)))

    def parse_item(self, path, item):
        "Process a pytest provided item"

        data = {
            'name': item.name.split('[')[0],
            'path': path.split('::')[0],
            'description': '',
            'remaining': 0,
            'complete': 100,
            'focus': ''
        }

        doc = item.function.__doc__
        if doc:
            desc = self.extract_description(doc)
            data['description'] = desc
            plan_info = self.extract_info(doc)
            data.update(plan_info)

        return data

    def extract_description(self, doc):
        first_sentence = doc.split('\n\n')[0].replace('\n',' ')
        return cut_whitespace(first_sentence)

    def extract_info(self, doc):
        plan_info = {}
        for sub_str in doc.split('\n\n'):
            cleaned = cut_whitespace(sub_str.replace('\n', ' '))
            splitted = plan_re.split(cleaned)
            if len(splitted) > 1:
                i = iter(splitted[1:]) #splitter starts at index 1
                while True:
                    try:
                        key = next(i)
                        val = next(i)
                    except StopIteration:
                        break
                    assert key
                    if key in plan_handlers:
                        plan_info[key] = plan_handlers[key](val)
        return plan_info


Из моего файла conftest.py у меня есть аргумент командной строки, настроенный в pytest_addoption function: parser.addoption('--generate_test_plan', action='store_true', default=False, help="Generate test plan")

И затем я настраиваю плагин в этой функции:

def pytest_configure(config):
    output_test_plan_file = Path('docs/source/test_plan.rst')
    class CustomPlan(GeneratePlan):
        def item_filter(self, items):
            return (i for i in items if 'tests/hw_regression_tests' in i.nodeid)

    if config.getoption('generate_test_plan'):
        config.pluginmanager.register(CustomPlan(output_file=output_test_plan_file))
        #config.pluginmanager.register(GeneratePlan())

Наконец, в одном из моих исходных файлов документации sphinx я просто включаю выходной первый файл:

Autogenerated test_plan
=======================

The below test_data is extracted from the individual tests in the suite.

.. include:: test_plan.rst


...