0

I have written some unit tests using unittest in Python. However, they do not simply test objects in the conventional way - rather, they invoke another Python script by calling it using Popen. This is by design - it's a command line utility, so I want to test it as a user would, which includes things such as command-line options, etc.). To be clear, both the unit tests and the script to be tested are written in Python (v3 to be precise).

The script I am testing makes heavy use of datetime.now(), and ideally I would like to mock that value somehow so that I can keep it constant. All the examples I've seen of doing this, though (e.g. this one using mock) assume some form of white-box testing.

Is there a way for me to do this?

Community
  • 1
  • 1
Andrew Ferrier
  • 16,664
  • 13
  • 47
  • 76

1 Answers1

1

Nothing prevents you from testing your CLI without using Popen. You just need to architect your code to make it possible:

Instead of having this:

if __name__ == "__main__":
    import argparse
    parser = argparse.ArgumentParser()
    # ... Add args
    ns = parser.parse_args()

Do this:

import argparse

def main(argv):
    parser = argparse.ArgumentParser()
    # ... Add args
    parser.parse_args(argv[1:])  # This is the default for argparse
    ns = parser.parse_args()

if __name__ == "__main__":
    import sys
    main(sys.argv)

Then, you can test the main function in isolation (just call main([...]) with a set of args you specify). Note that this should also work (with some adaptation) for other CLI frameworks.


Also, note that if you're indeed using argparse, you'll need to patch ArgumentParser() so that it doesn't call sys.exit when parsing fails.

An easy way to do is to declare a ParsingError exception, and patch ArgumentParser.error(self, message) with:

def error(self, message):
    raise ParsingError(message) 

You can then use assertRaises in your tests.

Thomas Orozco
  • 53,284
  • 11
  • 113
  • 116
  • 1
    Note that this isn't hypothetical at all, and that you can see it in action in this project: https://github.com/Scalr/installer-ng/tree/master/wrapper/scalr-manage/scalr_manage — disclaimer: I wrote that code. – Thomas Orozco Dec 18 '14 at 14:49
  • That's a neat idea, I hadn't thought of that. It's a little more complex than that, since my script also does other stuff such as fiddle with sys.excepthook, which might affect the unittest script. But this gives me a good place to start. Thanks for the idea. – Andrew Ferrier Dec 18 '14 at 14:49
  • @AndrewFerrier You might be able to wrap `main(sys.argv)` in an `except` catch all and not have to fiddle with the excepthook (though this means you won't cover this in test). Also exemplified here ;) https://github.com/Scalr/installer-ng/blob/master/wrapper/scalr-manage/scalr_manage/cli.py#L98-L102 – Thomas Orozco Dec 18 '14 at 14:51