4

I have a script using os.environ.get() to get variable from command line something like JENKINS_HOST="xx" JENKINS_AUTH="xx" JENKINS_TOKEN="xx" python script.py

In script.py has a function it likes this:

def init_auth():
    login_mode = True
    JENKINS_HOST = os.environ.get("JENKINS_HOST")
    JENKINS_AUTH = os.environ.get("JENKINS_AUTH")
    JENKINS_TOKEN = os.environ.get("JENKINS_TOKEN")

when I use pytest to test the function init_auth(), how could I transfer the cli environment to this function?

Saluton
  • 117
  • 1
  • 2
  • 9

2 Answers2

4

I'm not sure I quite understood your question.. basically, instead of retrieving values from the environment, you want to retrieve them from the CLI?

If so, one way I did that was by creating a conftest.py file in the same directory as the test and use the pytest_addoption and pytest_generate_tests hooks.

conftest.py:

def pytest_addoption(parser):
    """
    Add CLI options to `pytest` to pass those options to the test cases.
    These options are used in `pytest_generate_tests`.
    """
    parser.addoption('--jenkins-host')
    parser.addoption('--jenkins-auth')
    parser.addoption('--jenkins-token')

def pytest_generate_tests(metafunc):
    metafunc.parametrize(
        'jenkins_host, jenkins_auth, jenkins_token',
        [(metafunc.config.getoption('jenkins_host'),
          metafunc.config.getoption('jenkins_auth'),
          metafunc.config.getoption(jenkins_token'))]

TestFile.py

class TestThis:
    def test_my_thing(jenkins_host, jenkins_auth, jenkins_token):
        # Do tests here

CLI

pytest TestFile.py --jenkins-host "http://my-jenkinshost.com" --jenkins-auth whatever --jenkins-token THE_TOKEN

The arguments in the test case are parametrized (the equivalent of adding the annotation @pytest.mark.parametrize(...) in pytest_generate_tests.

This works well and it's fully supported by pytest. It's a basic example as there's a lot more you can do. See here more info on how these and other hooks work.

Joao Coelho
  • 2,838
  • 4
  • 30
  • 36
  • I think the missing part of this is that the `init_auth` is getting called inside the tests, and the code can't be changed to pass these variables in. This means the `test_my_thing` function will probably start with a bunch of `os.environ.set('JENKINS_HOST', jenkins_host)` etc. And probably some additional code to get the initial values and restore them when the test completes (with pass or fail). You need to restore them as otherwise they may pollute other tests. – Michael Anderson Mar 08 '19 at 01:34
  • `config.getoption(..)` has a `default` parameter, so the lines can be changed to `metafunc.config.getoption('jenkins_host', default= os.environ.get('JENKINS_HOST'))` which then uses the environment var if not defined in the CLI. https://docs.pytest.org/en/latest/reference.html#_pytest.config.Config.getoption – Joao Coelho Mar 08 '19 at 02:52
  • Thanks, both of you, this is what I want. I hope I can get variables from CLI like this pytest xxx.py --jenkins-host "http://my-jenkinshost.com" --jenkins-auth="xxxx". I didn' t know that there are hooks in pytest, I am a beginner for pytest. – Saluton Mar 08 '19 at 07:12
1

Lets assume you want to test a function like this:

def foo():
    something = os.environ.get("SOMETHING")
    if something=="BAD":
       raise Exception("BAM")

It is used in a little application like this:

def main():
   try:
      foo()
      print("OK")
   except:
      print("SAD")

I can specify environment values on the command line that eventually get to foo

$ SOMETHING=BAD ./my_app
SAD
$ SOMETHING=ELSE ./my_app
OK

Now, I want to test the behaviour of foo, but I need to be careful not to change my environment settings, so I need to set and restore them, even should the test go bad. That looks something like:

def test_foo_ok():
  orig = os.environ.get("SOMETHING")
  os.environ["SOMETHING"]="ELSE"
  try:
     foo()
  finally:
     if orig is None:
        del os.environ["SOMETHING"]
     else:
        os.environ["SOMETHING"]=orig

def test_foo_bad():
  orig = os.environ.get("SOMETHING")
  os.environ["SOMETHING"]="BAD"
  try:
    with pytest.raises(Exception) as excinfo:   
        foo()   
    assert str(excinfo.value) == "BAM" 
  finally:
     if orig is None:
        del os.environ["SOMETHING"]
     else:
        os.environ["SOMETHING"]=orig

You could wrap the boilerplate in those into a decorator if you wanted. I'd call it something like @testwithenv then the tests would look cleaner.

@testwithenv({"SOMETHING","ELSE"})
def test_foo_ok():
  foo()

@testwithenv({"SOMETHING","BAD"})
def test_foo_bad():
  with pytest.raises(Exception) as excinfo:   
    foo()   
  assert str(excinfo.value) == "BAM" 
Michael Anderson
  • 70,661
  • 7
  • 134
  • 187