繁体   English   中英

Python 动态测试计划生成

[英]Python Dynamic Test Plan generation

我使用 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

这个想法是然后根据函数的名称、文档字符串和嵌入的计划信息生成一个简单的 markdown/rst 或类似的表,并将其用作测试计划。

像这样的东西已经存在了吗?

最后,我使用了一个基于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 文档源文件之一中,我只包含输出 rst 文件:

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

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

.. include:: test_plan.rst


我们在公司中使用Sphinx-needsSphinx-Test-Reports做了类似的事情。

在测试文件中,我们使用 docstring 来存储我们的测试用例包括元数据:

def my_test():
    """
    .. test:: My test case
       :id: TEST_001
       :status: in progress
       :author: me

       This test case checks for **awesome** stuff.
    """
    a = 2
    b = 5
    # ToDo: chek if a+b = 7

然后我们使用autodoc 记录测试用例

My tests
========

.. automodule:: test.my_tests:
   :members:

这会在 sphinx 中产生一些不错的测试用例对象,我们可以过滤、链接并在表格和流程图中呈现。 狮身人面像需求

使用Sphinx-Test-Reports,我们也将结果加载到文档中:

.. test-report: My Test report
   :id: REPORT_1
   :file: ../pytest_junit_results.xml
   :links: [[tr_link('case_name', 'signature')]]

这将为每个测试用例创建对象,我们也可以过滤和链接这些对象。 感谢tr_link结果对象自动链接到测试用例对象。

之后,我们在 sphinx 中获得了所有需要的信息,并且可以使用例如.. needtable::来获取自定义视图。

暂无
暂无

声明:本站的技术帖子网页,遵循CC BY-SA 4.0协议,如果您需要转载,请注明本站网址或者原文地址。任何问题请咨询:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM