Python unittest passing arguments
Asked Answered
E

8

108

In Python, how would I pass an argument from the command line to a unittest function?

Here is the code so far… I know it's wrong.

class TestingClass(unittest.TestCase):

    def testEmails(self):
        assertEqual(email_from_argument, "[email protected]")


if __name__ == "__main__":
    unittest.main(argv=[sys.argv[1]])
    email_from_argument = sys.argv[1]
Expressionism answered 8/7, 2012 at 3:20 Comment(5)
The thing is, unit tests should be able to be run automatically, which makes passing arguments difficult. Perhaps if you explained more about what you're trying to test?Bickering
when you run in the command line you can run python testfunction.py and the name =="main" allows for this. I want to be able to run python testfunction.py [email protected] [email protected]Expressionism
How about using a configuration file? Would that serve? For example Nose TestConfigBickering
I would prefer not to... just a preference, but maybe I shouldExpressionism
Also see python, unittest: is there a way to pass command line options to the app (which is an even older question but lacks the warning about the unit tests ideally being stand alone and not relying on outside information).Baltimore
C
168

So the doctors here that are saying "You say that hurts? Then don't do that!" are probably right. But if you really want to, here's one way of passing arguments to a unittest test:

import sys
import unittest

class MyTest(unittest.TestCase):
    USERNAME = "jemima"
    PASSWORD = "password"

    def test_logins_or_something(self):
        print('username:', self.USERNAME)
        print('password:', self.PASSWORD)


if __name__ == "__main__":
    if len(sys.argv) > 1:
        MyTest.USERNAME = sys.argv.pop()
        MyTest.PASSWORD = sys.argv.pop()
    unittest.main()

That will let you run with:

python mytests.py myusername mypassword

You need the argv.pops, so your command line parameters don't mess with unittest's own...

The other thing you might want to look into is using environment variables:

import os
import unittest

class MyTest(unittest.TestCase):
    USERNAME = "jemima"
    PASSWORD = "password"

    def test_logins_or_something(self):
        print('username:', self.USERNAME)
        print('password:', self.PASSWORD)

if __name__ == "__main__":
    MyTest.USERNAME = os.environ.get('TEST_USERNAME', MyTest.USERNAME)
    MyTest.PASSWORD = os.environ.get('TEST_PASSWORD', MyTest.PASSWORD)
    unittest.main()

That will let you run with:

TEST_USERNAME=ausername TEST_PASSWORD=apassword python mytests.py

And it has the advantage that you're not messing with unittest's own argument parsing. The downside is it won't work quite like that on Windows...

Consternation answered 20/12, 2013 at 11:47 Comment(6)
Looks like pytest page moved. It is now at docs.pytest.orgQuickie
FYI, I've done something like this in the past: use "--" to separate my args from the ones that unittest might want; then I can pass the list of parameters after the "--" to my own arg parser, and remove them from sys.argv or explicitly pass the args unittest might want using unittest.main(argv=smaller_list)Tyrelltyrian
The sys.argv.pop() method does not seem to work in python3. I would love to know how this can be done in python3.Embolism
@Yeow_Meng, you might need to be more specific. I'm using that with python-3.6.5 and it works.Catamite
Very convenient if you want to run the same tests against different environments like different stages, etc. Likely environment variables are cleanest.Heptachord
This is important. I am working on a module that tests big tables from Excel and converts them into dataframes. It would be a nightmare to create the test cases and the result of operations (other datadframes) in anything other than Excel. Hence, I need to open once a workbook holding the dataframes and then have all the test cases use the same workbook without without having to open and close it repeatedly. This solution accomplishes this with flying colors.Palanquin
B
40

Another method for those who really want to do this in spite of the correct remarks that you shouldn't:

import unittest

class MyTest(unittest.TestCase):

    def __init__(self, testName, extraArg):
        super(MyTest, self).__init__(testName)  # calling the super class init varies for different python versions.  This works for 2.7
        self.myExtraArg = extraArg

    def test_something(self):
        print(self.myExtraArg)

# call your test
suite = unittest.TestSuite()
suite.addTest(MyTest('test_something', extraArg))
unittest.TextTestRunner(verbosity=2).run(suite)
Blain answered 28/4, 2014 at 21:43 Comment(6)
Good Example! Another at agiletesting.blogspot.com/2005/01/… . But care passed argument not mess with the unittest's..Rimrock
Thanks. This was helpful in figuring out how to make unittest work for my particular case.Godbey
Can I do it in a bulk? If there are multiple tests in MyTest classExercitation
Please can you tell me why have you chosen to not use unitest.main() and instead have used TestSuite/TextTestRunner?Tother
This was extremely helpful to me because I needed to run this same test with different input arguments, so using TestSuite is nice in order to chain the multiple instances of the tests together and run them all. Thanks!Retard
@Alex, [suite.addTest(MyTest(case, extraArg)) for case in unittest.defaultTestLoader.getTestCaseNames(MyTest)]Seasick
U
15

Even if the test gurus say that we should not do it: I do. In some context it makes a lot of sense to have parameters to drive the test in the right direction, for example:

  • which of the dozen identical USB cards should I use for this test now?
  • which server should I use for this test now?
  • which XXX should I use?

For me, the use of the environment variable is good enough for this puprose because you do not have to write dedicated code to pass your parameters around; it is supported by Python. It is clean and simple.

Of course, I'm not advocating for fully parametrizable tests. But we have to be pragmatic and, as I said, in some context you need a parameter or two. We should not abouse of it :)

import os
import unittest


