如何通过命令行在 pytest 中传递参数

我有一个代码,我需要从终端传递参数,比如 name。 下面是我的代码以及如何传递参数。我得到一个“未找到文件”类型的错误,我不明白。

我试过终端中的命令: pytest <filename>.py -almonds 我应该把名字印成“杏仁”

@pytest.mark.parametrize("name")
def print_name(name):
print ("Displaying name: %s" % name)
133490 次浏览

According to the official document, the mark decorator should look like below.

@pytest.mark.parametrize("arg1", ["StackOverflow"])
def test_mark_arg1(arg1):
assert arg1 == "StackOverflow" #Success
assert arg1 == "ServerFault" #Failed

Run

python -m pytest <filename>.py
  • Note1: function name must start with test_
  • Note2: pytest will redirect stdout (print), thus directly running stdout will not able to show any result on the screen. Also, there is no need to print result in your function in test cases.
  • Note3: pytest is a module run by python, which is not able to get sys.argv directly

If you really want to get outside configurable arguments, you should you implement that inside your script. (For example, loading content of file)

with open("arguments.txt") as f:
args = f.read().splitlines()
...
@pytest.mark.parametrize("arg1", args)
...

In your pytest test, don't use @pytest.mark.parametrize:

def test_print_name(name):
print ("Displaying name: %s" % name)

In conftest.py:

def pytest_addoption(parser):
parser.addoption("--name", action="store", default="default name")




def pytest_generate_tests(metafunc):
# This is called for every test. Only get/set command line arguments
# if the argument is specified in the list of test "fixturenames".
option_value = metafunc.config.option.name
if 'name' in metafunc.fixturenames and option_value is not None:
metafunc.parametrize("name", [option_value])

Then you can run from the command line with a command line argument:

pytest -s tests/my_test_module.py --name abc

I stumbled here looking for how to pass an argument, but I wanted to avoid parameterizing the test. The accepted answer does perfectly well address the exact question of parameterizing a test from the command line, but I would like to offer an alternative way to pass a command line argument to particular tests. The method below uses a fixture and skips the test if the fixture is specified but the argument is not:

test.py:

def test_name(name):
assert name == 'almond'

conftest.py:

import pytest


def pytest_addoption(parser):
parser.addoption("--name", action="store")


@pytest.fixture(scope='session')
def name(request):
name_value = request.config.option.name
if name_value is None:
pytest.skip()
return name_value

Examples:

$ py.test tests/test.py
=========================== test session starts ============================
platform linux -- Python 3.7.1, pytest-4.0.0, py-1.7.0, pluggy-0.8.0
rootdir: /home/ipetrik/dev/pytest_test, inifile:
collected 1 item


tests/test.py s                                                      [100%]


======================== 1 skipped in 0.06 seconds =========================

$ py.test tests/test.py --name notalmond
=========================== test session starts ============================
platform linux -- Python 3.7.1, pytest-4.0.0, py-1.7.0, pluggy-0.8.0
rootdir: /home/ipetrik/dev/pytest_test, inifile:
collected 1 item


tests/test.py F                                                      [100%]


================================= FAILURES =================================
________________________________ test_name _________________________________


name = 'notalmond'


def test_name(name):
>       assert name == 'almond'
E       AssertionError: assert 'notalmond' == 'almond'
E         - notalmond
E         ? ---
E         + almond


tests/test.py:5: AssertionError
========================= 1 failed in 0.28 seconds =========================

$ py.test tests/test.py --name almond
=========================== test session starts ============================
platform linux -- Python 3.7.1, pytest-4.0.0, py-1.7.0, pluggy-0.8.0
rootdir: /home/ipetrik/dev/pytest_test, inifile:
collected 1 item


tests/test.py .                                                      [100%]


========================= 1 passed in 0.03 seconds =========================

Use the pytest_addoption hook function in conftest.py to define a new option.
Then use pytestconfig fixture in a fixture of your own to grab the name.
You can also use pytestconfig from a test to avoid having to write your own fixture, but I think having the option have it's own name is a bit cleaner.

# conftest.py


def pytest_addoption(parser):
parser.addoption("--name", action="store", default="default name")
# test_param.py


import pytest


@pytest.fixture(scope="session")
def name(pytestconfig):
return pytestconfig.getoption("name")


def test_print_name(name):
print(f"\ncommand line param (name): {name}")


def test_print_name_2(pytestconfig):
print(f"test_print_name_2(name): {pytestconfig.getoption('name')}")
# in action


$ pytest -q -s --name Brian test_param.py


test_print_name(name): Brian
.test_print_name_2(name): Brian
.

Pass different values to a test function, depending on command line options
Suppose we want to write a test that depends on a command line option. Here is a basic pattern to achieve this:

# content of test_sample.py
def test_answer(cmdopt):
if cmdopt == "type1":
print("first")
elif cmdopt == "type2":
print("second")
assert 0  # to see what was printed


For this to work we need to add a command line option and provide the cmdopt through a fixture function:


# content of conftest.py
import pytest




def pytest_addoption(parser):
parser.addoption(
"--cmdopt", action="store", default="type1", help="my option: type1 or type2"
)




@pytest.fixture
def cmdopt(request):
return request.config.getoption("--cmdopt")

ref: https://docs.pytest.org/en/latest/example/simple.html#pass-different-values-to-a-test-function-depending-on-command-line-options

Then you can call it with:

pytest --cmdopt type1

If you are used to argparse, you can prepare it the usual way in arparse

import argparse
import sys


DEFAULT_HOST = test99
#### for --host parameter ###
def pytest_addoption(parser):
parser.addoption("--host")   # needed otherwhise --host will fail pytest


parser = argparse.ArgumentParser(description="run test on --host")
parser.add_argument('--host', help='host to run tests on (default: %(default)s)', default=DEFAULT_HOST)
args, notknownargs = parser.parse_known_args()
if notknownargs:
print("pytest arguments? : {}".format(notknownargs))
sys.argv[1:] = notknownargs


#
then args.hosts holds you variable, while sys.args is parsed further with pytest.

All you have to do is use pytest_addoption() in conftest.py and finally use request fixture:

# conftest.py


from pytest import fixture




def pytest_addoption(parser):
parser.addoption(
"--name",
action="store"
)


@fixture()
def name(request):
return request.config.getoption("--name")

And now you can run your test

def my_test(name):
assert name == 'myName'

using:

pytest --name myName

It's a bit of a workaround but it'll get the parameters into the test. Depending on the requirements, it could be enough.

def print_name():
import os
print(os.environ['FILENAME'])
pass

and then run the tests from the command-line:

FILENAME=/home/username/decoded.txt python3 setup.py test --addopts "-svk print_name"

Managed to get this to work with a Class of unittest.TestCase using answers here and https://docs.pytest.org/en/6.2.x/unittest.html

conftest.py:

import pytest


my_params = {
"name": "MyName",
"foo": "Bar",
}




def pytest_addoption(parser):
for my_param_name, my_param_default in my_params.items():
parser.addoption(f"--{my_param_name}", action="store", default=my_param_default)




@pytest.fixture()
def pass_parameters(request):
for my_param in my_params:
setattr(request.cls, my_param, request.config.getoption(f"--{my_param}"))


test_param.py

import unittest
import pytest




@pytest.mark.usefixtures("pass_parameters")
class TestParam(unittest.TestCase):
def test_it(self):
self.assertEqual(self.name, "MyName")

using:

pytest --name MyName