127

I have a code and I need to pass the arguments like name from terminal. Here is my code and how to pass the arguments. I am getting a "File not found" kind error that I don't understand.

I have tried the command in the terminal: pytest <filename>.py -almonds I should get the name printed as "almonds"

@pytest.mark.parametrize("name")
def print_name(name):
    print ("Displaying name: %s" % name)
sophros
  • 14,672
  • 11
  • 46
  • 75
ashish sarkar
  • 1,289
  • 2
  • 9
  • 7
  • Something to think about is that pytest really wants you to be able to specify multiple test files on the command line. In that case, what happens to command line arguments? Does everyone use -almonds? What if two different tests want different arguments? – James Moore Feb 17 '21 at 17:22

10 Answers10

103

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
clay
  • 18,138
  • 28
  • 107
  • 192
  • What is @pytest.mark.unit? Why do you use it? It seems that your code works without it, can I omit it? – pavel_orekhov May 25 '19 at 21:51
  • 3
    Don't use it. I removed it from the answer. In the past, it was supported and even recommended in older versions of pytest. In newer versions of pytest, it has been removed and it isn't supported. – clay May 28 '19 at 21:07
  • 4
    What happens when you use test classes? :) – Roelant Sep 04 '19 at 13:04
  • Can you also please point out how to add an argument to the list of test "fixturenames", as you said is needed in your answer. – dor00012 Sep 08 '20 at 07:16
  • 1
    One can see an explanation for pytest_generate_tests on [pytest documentation](https://docs.pytest.org/en/stable/parametrize.html#basic-pytest-generate-tests-example) – dor00012 Sep 08 '20 at 07:31
  • Does anyone know how to add multiple arguments? I am guessing `metafunc.parametrize("name", [option_value])` has to be changed. – tash Jan 04 '21 at 00:23
  • `ERROR: file or directory not found: abc` – Gulzar Feb 15 '22 at 16:09
91

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
.
Gulzar
  • 23,452
  • 27
  • 113
  • 201
Okken
  • 2,536
  • 21
  • 15
  • I followed this pattern, and in my case also added a pytest mark `@pytest.mark.model_diagnostics` to delineate those tests that require an input, e.g. `pytest -m model_diagnostics --fp-model=./model.h5`. This also requires "registering" of your mark, for example in your `pytest.ini`. – ryanjdillon Mar 03 '22 at 09:33
51

I stumbled here looking for how to pass an argument, but I wanted to avoid parameterizing the test. @clay's top answer 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 =========================
ipetrik
  • 1,749
  • 18
  • 28
  • 2
    ```python3 -m pytest test.py --name qwe``` gives error: ```pytest.py: error: unrecognized arguments: --name qwe```. I have no py.test, what should I do in this case, could you please clarify? – ged Jan 15 '20 at 10:59
  • 2
    @ged - calling it the way you called it works for me. Please note that you should have two files - conftest.py and test.py. I have edited the answer to make this more clear. – ipetrik Jan 18 '20 at 18:37
  • There hasn't been an accepted answer with the green check, so which one are you referring to? – thewhiteambit Mar 29 '23 at 11:08
  • Fair point - I don't know why I thought @clay's answer was accepted when I wrote this, but I guess it is just the top voted answer. Updated my answer. – ipetrik Mar 30 '23 at 15:43
25

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
Giorgos Myrianthous
  • 36,235
  • 20
  • 134
  • 156
5

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"
krissy
  • 81
  • 1
  • 5
3

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
ankostis
  • 8,579
  • 3
  • 47
  • 61
Mohamed.Abdo
  • 2,054
  • 1
  • 19
  • 12
2

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)
...
Kir Chou
  • 2,980
  • 1
  • 36
  • 48
2

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
ili
  • 405
  • 4
  • 6
0

I read a lot about this and I was really confused. I finally figured it out and here is what I did:

First create a file name: conftest.py Second Add the following code to it:

# this is a function to add new parameters to pytest
def pytest_addoption(parser):
    parser.addoption(
        "--MyParamName", action="store", default="defaultParam", help="This is a help section for the new param you are creating"
    )
# this method here makes your configuration global
option = None
def pytest_configure(config):
    global option
    option = config.option

Finally you will access your newly create params using a fixture to expose the param in your desired code:

@pytest.fixture
def myParam(request):
    return request.config.getoption('--MyParamName')

Here is how you will use the newly create param being passed in your pytest execution

# command to run pytest with newly created param
$ pytest --MyParamName=myParamValue

Location where the newly param fixture will be used: Example python test where param will be used:

Test_MyFucntion(myParam)
JEuvin
  • 866
  • 1
  • 12
  • 31
-4

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.
MortenB
  • 2,749
  • 1
  • 31
  • 35