Python script that contains its own unit tests - python

I have a small python script and I want to add a handful of tests.
For a larger system I would have modules and separate tests from the system under test, but for this tiny thing I want to keep it all in one .py file. Then I can run:
> foo.py --test
to run the tests and
> foo.py
to run my script normally.
I got this far:
import unittest
import sys
import argparse
def main():
parser = argparse.ArgumentParser()
parser.add_argument("--test", action="store_true")
args = parser.parse_args()
if (args.test):
return unittest.main()
class TestBasicFunction(unittest.TestCase):
def test(self):
self.assertTrue(True)
if __name__ == '__main__':
sys.exit(main())
But it fails because unittest.main() tries to parse the arguments to my script.
I am not picky about unit test framework. py test, nose, whatever is fine.
How can I do this?

Note that there are reasons you might want to consider separating unittests from the code it is testing.
However, here is how you could do it:
import unittest
import sys
import argparse
def main():
parser = argparse.ArgumentParser()
parser.add_argument("--test", action="store_true")
args = parser.parse_args()
print(args)
if args.test:
sys.argv.remove('--test')
return unittest.main(argv=sys.argv)
class TestBasicFunction(unittest.TestCase):
def test(self):
self.assertTrue(True)
if __name__ == '__main__':
sys.exit(main())

Related

How to exclude the program startup code (__name__ == "__main__") from being included in pytest coverage reports?

We have several small scripts in our project that take some command-line arguments. For example:
if __name__ == "__main__":
parser = argparse.ArgumentParser()
parser.add_argument("--branch")
command_line_args = parser.parse_args()
if not command_line_args.branch:
raise Exception
main(command_line_args.branch)
we are not really interested in unit-test this. However, this impacts our coverage report, is there a way to exclude the if __name__ == "__main__" from unit-tests using pytest?
You can add a comment, like this:
if __name__ == "__main__": # pragma: no cover
parser = argparse.ArgumentParser()
...

how to elegantly parse argumens in python before expensive imports?

I have a script, which parses a few arguments, and has some expensive imports, but those imports are only needed if the user gives valid input arguments, otherwise the program exits. Also, when the user says python script.py --help, there is no need for those expensive imports to be executed at all.
I can think of such a script:
import argparse
def parse_args():
parser = argparse.ArgumentParser()
parser.add_argument('--argument', type=str)
args = parser.parse_args()
return args
if __name__ == "__main__":
args = parse_args()
import gensim # expensive import
import blahblahblah
def the_rest_of_the_code(args):
pass
if __name__ == "__main__":
the_rest_of_the_code(args)
This does the job, but it doesn't look elegant to me. Any better suggestions for the task?
EDIT: the import is really expensive:
$ time python -c "import gensim"
Using TensorFlow backend.
real 0m12.257s
user 0m10.756s
sys 0m0.348s
You can import conditionally, or in a try block, or just about anywhere in code.
So you could do something like this:
import cheaplib
if __name__ == "__main__":
args = parse_args()
if expensive_arg in args:
import expensivelib
do_stuff(args)
Or even more clearly, only import the lib in the function that will use it.
def expensive_function():
import expensivelib
...
Not sure it's better than what you already have, but you can load it lazily:
def load_gensim():
global gensim
import gensim
If you only want to make sure the arguments make sense, you can have a wrapper main module that checks the arguments and then loads another module and call it.
main.py:
args = check_args()
if args is not None:
import mymodule
mymodule.main(args)
mymodule.py:
import gensim
def main(args):
# do work

Python unittesting: run tests in another module

