我一直在四处寻找,但我没能找到任何完全符合我要求的东西。
我想知道是否有一个实用程序可以扫描整个存储库的结构和源代码,并创建一个并行测试结构,其中没有一个并行测试结构,其中代码中的每个函数和方法都有一个等效的空单元测试。
必须手动编写一堆单元测试样板文件非常乏味。
例如,假设这个项目结构:
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/