3

I want to test a python file with pytest which contains a if __name__ == '__main__': it also has arguments parsed in it. the code is something like this:

if __name__ == '__main__':
    parser = argparse.ArgumentParser(description='Execute job.')

    parser.add_argument('--env', required=True, choices=['qa', 'staging', 'prod'])

    args = parser.parse_args()

    # some logic

The limitation here is I cannot add a main() method and wrap the logic in if __name__ == '__main__': inside it and run it from there! The code is a legacy code and cannot be changed!

I want to test this with pytest, I wonder how can I run this python file with some arguments inside my test?

Sasan Ahmadi
  • 567
  • 4
  • 19
  • 1
    What if your test found a bug? Would you not be able to fix it? – chepner Jan 10 '22 at 18:28
  • Does this answer your question? https://stackoverflow.com/q/18668947/3141234 – Alexander Jan 10 '22 at 18:29
  • And if you can't change it, there's no point writing tests for it. Either you verify it works using ad-hoc, one-time tests and you never need to run further tests again, or you verify it doesn't work and you have to replace it with something that *does* work (and can be properly tested). – chepner Jan 10 '22 at 18:30
  • this is thoughtful but not answering my case, of course I will change the code if a bug found or if I have to. But question here is if it is possible to have such a test case which is running the file from its entry phase with arguments defined. – Sasan Ahmadi Jan 10 '22 at 18:38
  • 1
    Then consider the lack of a `main` function a bug, and fix it. – chepner Jan 10 '22 at 18:39
  • @Alexander patching the arg could be helping but how to run the file from entry phase? – Sasan Ahmadi Jan 10 '22 at 18:39
  • @chepner I will ultimately have to do this if I couldn't find another answer, honestly I am a bit curious to see if there is anyway doing this. At the end it can be a good learning thing. – Sasan Ahmadi Jan 10 '22 at 18:41
  • 1
    You might be able to do something with `with open("script.py") as f: exec(f.read())`, but I would sooner fix the file than mess with `exec`. – chepner Jan 10 '22 at 18:43
  • 1
    This answers your question -- https://stackoverflow.com/a/51736889/13261176 – Teejay Bruno Jan 11 '22 at 00:39
  • thanks @TeejayBruno – Sasan Ahmadi Jan 11 '22 at 16:42

2 Answers2

3

Command line arguments are an input/output mechanism like any other. The key is to isolate it in a "boundary layer", and have your main program not depend on them directly.

In this case, rather than making your program access sys.argv directly (which is essentially a global variable), make it so your program is wrapped in an "entry point" function (e.g. "main") that takes args as an explicit parameter.

If you don't pass any arguments to ArgumentParser.parse_args, defaults to accessing sys.argv for you. Instead, just pass along your args param.

It might look something like:

def main(args):
    # some logic
    pass

if __name__ == '__main__':
    main(sys.arv)

Your unit tests can call this entry point function and pass in any args they want:

def test_main_does_foo_when_bar():
   result = main(["bar"])
   assert "foo" == result
Alexander
  • 59,041
  • 12
  • 98
  • 151
0

Generally I make a main function which always takes arguments and call it with command line arguments when needed.

def main(args):
    # parser : argparse.ArgumentParser
    args = parser.parse_args(args)
    pass

if __name__ == '__main__':
    main(sys.argv[1:])

When run from command line it will be called with command line arguments. It can be called at anytime by calling main directly.

MYousefi
  • 978
  • 1
  • 5
  • 10