I want to have the files of my application under the folder /Files, whereas the test units in /UnitTests, so that I have clearly separated app and test.
To be able to use the same module routes as the mainApp.py, I have created a testController.py in the root folder.
mainApp.py
testController.py
Files
|__init__.py
|Controllers
| blabla.py
| ...
UnitTests
|__init__.py
|test_something.py
So if in test_something.py I want to test one function that is in /Files/Controllers/blabla.py, I try the following:
import unittest
import Files.Controllers.blabla as blabla
class TestMyUnit(unittest.TestCase):
def test_stupid(self):
self.assertTrue(blabla.some_function())
if __name__ == '__main__':
unittest.main()
And then from the file testController.py, I execute the following code:
import TestUnits.test_something as my_test
my_test.unittest.main()
Which outputs no failures, but no tests executed
----------------------------------------------------------------------
Ran 0 tests in 0.000s
OK
[Finished in 0.3s]
I have tried with a test that has no dependences, and if executed as "main" works, but when called from outside, outputs the same:
import unittest
def tested_unit():
return True
class TestMyUnit(unittest.TestCase):
def test_stupid(self):
self.assertTrue(tested_unit())
if __name__ == '__main__':
unittest.main()
Question: how do I get this to work?
The method unittest.main() looks at all the unittest.TestCase classes present in the context.
So you just need to import your test classes in your testController.py file and call unittest.main() in the context of this file.
So your file testController.py should simply look like this :
import unittest
from UnitTests.test_something import *
unittest.main()
In test_something.py, do this:
def suite():
suite = unittest.TestSuite()
suite.addTest(unittest.makeSuite(TestMyUnit, 'test'))
return suite
In testController.py, do this:
from TestUnits import test_something
def suite():
suite = unittest.TestSuite()
suite.addTest(test_something.suite())
return suite
if __name__ == '__main__':
unittest.main(defaultTest='suite')
There is a workaround of using subprocess.call() to run tests, like:
import subprocess
args = ["python", "test_something.py"]
subprocess.call(args)

In Python, can I call the main() of an imported module?

