python - 为整个存储库动态创建测试文件模板

标签 python python-3.x pytest

我一直在四处寻找,但我没能找到任何完全符合我要求的东西。

我想知道是否有一个实用程序可以扫描整个存储库的结构和源代码,并创建一个并行测试结构,其中没有一个并行测试结构,其中代码中的每个函数和方法都有一个等效的空单元测试。

必须手动编写一堆单元测试样板文件非常乏味。

例如,假设这个项目结构:

myproject
|--src
   |--__init__.py
   |--a.py
   |--subpackage
      |--__init__.py
      |--b.py
      |--c.py

它应该创建:

myproject
|--src
|  |--__init__.py
|  |--a.py
|  |--subpackage
|     |--__init__.py
|     |--b.py
|     |--c.py
|
|--tests
   |--test_a.py
   |--subpackage
      |--test_b.py
      |--test_c.py

如果a.py的内容是:

class Printer:
    def print_normal(self, text):
        print(text)

    def print_upper(self, text):
        print(str(text).upper())

    def print_lower(self, text):
        print(str(text).lower())

def greet():
    print("Hi!")

test_a.py 的内容应该是这样的:

import pytest
from myproject.src import a

def test_Printer_print_normal():
    assert True

def test_Printer_print_upper():
    assert True

def test_Printer_print_lower():
    assert True

def test_greet():
    assert True

有没有人知道任何执行类似操作的 python 项目?即使不完全相同,在最初为具有数百个类和数千个方法的大型存储库设置 pytest 样板时,任何可以节省一些工作的东西都会节省大量时间。

提前致谢。

最佳答案

我自己在 Python 中搜索测试生成器工具,我只能找到那些生成 unittest 样式类的工具:

pythoscope

从Github安装最新版本:

$ pip2 install git+https://github.com/mkwiatkowski/pythoscope

理论上看起来很有前途:根据模块中的静态代码分析生成类,将项目结构映射到 tests 目录(每个库模块一个测试模块),每个函数都有自己的测试类。这个项目的问题是它几乎被废弃了:不支持 Python 3,当遇到向后移植到 Python 2 的特性时失败,因此 IMO 现在无法使用。有pull requests那里声称要添加 Python 3 支持,但当时它们对我不起作用。

尽管如此,如果您的模块具有 Python 2 语法,它会生成以下内容:

$ pythoscope --init .
$ pythoscope spam.py
$ cat tests/test_spam.py
import unittest


class TestPrinter(unittest.TestCase):
    def test_print_lower(self):
        # printer = Printer()
        # self.assertEqual(expected, printer.print_lower())
        assert False  # TODO: implement your test here

    def test_print_normal(self):
        # printer = Printer()
        # self.assertEqual(expected, printer.print_normal())
        assert False  # TODO: implement your test here

    def test_print_upper(self):
        # printer = Printer()
        # self.assertEqual(expected, printer.print_upper())
        assert False  # TODO: implement your test here

class TestGreet(unittest.TestCase):
    def test_greet(self):
        # self.assertEqual(expected, greet())
        assert False  # TODO: implement your test here

if __name__ == '__main__':
    unittest.main()

螺旋钻

从 PyPI 安装:

$ pip install auger-python

从运行时行为生成测试。虽然它可能是具有命令行界面的工具的一个选项,但它需要为库编写一个入口点。即使使用工具,它也只会为明确请求的内容生成测试;如果一个函数没有被执行,则不会为它生成测试。这使得它只能部分用于工具(最坏的情况是您必须多次运行该工具并激活所有选项以覆盖完整的代码库)并且几乎不能用于库。

尽管如此,这就是 Auger 将从您的模块的示例入口点生成的内容:

# runner.py

import auger
import spam

with auger.magic([spam.Printer], verbose=True):
    p = spam.Printer()
    p.print_upper()

执行 runner.py 产生:

$ python runner.py
Auger: generated test: tests/test_spam.py
$ cat tests/test_spam.py
import spam
from spam import Printer
import unittest


class SpamTest(unittest.TestCase):
    def test_print_upper(self):
        self.assertEqual(
            Printer.print_upper(self=<spam.Printer object at 0x7f0f1b19f208>,text='fizz'),
            None
        )


if __name__ == "__main__":
    unittest.main()

自定义工具

对于一次性工作,编写自己的 AST 访问器从现有模块生成测试 stub 应该不难。下面的示例脚本 testgen.py 使用与 pythoscope 相同的想法生成简单的测试 stub 。使用示例:

$ python -m testgen spam.py 
class TestPrinter:
    def test_print_normal(self):
        assert False, "not implemented"


    def test_print_upper(self):
        assert False, "not implemented"


    def test_print_lower(self):
        assert False, "not implemented"


def test_greet():
    assert False, "not implemented"

testgen.py 的内容:

#!/usr/bin/env python3

import argparse
import ast
import pathlib


class TestModuleGenerator(ast.NodeVisitor):

    linesep = '\n'

    def __init__(self, *args, **kwargs):
        super().__init__(*args, **kwargs)
        self.imports = set()
        self.lines = []
        self.indent = 0
        self.current_cls = None

    @property
    def code(self):
        lines = list(self.imports) + [self.linesep] + self.lines
        return self.linesep.join(lines).strip()

    def visit_FunctionDef(self, node: ast.FunctionDef):
        arg_self = 'self' if self.current_cls is not None else ''
        self.lines.extend([
            '    ' * self.indent + f'def test_{node.name}({arg_self}):',
            '    ' * (self.indent + 1) + 'assert False, "not implemented"',
            self.linesep,
        ])
        self.generic_visit(node)

    def visit_ClassDef(self, node: ast.ClassDef):
        clsdef_line = '    ' * self.indent + f'class Test{node.name}:'
        self.lines.append(clsdef_line)
        self.indent += 1
        self.current_cls = node.name
        self.generic_visit(node)
        self.current_cls = None
        if self.lines[-1] == clsdef_line:
            self.lines.extend([
                '  ' * self.indent + 'pass',
                self.linesep
            ])
        self.indent -= 1

    def visit_AsyncFunctionDef(self, node: ast.AsyncFunctionDef):
        self.imports.add('import pytest')
        self.lines.extend([
            '    ' * self.indent + '@pytest.mark.asyncio',
            '    ' * self.indent + f'async def test_{node.name}():',
            '    ' * (self.indent + 1) + 'assert False, "not implemented"',
            self.linesep,
        ])
        self.generic_visit(node)


if __name__ == '__main__':
    parser = argparse.ArgumentParser()
    parser.add_argument(
        'module',
        nargs='+',
        default=(),
        help='python modules to generate tests for',
        type=lambda s: pathlib.Path(s).absolute(),
    )
    modules = parser.parse_args().module
    for module in modules:
        gen = TestModuleGenerator()
        gen.visit(ast.parse(module.read_text()))
        print(gen.code)

关于python - 为整个存储库动态创建测试文件模板,我们在Stack Overflow上找到一个类似的问题: https://stackoverflow.com/questions/56885455/

相关文章:

python - 子函数的自定义打印函数 yield

django - 通过 Django admin 删除对象时,是否可以禁用相关对象的生成?

Python:在 pytest 中比较两个 JSON 对象

python - 保证不存在文件的单元测试 fixture

python - 在 Python 中从日期中减去 n 天

python - python中的实例

c++ - 内置mpi的自定义boost python模块?

python-3.x - 过滤掉 Pandas 中一列具有 NaN 值的组

python - 测试 : Allow Failure Rate

python - 从python中的shell命令获取返回值