Pat77
Pat77

Reputation: 21

pytest - command line argument outside test function

How to pass command line option as variable to my pytest non test file e.g. database name.

When i try:

import sys
import getopt


"""argv = sys.argv[1:]

opts, args = getopt.getopt(argv, "e:")

for opt, arg in opts:
    if opt in ['-e']:
        if arg == "test1":
            base_url = "url-test1.com"
            db_name = "db_test1"
        elif arg == 'test2':
            base_url = "url-test2.com"
            db_name = "db_test2"
        elif arg == 'test3':
            base_url = "url-test3.com"
            db_name = "db_test3"
    return base_url

and run

 python -m pytest -e test1

looks like pytest can't get -e flag

ERROR: usage: main.py [options] [file_or_dir] [file_or_dir] [...]

main.py: error: unrecognized arguments: -e

inifile: None

I also try pytest addoption and passing variable to test files works fine but how to pass cmnd line option as value to non test file?

def pytest_addoption(parser):
    parser.addoption("--url", action="store", default="url-test1.com")
    parser.addoption("--db", action="store", default="test1")

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

def db_name(request):
    return request.config.getoption("--db") #I want to pass this value to mysql.connector as database=db_name

EDIT 1

so my db_connect.py lokks like that

import mysql.connector
import argparse


def parse_args():
    parser = argparse.ArgumentParser()
    parser.add_argument('--db', required=True, type=str, help="Your database name")
    return parser.parse_args()


def main():
    args = parse_args()
    db = args.db
    return db


mydb = mysql.connector.connect(
    user='username',
    password='password',
    host='host',
    database=main()
)


if __name__ == '__main__':
    main()

and when i try to run

py.test --db test1

I got this error

ERROR: usage: py.test [options] [file_or_dir] [file_or_dir] [...]
py.test: error: unrecognized arguments: --db
  inifile: None

but when i run

py.test

i got

usage: py.test [-h] --db DB
py.test: error: the following arguments are required: --db

argument is required but when i pass it is unrecognized. How to handle it?

Upvotes: 2

Views: 1912

Answers (1)

edd
edd

Reputation: 1417

Welcome!

Specifically to "override" variables, modules and objects you should mock them. Mocking in testing refers to creating a fake object with similar behavior to the original one when creating the real object is expensive. Such as database connections. But, naturally, it isn't restricted to just databases. You can mock any object, as well as sys.argv.
You can read more extensively about mocking in the pytest docs but here's a short example

import module_to_test


def mytest(monkeypatch):
    """
    Mocks the configuration parameters values.
    """
    monkeypatch.setattr(module_to_test.sys, 'argv', ['somescript.py', '--db'])

That being said, I strongly recommend you do not use getopt. That is a deprecated method to parse arguments from the world of bash. There is a strong package called argparse that entirely replaces any such argument boilerplate code.

import argpase


def parse_args():
    """
    Parse arguments given in the command line. Expects just "--db"
    """
    parser = argparse.ArgumentParser()
    parser.add_argument('--db', required=True, type=str, help="Your DB name")
    return parser.parse_args()


def main():
    args = parse_args()
    db = args.db
    print(f"Wrap 10 to {db}. Engage!")


if __name__ == '__main__':
    main()

argparse docs

edit 1

Great work on that argparse!

Now, you can simply mock it. You don't need it parsing the command line, anymore. You want to control what it returns. So, this time, when you use monkeypatch to mock argparse.ArgumentParser, you'll pass in your own "dummy class" that does nothing but return fixed arguments when parser.parse_args() is called. Here's an example of such a class (you'll probably want to tweak it)

from collections import namedtuple


class DummyParser:
    def add_argument(self, *_, **__):
        """
        We know what arguments we want, we don't need to implement this.
        """
        pass

    def parse_args():
        """
        Money time!
        """
        fake_return_class = namedtuple('Namespace',
                                       ['db', 'the value we want for db'])
        args = fake_return_class(db="the value we want")
        return args


fake_parser = DummyParser()
fake_args = fake_parser.parse_args()
print(fake_args.db)

One tweak could be to make it a little more reusable and add your own constructor of what'll db be equal to.

Upvotes: 1

Related Questions