Göktuğ Kayaalp <s...@gkayaalp.com> writes: > I'm using python to write command line apps from time to time. I > wonder *what is the conventional way to test-run these apps from > within the project itself, while developing, without installing*.
As with making any application testable: Make small functions that do one thing well, with narrow, clearly-defined interfaces. With testing the module which will be run as the program, though, there are special considerations; in particular, command-line parsing and system exit. So the advice for testing the program's main module specialises to: Keep the body of “if __name__ == '__main__':” to an absolute minimum. Put all of the set-up and process-end functionality into discrete functions with discrete purposes, clear return values, and explicit parameters. That, for me, usually looks like this (or something with fewer moving parts if the application doesn't need them):: import argparse class FrobApplication: """ An application to frobnicate spiggleworts. """ def __init__(self): """ Set up a new instance of FrobApplication. """ self.argument_parser = self.make_argument_parser() def make_argument_parser(self): """ Make an argument parser for this application. """ parser = argparse.ArgumentParser( description="Frobnicate one or more spiggleworts.") parser.add_argument(…) … self.argument_parser = parser def parse_args(self, argv): """ Parse command-line arguments to this application. :param argv: The full sequence of command-line arguments. :return: None. """ self.program_name = argv[0] args = self.argument_parser.parse_args(argv[1:]) self.foo_flag = … def run(self): """ Run the application. """ self.argument_parser.parse_args( … def main(argv=None): """ Mainline code for this module. :param argv: The full sequence of command-line arguments. If ``None``, the Python runtime ``sys.argv`` is used. :return: The exit code for the process. """ if argv is None: from sys import argv as sys_argv argv = sys_argv exit_code = 0 try: frob_app = FrobApplication() frob_app.parse_args(argv) frob_app.main() except SystemExit, exc: exit_code = exc.code return exit_code if __name__ == '__main__': exit_code = __main__(sys.argv) sys.exit(exit_code) This is adapted from an article by Guido van Rossum <URL:http://www.artima.com/weblogs/viewpost.jsp?thread=4829>. The purpose of the distinct module-level ‘main’ is to turn into a normal function interface what Python usually handles in less-easily-tested ways: You want to normally use ‘sys.argv’, which ‘argparse’ will do by default; but you want the same code to also accept your own value for testing. You normally want to raise ‘SystemExit’ from any appropriate point in your application and have that exit the program with the exit code; but when testing, you just want a return value. This allows you to test each of the parts with a narrow interface: you can call ‘main’ feeding it your constructed command line, and getting a return value instead of a System Exit exception. You can test the application as a class: just the application initialisation, just the command-line parsing, just the main run routine. And so on. Yes, it's a whole lot of scaffolding; and no, I don't always use it all. But making testable code entails dividing your code into small, easily isolated, easily testable units. Sometimes that means you need to set up a bunch of divisions where normally Python takes care of it all — such as in the handling of command-line arguments or the exit of the process. -- \ “That's all very good in practice, but how does it work in | `\ *theory*?” —anonymous | _o__) | Ben Finney -- https://mail.python.org/mailman/listinfo/python-list