如何将fixture生成的数据提供给测试函数的参数化?如果不行,有什么替代方案吗?
我需要创建多个用户(通过发送HTTP请求),并使用这些用户的数据(登录名和密码)来测试登录页面。
我有一个工具,它可以生成用户并提供他们的登录信息(像这样的一系列元组:[(登录名1, 密码1), (登录名2, 密码2)])。我想把这些数据用作参数,因为我只知道一种正确的方法,可以用不同的测试数据多次运行一个测试。
以下是代码:
conftest.py
@pytest.fixture(scope="session", autouse=True)
def test_user_fixture():
print("INFO | Generating test user data")
user_data_set = user_data_generator()
login_data = []
for user_data in user_data_set:
login_data.append((user_data[0], user_data[1]))
if send_user_create_request(user_data) != 200: # this function sends request to create user AND returns status_code
pytest.exit("ERROR | Test user wasn't created")
yield login_data
print("INFO | Clearing test data")
test_login_page.py
@pytest.mark.usefixtures('webdriver_fixture', 'test_user_fixture')
class TestPositiveLogin:
@pytest.mark.parametrize("login, password", test_user_fixture)
def test_positive_login(self, webdriver_fixture, login, password):
driver = webdriver_fixture
page = BasePage(driver)
page.open_base_page()
page.login(login, password)
在这里,我尝试直接使用这个工具作为参数,因为登录数据完全符合参数的数据格式,但Python却提示:
NameError: name 'test_user_fixture' is not defined
你能帮我解决这个问题吗,或者给我其他的解决方案吗?
1 个回答
你现在的 test_user_fixture
这个设置返回的是一个登录名和密码的列表。我相信你使用 yield
是因为你想在后面做一些清理工作(比如删除用户)。
我建议 test_user_fixture
只返回一个登录名和密码。然后我们可以用 params=
这个关键词来给这个设置添加参数:
# conftest.py
import logging
import pytest
def user_data_generator():
"""Mocked"""
return [
("user1", "password1"),
("user2", "password2"),
]
USERS_DATA = list(user_data_generator())
def test_id(user_data):
"""Given (login, password), return the login part.
We use this login as part of the test ID
"""
return user_data[0]
@pytest.fixture(scope="session", autouse=True, params=USERS_DATA, ids=test_id)
def test_user_fixture(request):
login, password = request.param[:2]
logging.debug("In test_user_fixture, login=%r, password=%r", login, password)
if send_user_create_request((login, password)) != 200:
pytest.exit("ERROR | Test user wasn't created")
yield login, password
logging.info("Clearing test data")
还有
# test_login_page.py
import logging
class TestPositiveLogin:
def test_positive_login(self, webdriver_fixture, test_user_fixture):
login, password = test_user_fixture
logging.debug("webdriver_fixture=%r", webdriver_fixture)
logging.debug("login=%r", login)
logging.debug("password=%r", password)
还有
# pyproject.toml
[tool.pytest.ini_options]
log_cli="true"
log_level="DEBUG"
当 log_cli="false"
时的输出:
test_login_page.py::TestPositiveLogin::test_positive_login[user1] PASSED
test_login_page.py::TestPositiveLogin::test_positive_login[user2] PASSED
当 log_cli="true"
时的输出:
test_login_page.py::TestPositiveLogin::test_positive_login[user1]
---------------------------------------------------------------------- live log setup ----------------------------------------------------------------------
DEBUG root:conftest.py:26 In test_user_fixture, login='user1', password='password1'
---------------------------------------------------------------------- live log call -----------------------------------------------------------------------
DEBUG root:test_login_page.py:8 webdriver_fixture='Mocked WebDriver'
DEBUG root:test_login_page.py:9 login='user1'
DEBUG root:test_login_page.py:10 password='password1'
PASSED
test_login_page.py::TestPositiveLogin::test_positive_login[user2]
---------------------------------------------------------------------- live log setup ----------------------------------------------------------------------
INFO root:conftest.py:32 Clearing test data for login='user1'
DEBUG root:conftest.py:26 In test_user_fixture, login='user2', password='password2'
---------------------------------------------------------------------- live log call -----------------------------------------------------------------------
DEBUG root:test_login_page.py:8 webdriver_fixture='Mocked WebDriver'
DEBUG root:test_login_page.py:9 login='user2'
DEBUG root:test_login_page.py:10 password='password2'
PASSED
-------------------------------------------------------------------- live log teardown ---------------------------------------------------------------------
INFO root:conftest.py:32 Clearing test data for login='user2'
注意事项
我假设
USERS_DATA
包含[(user, password, ...), ...]
这个
params=USERS_DATA
是用来给设置添加参数的。虽然这个设置的作用范围是整个会话,但它会对USERS_DATA
中的每个元素调用一次。换句话说,如果USERS_DATA
有两个元素,这个设置就会被调用两次。test_id()
函数从测试参数中提取login
,这样可以更好地识别测试。test_user_fixture
返回的是一个(login, password)
的元组,所以在测试中我们可以把它拆开,这样更方便:login, password = test_user_fixture
我更喜欢用
logging
而不是print
,因为我可以通过在pyproject.toml
中这一行来控制它的开关:log_cli="true"
只需把
true
替换成false
,就可以有效地关闭所有日志记录。我还可以通过这一行来控制日志级别(比如 WARN、INFO、DEBUG 等):log_level="DEBUG"
更新
如果你去掉 ids=
的部分,输出会变成这样:
test_login_page.py::TestPositiveLogin::test_positive_login[test_user_fixture0] PASSED
test_login_page.py::TestPositiveLogin::test_positive_login[test_user_fixture1] PASSED
注意方括号里面的 test_user_fixture0
和 test_user_fixture1
:它们是 pytest
自动生成的 ID,并没有什么用。
我希望在这些方括号里放的是有用的 ID,比如 login
名称。
根据 pytest 文档,ids=
可以是一个 ID 的序列。这意味着以下的写法是一样的:
USERS_DATA = list(user_data_generator())
TEST_IDS = [user_data[0] for user_data in USERS_DATA]
@pytest.fixture(
scope="session",
autouse=True,
params=USERS_DATA,
ids=TEST_IDS,
)
def test_user_fixture(request):
...
例如,如果 USERS_DATA
是
[
("user1", "password1"),
("user2", "password2"),
]
那么 TEST_IDS
将会是
[
"user1",
"user2",
]
这些 ID 将会在方括号里面使用。注意 ids=
也可以是一个函数,这个函数接收 params=
参数中的单个元素并返回一个 ID。在这种情况下,我们有:
USERS_DATA = list(user_data_generator())
def test_id(user_data):
# Example of user_data: ("user1", "password1")
return user_data[0]
@pytest.fixture(
scope="session",
autouse=True,
params=USERS_DATA,
ids=test_id,
)
def test_user_fixture(request):
...
这意味着 pytest
会把 USERS_DATA
中的每个元素传入 test_id
函数,并使用返回值作为 ID。
我们应该使用哪种方法呢?我认为第一种方法使用 TEST_IDS
更容易理解。第二种方法更强大,这也是我在项目中使用的方法。