class MyTest(unittest.TestCase):
    def setUp(self):
        self.var1 = os.environ["VAR1"]
        self.var2 = os.environ["VAR2"]

    def test_01(self):
        print("var1: {}, var2: {}".format(self.var1, self.var2))

Then from the command line (tested on Linux)

$ export VAR1=1
$ export VAR2=2
$ python -m unittest MyTest
var1: 1, var2: 2
.
----------------------------------------------------------------------
Ran 1 test in 0.000s

OK
Unmeaning answered 13/10, 2017 at 14:26 Comment(1)
Thanks for thinking differently - ie "oh it's not the python party line so can't do that"Pulpwood
S
6

If you want to use steffens21's approach with unittest.TestLoader, you can modify the original test loader (see unittest.py):

import unittest
from unittest import suite

class TestLoaderWithKwargs(unittest.TestLoader):
    """A test loader which allows to parse keyword arguments to the
       test case class."""
    def loadTestsFromTestCase(self, testCaseClass, **kwargs):
        """Return a suite of all tests cases contained in 
           testCaseClass."""
        if issubclass(testCaseClass, suite.TestSuite):
            raise TypeError("Test cases should not be derived from "\
                            "TestSuite. Maybe you meant to derive from"\ 
                            " TestCase?")
        testCaseNames = self.getTestCaseNames(testCaseClass)
        if not testCaseNames and hasattr(testCaseClass, 'runTest'):
            testCaseNames = ['runTest']

        # Modification here: parse keyword arguments to testCaseClass.
        test_cases = []
        for test_case_name in testCaseNames:
            test_cases.append(testCaseClass(test_case_name, **kwargs))
        loaded_suite = self.suiteClass(test_cases)

        return loaded_suite 

# call your test
loader = TestLoaderWithKwargs()
suite = loader.loadTestsFromTestCase(MyTest, extraArg=extraArg)
unittest.TextTestRunner(verbosity=2).run(suite)
Sagerman answered 20/6, 2016 at 7:22 Comment(2)
Where is self.getTestCaseNames method coming from?Exercitation
It belongs to the unittest.TestLoader class which TestLoaderWithKwargs inherits from.Sagerman
P
1

I have the same problem. My solution is after you handle with parsing arguments using argparse or another way, remove arguments from sys.argv:

sys.argv = sys.argv[:1]  

If you need, you can filter unittest arguments from main.parseArgs().

Potful answered 12/4, 2017 at 9:55 Comment(0)
W
1

I did a little investigation and found a solution like this which for me is excellent:

# conftest.py
import argparse

def pytest_addoption(parser):
 'Pytest'
 parser.addoption("--name", required=True, help="Name option Pytest")


def parse_args(argv):
 'Unit Test'
 parser = argparse.ArgumentParser(description='Example argparse usage')
 parser.add_argument('input_files', nargs='*', help='Input file path')
 parser.add_argument('--name', required=True, help="Name option Unit Test")
 return parser.parse_args(argv)

And test_main.py

# test_main.py
import sys
import unittest
from conftest import parse_args

# Transfer sys.args
args = parse_args(sys.argv)

class TestName(unittest.TestCase):
    name = None
    @classmethod
    def setUpClass(cls):
        'setup only once'
        cls.name = args.name

    def test_name(self):
        self.assertEqual(self.name, args.name)
    def test_upper_name(self):
        self.assertEqual(self.name.upper(), args.name.upper())
    def test_lower_name(self):
        self.assertEqual(self.name.lower(), args.name.lower())

if __name__ == '__main__':
  unittest.main(argv=['ignored','-v']) # Allows unittest to take params

So as result, we can run pytest:

 pytest   ./test_name.py  test_name2.py --name Bee

And Unit tests will also work:

python3   ./test_name.py  --name Bee

Note: in the Unit test case below, './test_name2.py' will be ignored!

python3   ./test_name.py ./test_name2.py --name Bee
Wormseed answered 13/2 at 13:52 Comment(0)
F
0

This is my solution:

# your test class
class TestingClass(unittest.TestCase):

    # This will only run once for all the tests within this class
    @classmethod
    def setUpClass(cls) -> None:
       if len(sys.argv) > 1:
          cls.email = sys.argv[1]

    def testEmails(self):
        assertEqual(self.email, "[email protected]")


if __name__ == "__main__":
    unittest.main()

You could have a runner.py file with something like this:

# your runner.py
loader = unittest.TestLoader()
tests = loader.discover('.') # note that this will find all your tests, you can also provide the name of the package e.g. `loader.discover('tests')
runner = unittest.TextTestRunner(verbose=3)
result = runner.run(tests

With the above code, you should be to run your tests with runner.py [email protected].

Frondescence answered 11/2, 2021 at 18:43 Comment(0)
D
-5

Unit testing is meant for testing the very basic functionality (the lowest level functions of the application) to be sure that your application building blocks work correctly. There is probably no formal definition of what does that exactly mean, but you should consider other kinds of testing for the bigger functionality -- see Integration testing. The unit testing framework may not be ideal for the purpose.

Douglasdouglashome answered 8/7, 2012 at 8:51 Comment(2)
Yes but unittest framework itself can be used (and is used in many cases) to develop and integration test frameworks which often requires complex setup and teardown. In such a case it would be helpful to have a baseclass with test functions and params and with an inherited class with the param definition if possible or to use environment based config script.Tonus
Agreed with Umar, the unittest framework is very useful for things outside its original purpose, if you're careful it's very handy. Not least because then you integrate with all the other tools, e.g. things it can output to.Stiltner

© 2022 - 2024 — McMap. All rights reserved.