提问人:Stefan 提问时间:11/2/2023 最后编辑:Stefan 更新时间:11/8/2023 访问量:124
如何使用 src 和测试文件夹结构为 Python 生成单元测试(最好与 PyCharm 集成)?
How to generate unit tests for Python using src and test folder structure (preferable with PyCharm integration)?
问:
在我的 python 项目中,我有以下文件夹结构:
src
foo
foo.py
baa
baa.py
test
foo
baa
并希望生成一个单元测试文件来测试 .test/foo/test_foo.py
src/foo/foo.py
在 PyCharm 中,我可以
- 去 foo.py 的某个地方,然后
- 使用组合键和
Ctrl+Shift+T
- 选择“创建新测试”选项。
这将生成一个新的测试文件,并使用一些测试方法对其进行初始化。另请参阅 https://www.jetbrains.com/help/pycharm/creating-tests.html
然而:
PyCharm 不会自动检测所需的目标目录,但建议改用源目录。这将导致测试文件与测试文件位于同一文件夹中。
test/foo
src/foo
组合键不适用于不包含类或函数但仅提供属性的文件/模块。
PyCharm 会生成不需要的 init.py 文件。
因此,PyCharm 的内置单元测试生成对我不起作用。
=> 为 Python 生成单元测试的推荐方法是什么?
我还尝试使用 UnitTestBot 或 pynguin,但无法生成相应的测试文件。手动创建所有文件夹和文件将是一项繁琐的任务,我希望现代 IDE 能够为我完成(部分)工作。
你可能会争辩说,测试应该先写出来。因此,如果可以选择反其道而行之,并从退出测试中生成测试文件......这也会有所帮助。
另一种选择可能是使用 GitHub Copilot。但是,我不被允许在工作中使用它,也不想将我们的代码发送到远程服务器。因此,我仍在寻找一种更保守的方法。
另一种方法是使用自定义脚本,该脚本遍历现有的 src 文件夹结构,并至少在测试文件夹中创建相应的文件夹和文件。我希望有一个现有的解决方案,但还没有找到一个。
相关:
https://github.com/UnitTestBot/UTBotJava/issues/2670
https://github.com/se2p/pynguin/issues/52
pytest 或 python 中的任何测试工具可以自动生成单元测试吗?
https://www.strictmode.io/articles/using-github-copilot-for-testing
https://dev.to/this-is-learning/copilot-chat-writes-unit-tests-for-you-1c82
答:
下面是一个脚本的初稿,该脚本为缺少的测试文件生成单元测试框架。我让 AI 为我生成该脚本并对其进行了重构。
import inspect
import os
def main():
src_dir = 'src'
test_dir = 'test'
generate_unit_test_skeleton(src_dir, test_dir)
def generate_unit_test_skeleton(src_dir, test_dir):
# Loop over all folders and files in src directory
for root, dirs, files in os.walk(src_dir):
# Get the relative path of the current folder or file
relative_folder_path = os.path.relpath(root, src_dir)
# Create the corresponding unit test folder in test directory
test_folder = generate_test_folder_if_not_exists(relative_folder_path, test_dir)
# Loop over all files in the current folder
for file in files:
# Check if the file has a .py extension
if file.endswith('.py'):
generate_unit_tests_for_file(
file,
relative_folder_path,
test_folder,
)
def generate_unit_tests_for_file(
file,
relative_directory,
test_directory,
):
# Create the corresponding unit test file in test directory
generated_test_file_path = generate_unit_test_file(
file,
relative_directory,
test_directory,
)
if generated_test_file_path is not None:
# Get all classes and functions defined in the original file
classes, functions = determine_members(file, relative_directory)
# Generate test functions for each class and function
generate_test_functions(
file,
generated_test_file_path,
classes,
functions,
)
def generate_test_functions(
file,
test_file_path,
classes,
functions,
):
module_name = determine_module_name(file)
with open(test_file_path, 'a') as test_file:
for class_name, class_instance in classes:
generate_test_function_for_class(
test_file,
module_name,
class_name,
class_instance,
)
for function_name, function_instance in functions:
generate_test_function_for_function(
test_file,
module_name,
function_name,
function_instance,
)
def generate_test_function_for_function(
test_file,
module_name,
function_name,
function_instance,
):
# Generate the test function name
test_function_name = f'test_{function_name}'
arguments = determine_arguments(function_instance)
# Write the test function to the test file
test_file.write(f'def {test_function_name}():\n')
test_file.write(f' # TODO: Implement test\n')
test_file.write(f' # result = {module_name}.{function_name}({arguments})\n')
test_file.write(f' pass\n')
test_file.write('\n')
def generate_test_function_for_class(
test_file,
module_name,
class_name,
class_instance,
):
# Generate the test function name
test_function_name = f'test_{class_name}'
arguments = determine_arguments(class_instance)
# Write the test function to the test file
test_file.write(f'def {test_function_name}():\n')
test_file.write(f' # TODO: Implement test\n')
test_file.write(f' # instance = {module_name}.{class_name}({arguments})\n')
test_file.write(f' pass\n')
test_file.write('\n')
def determine_members(file, relative_directory):
# Get the module name
module_name = os.path.splitext(file)[0]
# Import the module
directory_import_path = relative_directory.replace('\\', '.')
import_path = f'{directory_import_path}.{module_name}'
module = __import__(import_path, fromlist=[module_name])
# Get all classes and functions defined in the module
classes = inspect.getmembers(module, inspect.isclass)
functions = inspect.getmembers(module, inspect.isfunction)
return classes, functions
def determine_arguments(function_instance):
try:
signature = inspect.signature(function_instance)
except ValueError:
return ''
parameters = signature.parameters
arguments = []
for param in parameters.values():
argument = determine_argument(param)
arguments.append(argument)
argument_string = ', '.join(arguments)
if len(arguments) > 2:
argument_string += ',' # leading comma causes line breaks if formatted with black
return argument_string
def determine_argument(param):
argument = param.name
if param.default != inspect.Parameter.empty:
default_value = determine_default_value(param.default)
argument += f'={default_value}'
return argument
def determine_default_value(default_instance):
if inspect.isfunction(default_instance):
return default_instance.__name__
elif isinstance(default_instance, str):
return f"'{default_instance}'"
else:
return default_instance
def generate_unit_test_file(file, relative_directory, test_directory):
test_file_path = os.path.join(test_directory, f'test_{file}')
if os.path.exists(test_file_path):
return None
else:
# Open the test file in write mode
with open(test_file_path, 'w') as f:
# Write the initial import statement
import_statement = generate_import_statement(file, relative_directory)
f.write(import_statement)
f.write('\n')
return test_file_path
def generate_import_statement(file, relative_directory):
directory_import_path = relative_directory.replace('////', '.')
module_name = determine_module_name(file)
statement = f'from {directory_import_path} import {module_name}\n'
return statement
def determine_module_name(file):
name = os.path.splitext(file)[0]
return name
def generate_test_folder_if_not_exists(relative_path, test_dir):
test_folder = os.path.join(test_dir, relative_path)
if not os.path.exists(test_folder):
os.makedirs(test_folder)
return test_folder
if __name__ == '__main__':
main()
文件“test/foo/test_foo.py”的示例结果:
import foo.foo
def test_Language():
# TODO: Implement test
# instance = controls.Language(value, names=None, module=None, qualname=None, type=None, start=1, boundary=None,)
pass
def test_Layout():
# TODO: Implement test
# instance = controls.Layout(kwargs)
pass
def test_SimpleNamespace():
# TODO: Implement test
# instance = controls.SimpleNamespace()
pass
评论