Monday, February 20, 2006

Python wish: a new meaning for "import ... as ..."

For Python modules that are meant to be imported, I usually try to put the testing code inside the module using the standard

if __name__ == "__main__":

block of code.

However, if the module is also meant to possibly be used by itself, then this approach doesn't work and the testing code has to be put in a separate module. I wish it would be possible to write something like

if __name__ == ''
SomeNameChosenForTestingPurpose":

inside this_module.py and that importing the module using

import
this_module as SomeNameChosenForTestingPurpose

would result in the variable __name__ inside this_module.py being assigned to SomeNameChosenForTestingPurpose. This would allow the inclusion of the testing code to be always kept inside a given Python module. To build a test suite, one would only need to have a module that does a series of "import ... as ..." statements.

As it is, __name__ is either equal to "__main__" or the original name of the Python file (this_module in the example above).

===In short, I would like to be able to design Python modules in this way:

some clever Python code

if __name__ == "__main__":
some clever Python code to be
executed when this module
is called as the main program.

if __name__ == "SomeNameChosenForTestingPurpose":
Some clever Python code
to be executed when
this module is imported as
SomeNameChosenForTestingPurpose

6 comments:

EY said...

What's wrong with, e.g.:

def run_tests():
    # do stuff

if __name__ == '__main__':
    run_tests()

Ian Bicking said...

With py.test (and maybe with nose, I'm not sure) you can just put the test in a function named test_*, and then run py.test file.py.

Anonymous said...

Interesting, but what happens when you import that module with different names in different places?

André Roberge said...

Jason:
You are quite right. I was totally sidetracked looking at some C code and its preprocessor directives, and was thinking of the possibility of having
if __name__ == ...
statements sprinkled throughout the file. But, of course, with the dynamic nature of Python, this can be emulated (and in a better way) by doing
import module
module.run_test()
where run_test() can set the various flags needed.

Ian: Excellent point; I should look at py.test (and other testing frameworks).

Peter: That is exactly what I wanted to be able to do (see above comment regarding pre-processor in C)... but is not needed.
====
Note to self: do not post at the end of a day staring at some non-Python code...

Anonymous said...

IMHO testing by using __name__ == "__main__" at the end of a module is bad practice. The tests will need to be run individually and probably manually, so they will hardly ever get run.

It is much better to have the tests in a separate file and written using a testing framework such as unittest or py.test. This allows you to easily run the tests for an individual module, class or method, or to run the entire suite of tests across all modules in a project. You can easily automate the tests to run on checkin or as part of an overnight build. This means that the tests will get run frequently and become an integral part of the development process instead of an occasional afterthought.

I recommend reading the book 'Test Driven Development by Example' by Kent Beck to see just how integrated the tests can be with the development.

Anonymous said...

While I know many say that having the unit tests in a separate file is the right approach, I've found that having a __main__ self-test is very handy because I develop in Emacs so I can hit "control-C control-C" and run the current module. This makes it the code/debug cycle very fast. Also, I can get the function definitions and uses with the same text searches (as when changing the API) and not need to scan two different files.

Once that's done I can move the self-tests elsewhere, which also means making the hard coded filenames (like to /home/somebody/whatever) more portable -- something I don't want to worry about when doing the initial development.