Sunday, February 03, 2013

A Little Python Partisanship

Although most of my time at The Job is spent in compiled languages, I have been trying to develop some skill in Python to enliven my technical skills.

This week, I got a chance to try something that I know would have been a real pain in a comp[iled language like C++.

The gist of the issue is that I want to automate some integration tests I'm writing for the system, so that I can just run them all with one command.  I am trying to make them as modular as possible so that I can add new ones easily, since I know from past experience that if it takes any significant time to add tests, I will not have the time to add them.  So I come up with a skeleton program that will run the tests, dividing them into preconditions that must pass, the test itself, and postconditions that must pass.  Now I run into the issue that at the moment I do not want to package my tests, so I need to import the modules directly and individually.  However, that makes it hard to add new tests, since the import list will grow with each test.

A quick search of and I find the import statement is a shortcut for __import__(), and __import__() can take runtime strings as arguments.  This means I can pass the test modules' names into the test skeleton.  A little help from exec/eval and I can call an arbitrary function from my test module based on command line input.

Here's the code I have so far:

d = {}
with open(sys.argv[1]) as f:
for line in f:
(key, val) = line.split()
d[key] = val

execStmt1 = d['modulename'] + " = __import__('" + d['modulename'] + "', globals(), locals(), [], -1)"
execStmt2 = d['modulename'] + '.' + d['functionname'] + '(d)'

print execStmt1
print execStmt2
exec execStmt1
exec execStmt2
targetFunction = eval(d['modulename'] + '.' + d['functionname'])

And the data file sent in looks like this:

modulename dummy
functionname dummy2
arg1 1
arg2 2

And the dummy module looks like this:

def dummy(arg1, arg2):
print "dummy(", arg1, ",",arg2,")"

def dummy2(d):
print "dummy2(",d['arg1'], ",", d['arg2'], ")"

if __name__ == '__main__':

So this gets me most of the way there.  I still need to rework how the preconditions and postconditions are run and their return values checked, and a few other things, but this was just so amazingly EASY to do, I thought it was worth a post