Problem with sys.argv[1] when unittest module is in a script
Asked Answered
S

4

12

I have a script that does various things and access parameters using sys.argv but when the script gets to the unittest part of the code it says there is no module for this. The script that I have is:

class MyScript():

    def __init__(self):
        self.value = sys.argv[1]

    def hello(self):
        print self.value

    def suite(self):
        modules_to_test = ('external_sanity_onvif', 'starttest')
        alltests = unittest.TestSuite()
        for module in map(__import__, modules_to_test):
            alltests.addTest(unittest.findTestCases(module))
        return alltests


if __name__ == '__main__': 
    Run = MyScript()
    Run.hello()
    log_file = 'log_file.txt'
    test_file = open(log_file, "w") 
    runner = unittest.TextTestRunner(test_file)
    unittest.main(defaultTest='Run.suite', testRunner=runner)

Say I enter ./script.py Hello in the command line. The error I get is:

AttributeError: 'module' object has no attribute 'Hello'

If I remove the unittest module it works. Also if I remove the testrunner log and leave it at:

unittest.main(defaultTest='Run.suite')

This still doesn't work.

Can anyone help.

Thanks

I tried this hack but it still tries to read the sys.argv.

project = sys.argv[4:]
sys.argv = sys.argv[0:4]

I have tried this with just argv but it still tires to read the extra parameters.

Sammy answered 11/5, 2010 at 15:57 Comment(1)
Could you post the full error message (with the stack trace)? It will tell us on what line the AttributeError occurred.Dellinger
M
20

The problem is that unittest.main() wants your precious argv for its own use! It uses either the argv you give it as a function parameter, or sys.argv if you don't give it argv explicitly, and tries to load tests named the arguments you give. In this case, this means it's looking for either a submodule called Hello, a TestCase class named Hello, a test case method within a test case class named Hello, or a callable object called Hello that returns a TestCase or TestSuite instance, all within your module 'script'.

There are several ways to fix this:

  • Bypass unittest.main() and call lower-level unittest functions yourself to set up and run the test cases you have in mind.
  • Remove your code's dependency on sys.argv, and use the unittest.main() behavior to your advantage. If your module isn't meant to be run independently except as a unit test, this probably makes sense, since callers of your module may not be expecting you to read from their argv!
  • Separate the test code and main routine into a separate test module. You'd still have to figure out how to get the right argv into your code though from the test module.
  • Specify argv=[sys.argv[0]] as an argument to unittest.main(); that should keep it from trying to read yours.
Marque answered 15/5, 2010 at 5:19 Comment(0)
P
10

If you don't need the command-line features of the unittest module, you can make the optparse and unittest modules play well together by modifying sys.argv just before calling unittest.main()

Try this just before your unittest.main() call:

del sys.argv[1:]

This removes your command line arguments before unittest sees them.

If you aren't using the optparse module, you can do this instead:

my_args = sys.argv[1:]
del sys.argv[1:]
# do_stuff(my_args)
unittest.main()
Paleolith answered 13/8, 2011 at 17:50 Comment(0)
M
1

Generally speaking, the idea behind unit testing is that the tests run without an external driver. E.g. you don't want to depend on some input from the command line. The solution that you're looking for may be through the use of Fixtures?

import sys
import unittest

class MyScript(unittest.TestCase):
    def setUp(self):
        self.value = "Default Value"

    def setHello(self, value):
        self.value = value

    def hello(self):
        return self.value

class UserA(MyScript):
    def setUp(self):
        self.setHello("UserA")

    def testMyName(self):
        self.failUnlessEqual(self.hello(), "UserA")

class UserB(MyScript):
    def setUp(self):
        self.setHello("UserB")

    def testMyName(self):
        self.failUnlessEqual(self.hello(), "UserB")

if __name__ == '__main__': 
    unittest.main()
Myles answered 11/5, 2010 at 16:45 Comment(0)
M
-1

Python gives you all of the information it can, and unless you want to post the whole set of things you're trying to test we can only guess at what you're trying to accomplish. The error says there's no attribute named "Hello", so maybe the modules you're trying to test should get such a variable, function, or otherwise.

Is there any reason why you're not just using unittest.main()?

Myles answered 11/5, 2010 at 16:13 Comment(2)
In this particular pice of code I am trying to pass Hello in as a parameter which will work. Then I have a function that calls a series of test which is separate to the other functions. But when unittest and sys.argv are in the same script there is a problem. The tests I am doing do not matter as they work, the problem Is that when I try and enter a parameter into a function with no link the unittest and then run the unittest function further down the file I get the error. The reason I am using that type of unitest command is that I am calling a specific test suite.Sammy
Your unit tests should not take parameters from the command line. The unittest framework clearly uses sys.argv for its own goals, although there may be a way to override that (e.g. maybe there's an argv parameter to the unittest calls? I don't know off hand.) If you want to parameterize your tests, so you can instantiate them with different sets of data, you should probably also do that via "fixtures." I'll post another answer.Myles

© 2022 - 2024 — McMap. All rights reserved.