In Python I have a module myModule.py where I define a few functions and a main(), which takes a few command line arguments.
I usually call this main() from a bash script. Now, I would like to put everything into a small package, so I thought that maybe I could turn my simple bash script into a Python script and put it in the package.
So, how do I actually call the main() function of myModule.py from the main() function of MyFormerBashScript.py? Can I even do that? How do I pass any arguments to it?
It's just a function. Import it and call it:
import myModule
myModule.main()
If you need to parse arguments, you have two options:
Parse them in main(), but pass in sys.argv as a parameter (all code below in the same module myModule):
def main(args):
# parse arguments using optparse or argparse or what have you
if __name__ == '__main__':
import sys
main(sys.argv[1:])
Now you can import and call myModule.main(['arg1', 'arg2', 'arg3']) from other another module.
Have main() accept parameters that are already parsed (again all code in the myModule module):
def main(foo, bar, baz='spam'):
# run with already parsed arguments
if __name__ == '__main__':
import sys
# parse sys.argv[1:] using optparse or argparse or what have you
main(foovalue, barvalue, **dictofoptions)
and import and call myModule.main(foovalue, barvalue, baz='ham') elsewhere and passing in python arguments as needed.
The trick here is to detect when your module is being used as a script; when you run a python file as the main script (python filename.py) no import statement is being used, so python calls that module "__main__". But if that same filename.py code is treated as a module (import filename), then python uses that as the module name instead. In both cases the variable __name__ is set, and testing against that tells you how your code was run.
Martijen's answer makes sense, but it was missing something crucial that may seem obvious to others but was hard for me to figure out.
In the version where you use argparse, you need to have this line in the main body.
args = parser.parse_args(args)
Normally when you are using argparse just in a script you just write
args = parser.parse_args()
and parse_args find the arguments from the command line. But in this case the main function does not have access to the command line arguments, so you have to tell argparse what the arguments are.
Here is an example
import argparse
import sys
def x(x_center, y_center):
print "X center:", x_center
print "Y center:", y_center
def main(args):
parser = argparse.ArgumentParser(description="Do something.")
parser.add_argument("-x", "--xcenter", type=float, default= 2, required=False)
parser.add_argument("-y", "--ycenter", type=float, default= 4, required=False)
args = parser.parse_args(args)
x(args.xcenter, args.ycenter)
if __name__ == '__main__':
main(sys.argv[1:])
Assuming you named this mytest.py
To run it you can either do any of these from the command line
python ./mytest.py -x 8
python ./mytest.py -x 8 -y 2
python ./mytest.py
which returns respectively
X center: 8.0
Y center: 4
or
X center: 8.0
Y center: 2.0
or
X center: 2
Y center: 4
Or if you want to run from another python script you can do
import mytest
mytest.main(["-x","7","-y","6"])
which returns
X center: 7.0
Y center: 6.0
It depends. If the main code is protected by an if as in:
if __name__ == '__main__':
...main code...
then no, you can't make Python execute that because you can't influence the automatic variable __name__.
But when all the code is in a function, then might be able to. Try
import myModule
myModule.main()
This works even when the module protects itself with a __all__.
from myModule import * might not make main visible to you, so you really need to import the module itself.
I had the same need using argparse too.
The thing is parse_args function of an argparse.ArgumentParser object instance implicitly takes its arguments by default from sys.args. The work around, following Martijn line, consists of making that explicit, so you can change the arguments you pass to parse_args as desire.
def main(args):
# some stuff
parser = argparse.ArgumentParser()
# some other stuff
parsed_args = parser.parse_args(args)
# more stuff with the args
if __name__ == '__main__':
import sys
main(sys.argv[1:])
The key point is passing args to parse_args function.
Later, to use the main, you just do as Martijn tell.
The answer I was searching for was answered here: How to use python argparse with args other than sys.argv?
If main.py and parse_args() is written in this way, then the parsing can be done nicely
# main.py
import argparse
def parse_args():
parser = argparse.ArgumentParser(description="")
parser.add_argument('--input', default='my_input.txt')
return parser
def main(args):
print(args.input)
if __name__ == "__main__":
parser = parse_args()
args = parser.parse_args()
main(args)
Then you can call main() and parse arguments with parser.parse_args(['--input', 'foobar.txt']) to it in another python script:
# temp.py
from main import main, parse_args
parser = parse_args()
args = parser.parse_args([]) # note the square bracket
# to overwrite default, use parser.parse_args(['--input', 'foobar.txt'])
print(args) # Namespace(input='my_input.txt')
main(args)
Assuming you are trying to pass the command line arguments as well.
import sys
import myModule
def main():
# this will just pass all of the system arguments as is
myModule.main(*sys.argv)
# all the argv but the script name
myModule.main(*sys.argv[1:])
I hit this problem and I couldn't call a files Main() method because it was decorated with these click options, eg:
# #click.command()
# #click.option('--username', '-u', help="Username to use for authentication.")
When I removed these decorations/attributes I could call the Main() method successfully from another file.
from PyFileNameInSameDirectory import main as task
task()

how to add dozen of test cases to a test suite automatically in python

