簡體   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