11

I am trying to pass arguments from a pytest testcase to a module being tested. For example, using the main.py from Python boilerplate, I can run it from the command line as:

$ python3 main.py
usage: main.py [-h] [-f] [-n NAME] [-v] [--version] arg
main.py: error: the following arguments are required: arg
$ python3 main.py xx
hello world
Namespace(arg='xx', flag=False, name=None, verbose=0)

Now I am trying to do the same with pytest, with the following test_sample.py

(NOTE: the main.py requires command line arguments. But these arguments need to be hardcoded in a specific test, they should not be command line arguments to pytest. The pytest testcase only needs to send these values as command line arguments to main.main().)

import main
def test_case01():
    main.main()
    # I dont know how to pass 'xx' to main.py,
    # so for now I just have one test with no arguments

and running the test as:

pytest -vs test_sample.py

This fails with error messages. I tried to look at other answers for a solution but could not use them. For example, 42778124 suggests to create a separate file run.py which is not a desirable thing to do. And 48359957 and 40880259 seem to deal more with command line arguments for pytest, instead of passing command line arguments to the main code.

I dont need the pytest to take command line arguments, the arguments can be hardcoded inside a specific test. But these arguments need to be passed as arguments to the main code. Can you give me a test_sample.py, that calls main.main() with some arguments?

R71
  • 4,283
  • 7
  • 32
  • 60
  • you could use monkeypatching. `def test_case01(monkeypatch): with monkeypatch.context() as m: m.setattr(sys, 'argv', ['my', 'dummy', 'args']); main()`. Inside the context block, the `sys.argv` list is monkeypatched with your values. – hoefling Jan 07 '19 at 15:40
  • Which are you testing - commandline interface, or the code behind it - the functions and classes that use the parsed `args`? – hpaulj Jan 07 '19 at 18:02
  • @hpaulj: I am testing the functions that use the parsed args. – R71 Jan 08 '19 at 08:18
  • @hoefling: I have accepted another answer, which is working for me with minimal code changes. But I did not understand your comment, would it be possible for you to add a detailed answer, so that maybe I can use your solution later? – R71 Jan 09 '19 at 05:28

2 Answers2

10

If you can't modify the signature of the main method, you can use the monkeypatching technique to temporarily replace the arguments with the test data. Example: imagine writing tests for the following program:

import argparse


def main():
    parser = argparse.ArgumentParser(description='Greeter')
    parser.add_argument('name')
    args = parser.parse_args()
    return f'hello {args.name}'


if __name__ == '__main__':
    print(main())

When running it from the command line:

$ python greeter.py world
hello world

To test the main function with some custom data, monkeypatch sys.argv:

import sys
import greeter

def test_greeter(monkeypatch):
    with monkeypatch.context() as m:
        m.setattr(sys, 'argv', ['greeter', 'spam'])
        assert greeter.main() == 'hello spam'

When combined with the parametrizing technique, this allows to easily test different arguments without modifying the test function:

import sys
import pytest
import greeter

@pytest.mark.parametrize('name', ['spam', 'eggs', 'bacon'])
def test_greeter(monkeypatch, name):
    with monkeypatch.context() as m:
        m.setattr(sys, 'argv', ['greeter', name])
        assert greeter.main() == 'hello ' + name

Now you get three tests, one for each of the arguments:

$ pytest -v test_greeter.py

...

test_greeter.py::test_greeter[spam] PASSED
test_greeter.py::test_greeter[eggs] PASSED
test_greeter.py::test_greeter[bacon] PASSED
hoefling
  • 59,418
  • 12
  • 147
  • 194
2

A good practice might to have this kind of code, instead of reading arguments from main method.

# main.py
def main(arg1):
    return arg1

if __name__ == '__main__':
    parser = argparse.ArgumentParser(description='My awesome script')
    parser.add_argument('word', help='a word')
    args = parser.parse_args()
    main(args.word)

This way, your main method can easily be tested in pytest

import main
def test_case01():
    main.main(your_hardcoded_arg)

I am not sure you can call a python script to test except by using os module, which might be not a good practice

BlueSheepToken
  • 5,751
  • 3
  • 17
  • 42
  • 1
    For someone else looking into this issue without requiring to get into monkeypatching, [here](http://euccas.github.io/blog/20160807/python-unittest-handle-command-line-arguments.html) is a nice solution as well. By the way, why has this accepted answer been downvoted? – Kiteloopdesign Jul 09 '20 at 10:53