首页
学习
活动
专区
圈层
工具
发布
社区首页 >问答首页 >Python动态测试计划生成

Python动态测试计划生成
EN

Stack Overflow用户
提问于 2019-02-21 20:29:55
回答 2查看 457关注 0票数 1

我使用Sphinx编写文档,使用pytest进行测试。我需要生成一个测试计划,但我真的不想手动生成它。

我想到了一个很好的解决方案,那就是将测试元数据实际嵌入到测试本身中,嵌入到它们各自的文档字符串中。此元数据将包括% complete、剩余时间等内容。然后,我可以运行所有测试(在这一点上主要包括占位符),并从它们生成测试计划。这将保证测试计划和测试本身是同步的。

我在考虑做一个最简单的插件或者是一个sphinx插件来处理这个问题。

使用pytest,我能看到的最接近的钩子看起来像pytest_collection_modifyitems,它在收集完所有测试后被调用。

或者,我正在考虑使用Sphinx,也许复制/修改todolist插件,因为它看起来是最接近这个想法的。这个插件的输出会更有用,因为输出会很好地嵌入到我现有的基于Sphinx的文档中,尽管这个插件有很多功能,我没有时间去理解它。

文档字符串中可能包含如下内容:

代码语言:javascript
复制
: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或类似的表,并将其用作测试计划。

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

EN

回答 2

Stack Overflow用户

发布于 2019-02-22 18:07:13

最后,我选择了一个基于pytest的插件,因为它的代码非常简单。

如果其他人感兴趣,下面是插件:

代码语言:javascript
复制
"""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")

然后我在这个函数中配置插件:

代码语言:javascript
复制
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文件:

代码语言:javascript
复制
Autogenerated test_plan
=======================

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

.. include:: test_plan.rst
票数 1
EN

Stack Overflow用户

发布于 2019-11-21 21:45:43

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

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

代码语言:javascript
复制
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来记录测试用例。

代码语言:javascript
复制
My tests
========

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

这在sphinx中产生了一些很好的测试用例对象,我们可以在表格和流程图中过滤、链接和显示这些对象。参见Sphinx-Needs

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

代码语言:javascript
复制
.. test-report: My Test report
   :id: REPORT_1
   :file: ../pytest_junit_results.xml
   :links: [[tr_link('case_name', 'signature')]]

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

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

票数 0
EN
页面原文内容由Stack Overflow提供。腾讯云小微IT领域专用引擎提供翻译支持
原文链接:

https://stackoverflow.com/questions/54807126

复制
相关文章

相似问题

领券
问题归档专栏文章快讯文章归档关键词归档开发者手册归档开发者手册 Section 归档