i have dozen of test cases in different folders. In the root directory there is a test runner.
unittest\
package1\
test1.py
test2.py
package2\
test3.py
test4.py
testrunner.py
Currently I added the four test cases manually into a test suite
import unittest
from package1.test1 import Test1
from package1.test2 import Test2
from package2.test3 import Test3
from package2.test4 import Test4
suite = unittest.TestSuite()
suite.addTests(unittest.makeSuite(Test1))
suite.addTests(unittest.makeSuite(Test2))
suite.addTests(unittest.makeSuite(Test3))
suite.addTests(unittest.makeSuite(Test4))
result = unittest.TextTestRunner(verbosity=2).run(suite)
if not result.wasSuccessful():
sys.exit(1)
How to let test runner test all test cases automatically? Such as:
for testCase in findTestCases():
suite.addTests(testCase)
In my opinion you should switch to unittest2 or other test frameworks with discovery features. Discovery tests is a really sane way to run them.
Most known are:
nosetests
py.test
For example with nosetest is sufficient to run nosetests from the project root directory and it will discover and run all the unit tests it find. Pretty simple.
Notice also that unittest2 will be included in python 2.7 (and backported till 2.4 I guess).
The above modules are good but NoseTests can be funny when trying to enter in parameters and this is also faster and fit's into another module nicely.
import os, unittest
class Tests():
def suite(self): #Function stores all the modules to be tested
modules_to_test = []
test_dir = os.listdir('.')
for test in test_dir:
if test.startswith('test') and test.endswith('.py'):
modules_to_test.append(test.rstrip('.py'))
alltests = unittest.TestSuite()
for module in map(__import__, modules_to_test):
module.testvars = ["variables you want to pass through"]
alltests.addTest(unittest.findTestCases(module))
return alltests
if __name__ == '__main__':
MyTests = Tests()
unittest.main(defaultTest='MyTests.suite')
If you want to add results to a log file add this at the end instead:
if __name__ == '__main__':
MyTests = Tests()
log_file = 'log_file.txt'
f = open(log_file, "w")
runner = unittest.TextTestRunner(f)
unittest.main(defaultTest='MyTests.suite', testRunner=runner)
Also at the bottom of the modules you are testing place code like this:
class SomeTestSuite(unittest.TestSuite):
# Tests to be tested by test suite
def makeRemoveAudioSource():
suite = unittest.TestSuite()
suite.AddTest(TestSomething("TestSomeClass"))
return suite
def suite():
return unittest.makeSuite(TestSomething)
if __name__ == '__main__':
unittest.main()
What I did is a wrapper script which running separate test files:
Main wrapper run_tests.py:
#!/usr/bin/env python3
# Usage: ./run_tests.py -h http://example.com/ tests/**/*.py
import sys, unittest, argparse, inspect, logging
if __name__ == '__main__':
# Parse arguments.
parser = argparse.ArgumentParser(add_help=False)
parser.add_argument("-v", "--verbose", action="store_true", dest="verbose", help="increase output verbosity" )
parser.add_argument("-d", "--debug", action="store_true", dest="debug", help="show debug messages" )
parser.add_argument("-h", "--host", action="store", dest="host", help="Destination host" )
parser.add_argument('files', nargs='*')
args = parser.parse_args()
# Load files from the arguments.
for filename in args.files:
exec(open(filename).read())
# See: http://codereview.stackexchange.com/q/88655/15346
def make_suite(tc_class):
testloader = unittest.TestLoader()
testnames = testloader.getTestCaseNames(tc_class)
suite = unittest.TestSuite()
for name in testnames:
suite.addTest(tc_class(name, cargs=args))
return suite
# Add all tests.
alltests = unittest.TestSuite()
for name, obj in inspect.getmembers(sys.modules[__name__]):
if inspect.isclass(obj) and name.startswith("FooTest") and len(name) > len("FooTest"):
alltests.addTest(make_suite(obj))
# Run tests.
result = unittest.TextTestRunner(verbosity=2).run(alltests)
sys.exit(not result.wasSuccessful())
Then another wrapper for tests:
class FooTest(unittest.TestCase):
def __init__(self, *args, cargs=None, **kwargs):
super().__init__(*args, **kwargs)
self.vdisplay = Xvfb(width=1280, height=720)
self.vdisplay.start()
self.args=cargs
self.log=logging
def setUp(self):
self.site = webdriver.Firefox()
def kill(self):
self.vdisplay.stop()
Then each test in separate files would look like:
import sys, os, unittest
from FooTest import FooTest
class FooTest1(FooTest):
def test_homepage(self):
self.site.get(self.base_url + "/")
log.debug("Home page loaded.")
Then you can easily run tests from shell like:
$ ./run_tests.py -h http://example.com/ test1.py test2.py
You can use wildcard to specify all files within certain directories, or use a new globbing option (**) to run all tests recursively (enable by shopt -s globstar).

Categories

Resources