我正在尝试编写一个测试套件,用于使用testinfra验证某些服务器的状态。
这是我第一次使用python/testinfra/pytest。
作为一个简短的伪码例子
test_files.py
testinfra_hosts=[server1,server2,server3]
with open("tests/microservices_default_params.yml", "r") as f:
try:
default_params = yaml.safe_load(f)
except yaml.YAMLError as exc:
print(exc)
with open("tests/" + server + "/params/" + server + "_params.yml", "r") as f:
try:
instance_params = yaml.safe_load(f)
except yaml.YAMLError as exc:
print(exc)
@pytest.mark.parametrize(
"name", [] + default_params["files"] + instance_params["files"]
)
def test_files(host, name):
file = host.file(name)
assert file.exists每个服务器都有自己独特的params yaml文件。我希望每个服务器都通过相同的测试,但是我需要每个服务器使用它各自的.yml文件中自己的参数化值来运行测试。
上面的代码的问题是,它将尝试对服务器2和3执行所有server1s唯一的params,然后将再次开始,服务器2在服务器1-3唯一的params上运行。
我无法找到一种干净的方法,在server1和服务器1 params中运行一次测试,然后再对server2和server2 params执行同样的操作。
我尝试在测试文件本身中使用for循环,将每个instance_params.yml读入字典中,其中的键是包含所有服务器params的服务器名称和值--但这听起来不太好,而且因为断言在循环中,如果该服务器的一个参数失败,则该循环退出,并且不会为该服务器尝试任何进一步的参数。
我研究过pytest_collection_modifyitems,但我无法完全理解如何让它做我想做的事情。我觉得可能有一个简单的解决办法,我错过了。
我最后的办法是把测试结果和参数化的对讲机分开作为
@pytest.mark.parametrize(
"server1_params", instance_params['server1']['files]
)
def_test_files_server1(host, server1_params):
...
@pytest.mark.parametrize(
"server2_params", instance_params['server2']['files]
)
def_test_files_server2(host,server2_params):
...不过,我觉得这种方法听起来不对。
对于一个新来的孩子,我会很感激的。在此之前,我从未在这里要求过任何有意义的事情:)
更新:@ajk找到了解决方案!pytest_generate_tests函数正是我所需要的--我一直在加深对pytest的理解。
谢谢ajk!我欠你一个人情
发布于 2020-07-28 05:38:04
这是一个很好的问题,看起来你已经从几个不同的角度提出了这个问题。我自己也不是专家,但有几种不同的方法可以用pytest来做这样的事情。它们都涉及将繁重的工作移交给固定装置。因此,下面是一种将您已经共享的代码改编为使用一些固定装置的方法的快速分解:
默认参数
看起来你有一些不特定于主机的参数。将这些值拖到会话范围夹具中可能是有意义的,这样您就可以在许多主机上重用相同的值:
@pytest.fixture(scope="session")
def default_params():
with open("tests/microservices_default_params.yml", "r") as f:
try:
return yaml.safe_load(f)
except yaml.YAMLError as exc:
print(exc)这样,您可以将default_params作为参数添加到任何需要这些值的测试函数中。
主机专用参数
您当然可以像以前一样加载这些参数,或者在测试本身中添加一些查找逻辑。这可能是最好和最清晰的方法!另一种选择是有一个参数化夹具,其值因实例而异:
@pytest.fixture(scope="function", params=testinfra_hosts)
def instance_params(request):
with open(f"tests/{request.param}/params/{request.param}_params.yml", "r") as f:
try:
return request.param, yaml.safe_load(f)
except yaml.YAMLError as exc:
print(exc)现在,如果我们将instance_params作为参数添加到测试函数中,它将对testinfra_hosts中的每个条目运行一次测试。夹具将根据活动主机每次返回一个新值。
编写测试
如果我们把重物搬到固定装置上,那么实际的测试就会变得更简单:
def test_files(default_params, instance_params):
hostname, params = instance_params
merged_files = default_params["files"] + params["files"]
print(f"""
Host: {hostname}
Files: {merged_files}
""")(我在这里甚至没有断言任何事情,只是在玩固定装置,以确保他们在做我认为他们正在做的事情)
把它当作旋转,
我在本地试用了以下示例yaml文件:
测试/微服务_default_default_
files:
- common_file1.txt
- common_file2.txt测试/server1 1/params/server1_pars.yml
files:
- server1_file.txt测试/server2 2/params/server2_pars.yml
files:
- server2_file.txt测试/server3 3/params/server3_pars.yml
files:
- server3_file.txt
- server3_file2.txt运行测试文件会产生:
test_infra.py::test_files[server1]
Host: server1
Files: ['common_file1.txt', 'common_file2.txt', 'server1_file.txt']
PASSED
test_infra.py::test_files[server2]
Host: server2
Files: ['common_file1.txt', 'common_file2.txt', 'server2_file.txt']
PASSED
test_infra.py::test_files[server3]
Host: server3
Files: ['common_file1.txt', 'common_file2.txt', 'server3_file.txt', 'server3_file2.txt']
PASSED这似乎至少是你所追求的总方向。我希望其中的一些是有用的-祝好运和快乐的测试!
更新
下面的注释询问如何将其分解,因此列表中的每个文件都会生成自己的测试。我不确定这样做的最佳方式,但有几个选择可能是:
@pytest.mark.parametrizepytest_generate_tests设置动态参数化,类似于描述的这里。在任何一种情况下,您都可以从这样的东西开始:
import pytest
import yaml
testinfra_hosts = ["server1", "server2", "server3"]
def get_default_params():
with open("tests/microservices_default_params.yml", "r") as f:
try:
return yaml.safe_load(f)
except yaml.YAMLError as exc:
print(exc)
def get_instance_params(instance):
with open(f"tests/{instance}/params/{instance}_params.yml", "r") as f:
try:
return yaml.safe_load(f)
except yaml.YAMLError as exc:
print(exc)要使用@pytest.mark.parametrize,您可以使用以下内容进行跟踪:
def get_instance_files(instances):
default_files = get_default_params()["files"]
for instance in instances:
instance_files = default_files + get_instance_params(instance)["files"]
for filename in instance_files:
yield (instance, filename)
@pytest.mark.parametrize("instance_file", get_instance_files(testinfra_hosts))
def test_files(instance_file):
hostname, filename = instance_file
print(
f"""
Host: {hostname}
Files: {filename}
"""
)或者采用pytest_generate_tests方法,您可以这样做:
def pytest_generate_tests(metafunc):
if "instance_file" in metafunc.fixturenames:
default_files = get_default_params()["files"]
params = [
(host, filename)
for host in testinfra_hosts
for filename in (
default_files + get_instance_params(host)["files"]
)
]
metafunc.parametrize(
"instance_file", params, ids=["_".join(param) for param in params]
)
def test_files(instance_file):
hostname, filename = instance_file
print(
f"""
Host: {hostname}
Files: {filename}
"""
)这两种方法都可以工作,我怀疑更有经验的人可能会将pytest_generate_tests版本打包到一个类中,并稍微清理一下逻辑。不过,我们得从某个地方开始,嗯?
https://stackoverflow.com/questions/63076466
复制相似问题