I’ve recently come across a problem regarding the unittest module in Python when using command-line arguments. This problem occurs both with Python 2.7 and 3.3. Here is a simple example of a Python test file my_tests.py to demonstrate this problem:
import unittest import sys from production_code import get_thing class MyTests(unittest.TestCase): def testFirstThing(self): result = get_thing("first", command_line_param) self.assertEqual("new_first", result) def testSecondThing(self): result = get_thing("second", command_line_param) self.assertEqual("new_second", result) if __name__ == '__main__': if len(sys.argv) != 2: sys.exit("ERROR: A command-line parameter must be supplied for these tests") command_line_param = sys.argv[1] unittest.main()
When I run this script with
python my_tests.py foo
, I get the following output:
Traceback (most recent call last): File "my_tests.py", line 18, in unittest.main() File "/System/Library/Frameworks/Python.framework/Versions/2.7/lib/python2.7/unittest/main.py", line 94, in __init__ self.parseArgs(argv) File "/System/Library/Frameworks/Python.framework/Versions/2.7/lib/python2.7/unittest/main.py", line 149, in parseArgs self.createTests() File "/System/Library/Frameworks/Python.framework/Versions/2.7/lib/python2.7/unittest/main.py", line 158, in createTests self.module) File "/System/Library/Frameworks/Python.framework/Versions/2.7/lib/python2.7/unittest/loader.py", line 128, in loadTestsFromNames suites = [self.loadTestsFromName(name, module) for name in names] File "/System/Library/Frameworks/Python.framework/Versions/2.7/lib/python2.7/unittest/loader.py", line 100, in loadTestsFromName parent, obj = obj, getattr(obj, part) AttributeError: 'module' object has no attribute 'foo'
I’d used unittest.main() many times before without issue, and it wasn’t too clear from the error message what the problem is here. What’s happening is that if command-line arguments are supplied to the test script in sys.argv, unittest tries to use these arguments as test cases. For the example above, unittest.main() is therefore looking for a TestCase object called foo, or a callable object called foo that returns a TestCase. As neither of these are present in my_tests.py, the above error is therefore occurring.
There are two ways to resolve this problem:
1) Extract the command-line arguments into separate variables and then remove them from sys.argv before calling unittest.main(). This is done by replacing the block at the bottom of my_tests.py with:
if __name__ == '__main__': if len(sys.argv) != 2: sys.exit("ERROR: A command-line parameter must be supplied for these tests") command_line_param = sys.argv[1] del sys.argv[1:] unittest.main()
2) Run the tests with an alternative unittest function to unittest.main():
if __name__ == '__main__': if len(sys.argv) != 2: sys.exit("ERROR: A command-line parameter must be supplied for these tests") command_line_param = sys.argv[1] suite = unittest.TestLoader().loadTestsFromTestCase(MyTests) unittest.TextTestRunner().run(suite)
I think that solution 1) is neater and easier to follow.
