Python: run a unittest.TestCase without calling unittest.main()? - python

I've written a small test suite in Python's unittest:
class TestRepos(unittest.TestCase):
#classmethod
def setUpClass(cls):
"""Get repo lists from the svn server."""
...
def test_repo_list_not_empty(self):
"""Assert the the repo list is not empty"""
self.assertTrue(len(TestRepoLists.all_repos)>0)
def test_include_list_not_empty(self):
"""Assert the the include list is not empty"""
self.assertTrue(len(TestRepoLists.svn_dirs)>0)
...
if __name__ == '__main__':
unittest.main(testRunner=xmlrunner.XMLTestRunner(output='tests',
descriptions=True))
The output is formatted as Junit test using the xmlrunner pacakge.
I've added a command line argument for toggling JUnit output:
if __name__ == '__main__':
parser = argparse.ArgumentParser(description='Validate repo lists.')
parser.add_argument('--junit', action='store_true')
args=parser.parse_args()
print args
if (args.junit):
unittest.main(testRunner=xmlrunner.XMLTestRunner(output='tests',
descriptions=True))
else:
unittest.main(TestRepoLists)
The problem is that running the script without --junit works, but calling it with --junit clashes with unittest's arguments:
option --junit not recognized
Usage: test_lists_of_repos_to_branch.py [options] [test] [...]
Options:
-h, --help Show this message
-v, --verbose Verbose output
...
How can I run a unittest.TestCase without calling unittest.main()?

You really should use a proper test runner (such as nose or zope.testing). In your specific case, I'd use argparser.parse_known_args() instead:
if __name__ == '__main__':
parser = argparse.ArgumentParser(add_help=False)
parser.add_argument('--junit', action='store_true')
options, args = parser.parse_known_args()
testrunner = None
if (options.junit):
testrunner = xmlrunner.XMLTestRunner(output='tests', descriptions=True)
unittest.main(testRunner=testrunner, argv=sys.argv[:1] + args)
Note that I removed --help from your argument parser, so the --junit option becomes hidden, but it will no longer interfere with unittest.main. I also pass the remaining arguments on to unittest.main().

Related

argparse subparser exit_on_error

I want to disable exit_on_error for a parser and subparser, to prevent error messages:
import argparse
if __name__ == '__main__':
parser = argparse.ArgumentParser(exit_on_error=False)
subparsers = parser.add_subparsers()
subcommand = subparsers.add_parser('subcommand')
subcommand.add_argument('argument')
try:
arguments = parser.parse_args()
except:
parser.print_usage(std.stderr)
With no arguments (ie: no subparser invoked) it works as intended:
$ python3 main.py
usage: main.py {subcommand} ...
But when I use a subcommand, it doesn't:
$ python3 main.py subcommand
usage: main.py subcommand [-h] argument
main.py subcommand: error: the following arguments are required: argument
usage: main.py {subcommand} ...
In this instance, I'd want it to print this instead:
$ python3 main.py subcommand
usage: main.py {subcommand} ...
I checked, and neither add_subparsers nor add_parser take an exit_on_error argument. How can I suppress printing those error messages?

pytest - command line argument outside test function

How to pass command line option as variable to my pytest non test file e.g. database name.
When i try:
import sys
import getopt
"""argv = sys.argv[1:]
opts, args = getopt.getopt(argv, "e:")
for opt, arg in opts:
if opt in ['-e']:
if arg == "test1":
base_url = "url-test1.com"
db_name = "db_test1"
elif arg == 'test2':
base_url = "url-test2.com"
db_name = "db_test2"
elif arg == 'test3':
base_url = "url-test3.com"
db_name = "db_test3"
return base_url
and run
python -m pytest -e test1
looks like pytest can't get -e flag
ERROR: usage: main.py [options] [file_or_dir] [file_or_dir] [...]
main.py: error: unrecognized arguments: -e
inifile: None
I also try pytest addoption and passing variable to test files works fine but how to pass cmnd line option as value to non test file?
def pytest_addoption(parser):
parser.addoption("--url", action="store", default="url-test1.com")
parser.addoption("--db", action="store", default="test1")
#pytest.fixture()
def url(request):
return request.config.getoption("--url")
def db_name(request):
return request.config.getoption("--db") #I want to pass this value to mysql.connector as database=db_name
EDIT 1
so my db_connect.py lokks like that
import mysql.connector
import argparse
def parse_args():
parser = argparse.ArgumentParser()
parser.add_argument('--db', required=True, type=str, help="Your database name")
return parser.parse_args()
def main():
args = parse_args()
db = args.db
return db
mydb = mysql.connector.connect(
user='username',
password='password',
host='host',
database=main()
)
if __name__ == '__main__':
main()
and when i try to run
py.test --db test1
I got this error
ERROR: usage: py.test [options] [file_or_dir] [file_or_dir] [...]
py.test: error: unrecognized arguments: --db
inifile: None
but when i run
py.test
i got
usage: py.test [-h] --db DB
py.test: error: the following arguments are required: --db
argument is required but when i pass it is unrecognized. How to handle it?
Welcome!
Specifically to "override" variables, modules and objects you should mock them. Mocking in testing refers to creating a fake object with similar behavior to the original one when creating the real object is expensive. Such as database connections. But, naturally, it isn't restricted to just databases. You can mock any object, as well as sys.argv.
You can read more extensively about mocking in the pytest docs but here's a short example
import module_to_test
def mytest(monkeypatch):
"""
Mocks the configuration parameters values.
"""
monkeypatch.setattr(module_to_test.sys, 'argv', ['somescript.py', '--db'])
That being said, I strongly recommend you do not use getopt. That is a deprecated method to parse arguments from the world of bash. There is a strong package called argparse that entirely replaces any such argument boilerplate code.
import argpase
def parse_args():
"""
Parse arguments given in the command line. Expects just "--db"
"""
parser = argparse.ArgumentParser()
parser.add_argument('--db', required=True, type=str, help="Your DB name")
return parser.parse_args()
def main():
args = parse_args()
db = args.db
print(f"Wrap 10 to {db}. Engage!")
if __name__ == '__main__':
main()
argparse docs
edit 1
Great work on that argparse!
Now, you can simply mock it. You don't need it parsing the command line, anymore. You want to control what it returns. So, this time, when you use monkeypatch to mock argparse.ArgumentParser, you'll pass in your own "dummy class" that does nothing but return fixed arguments when parser.parse_args() is called. Here's an example of such a class (you'll probably want to tweak it)
from collections import namedtuple
class DummyParser:
def add_argument(self, *_, **__):
"""
We know what arguments we want, we don't need to implement this.
"""
pass
def parse_args():
"""
Money time!
"""
fake_return_class = namedtuple('Namespace',
['db', 'the value we want for db'])
args = fake_return_class(db="the value we want")
return args
fake_parser = DummyParser()
fake_args = fake_parser.parse_args()
print(fake_args.db)
One tweak could be to make it a little more reusable and add your own constructor of what'll db be equal to.

Passing command line arguments in python by pytest

I am able to pass command line arguments when running
python <filename>.py arg1
But when am trying to pass the command line arguments for running pytest it fails and gives error as below. Can you please advise.
pytest <filename>.py arg1
ERROR: file not found: arg1
EDIT:
For example am thinking of using it this way assuming I have passed an argument and am reading it via sys.argv:
import sys
arg = sys.argv[3]
def f():
return 3
def test_function():
assert f() == arg
Your pytest <filename>.py arg1 command is trying to call pytest on two modules <filename>.py and arg1 , But there is no module arg1.
If you want to pass some argument before running pytest then run the pytest from a python script after extracting your variable.
As others suggested though you would probably want to parameterize your tests in some other way, Try:Parameterized pytest.
# run.py
import pytest
import sys
def main():
# extract your arg here
print('Extracted arg is ==> %s' % sys.argv[2])
pytest.main([sys.argv[1]])
if __name__ == '__main__':
main()
call this using python run.py filename.py arg1
Here's the method I just cooked up from reading the parameterized pytest docs and hacking for a while... I don't know how stable or good it is going to be overall since I just got it working.
I did however check that HTML coverage generation works with this method.
add a file to your test directory for configuring the command-line args you want to pass:
tests\conftest.py
# this is just so we can pass --server and --port from the pytest command-line
def pytest_addoption(parser):
''' attaches optional cmd-line args to the pytest machinery '''
parser.addoption("--server", action="append", default=[], help="real server hostname/IP")
parser.addoption("--port", action="append", default=[], help="real server port number")
and then add a test file, with this special pytest_generate_tests function which is called when collecting a test function
tests\test_junk.py
def pytest_generate_tests(metafunc):
''' just to attach the cmd-line args to a test-class that needs them '''
server_from_cmd_line = metafunc.config.getoption("server")
port_from_cmd_line = metafunc.config.getoption("port")
print('command line passed for --server ({})'.format(server_from_cmd_line))
print('command line passed for --port ({})'.format(port_from_cmd_line))
# check if this function is in a test-class that needs the cmd-line args
if server_from_cmd_line and port_from_cmd_line and hasattr(metafunc.cls, 'real_server'):
# now set the cmd-line args to the test class
metafunc.cls.real_server = server_from_cmd_line[0]
metafunc.cls.real_port = int(port_from_cmd_line[0])
class TestServerCode(object):
''' test-class that might benefit from optional cmd-line args '''
real_server=None
real_port = None
def test_valid_string(self):
assert self.real_server!=None
assert self.real_port!=None
def test_other(self):
from mypackage import my_server_code
if self.real_server != None:
assert "couldn\'t find host" not in my_server_code.version(self.real_server, self.real_port)
then run (with HTML coverage, for example) with:
pytest tests\test_junk.py --server="abc" --port=123 --cov-report html --cov=mypackage
It seems monkeypatch also works.
Example:
import sys
def test_example(monkeypatch):
monkeypatch.setattr(sys, 'argv', ['/path/to/binary', 'opt1', '...'])
assert f() == '...'
def test_another():
# sys.argv is not modified here
assert f() != '...'

Call a python script within another python script causes problems with argparse

After trying the solution offered by this thread:
What is the best way to call a Python script from another Python script?
I came along with another problem dealing with arguments.
I have:
test1.py
def some_func():
print 'in test 1, unproductive'
if __name__ == '__main__':
# test1.py executed as script
# do something
some_func()
service.py (with dummy arguments)
import argparse
import test1
actions = ['start', 'remove']
parser = argparse.ArgumentParser()
parser.add_argument("action", help="Possible actions are: "
"'{d[0]}|{d[1]}' ".format(d=actions))
parser.add_argument("-d", "--debug", help="Debug mode", action="store_true")
args = parser.parse_args()
def service_func():
print 'service func'
if __name__ == '__main__':
# service.py executed as script
# do something
service_func()
test1.some_func()
This works:
python service.py start
service func
in test 1, unproductive
However, if I want to add arguments in test1.py as well:
test1.py
import argparse
######## new stuff ##########
parser = argparse.ArgumentParser()
parser.add_argument("-d", "--debug", help="Debug mode", action="store_true")
args = parser.parse_args()
##############################
def some_func():
print 'in test 1, unproductive'
if __name__ == '__main__':
# test1.py executed as script
# do something
some_func()
Now I get:
python service.py start
usage: service.py [-h] [-d]
service.py: error: unrecognized arguments: start
Not sure why...
python service.py start - to argparse 'start' looks like a positional argument. It does not have a '--' to mark it as a flag string.
I'm guessing the parser in service.py is handling it ok, though you don't display or otherwise use the resulting args.
The usage in the error message is consistent with the parser in test1.py. It does not define a positional, just the --debug
usage: service.py [-h] [-d]
test1.py is using the same sys.argv list.
There are several solutions:
add a positional argument to test1
use parse_known_args instead of parse_args.
modify sys.argv in service.py (after parsing) to remove this 'start' string. A change in sys.argv in this script will carry over to test1. Print sys.argv in both to be sure.
use REMAINDER as documented in the docs
argparse.REMAINDER. All the remaining command-line arguments are gathered into a list. This is commonly useful for command line utilities that dispatch to other command line utilities:
Call
args, extras = parser.parse_known_args()
This would set
args to Namespace(debug=False) and extras to ['start']. Otherwise you get a tuple of these two values, (Namespace(debug=False), ['start']).
If you haven't modified sys.argv in the first script, the 2nd one will see the same list. Parsing does not modify this list.

Python - unittest trying to call imported custom argparser

I have a unittest that wants to call an imported module to do both parse_os based on the unittest's command-line option but it seems unittest does not recognize the option, any ideas:
./python testParser.py --mac
option --mac not recognized
Usage: testParser.py [options] [test] [...]
Options:
-h, --help Show this message
-v, --verbose Verbose output
-q, --quiet Minimal output
-f, --failfast Stop on first failure
-c, --catch Catch control-C and display results
-b, --buffer Buffer stdout and stderr during test runs
Examples:
testParser.py - run default set of tests
testParser.py MyTestSuite - run suite 'MyTestSuite'
testParser.py MyTestCase.testSomething - run MyTestCase.testSomething
testParser.py MyTestCase - run all 'test*' test methods
in MyTestCase
I want to run my unittest program like this: python testParser.py --mac
EDITTED: Works now by changing 'unittest.main()' to:
runner = unittest.TextTestRunner(stream=stderr_file)
itersuite = unittest.TestLoader().loadTestsFromTestCase(TT28046_ForensicSearchSmokeTest)
runner.run(itersuite)
Unittest program:
import logging
import unittest
from myargparse import *
class MyTest(unittest.TestCase):
def test_parse_os(self):
## Parse the args:
self.install = install_sw(parse_os(arg=""))
print 'Which os? %s' % self.install
if __name__ == '__main__':
logging.basicConfig(level=logging.INFO)
# get the default logger
logger = logging.getLogger()
# add a file handler
logger.addHandler(logging.FileHandler('stdout.txt', mode='w'))
# set up a stream for all stderr output
stderr_file = open('stderr.txt', 'w')
# attach that stream to the testRunner
unittest.main(testRunner=unittest.TextTestRunner(stream=stderr_file))
My imported module:
import argparse
import os
import sys
def parse_os(arg):
my_os = ''
parser = argparse.ArgumentParser()
parser.add_argument("-m", "--mac",
action="store_true")
parser.add_argument("-w", "--win",
action="store_true")
args = parser.parse_args()
if args.mac:
print 'Mac'
my_os = "Mac"
if args.win:
print 'Windows'
my_os = "Windows"
return my_os
def install_sw(my_os):
installed_os = None
if my_os == 'Mac':
print 'Installing Mac...'
installed_os = 'Mac'
if my_os == 'Windows':
print 'Installing Windows...'
installed_os = 'Windows'
return installed_os
The sys.argv variable is a simple list so you can modify/replace it at your wish.
I'd consider using a context manager in this case, on the lines of:
class SysArgv(object):
def __init__(self, argv):
self._old_argv = sys.argv
sys.argv = argv
def __enter__(self):
return self
def __exit__(self, exc_type, exc_value, tb):
sys.argv = self._old_argv
return False
And used as:
In [4]: with SysArgv(['a', 'b', 'c']):
...: print(sys.argv)
...:
['a', 'b', 'c']
In your case simple wrap the test code like:
with SysArgv(['the_module_name.py', '--mac']):
# test code goes here
and the argparse module will see the arguments you wants.
As for passing the arguments to the unittest module when running the tests, it's possible passing the argv argument to unittest.main. From the documentation:
The `argv` argument can be a list of options passed to the program, with the first element being the program name. If not specified or `None`, the values of `sys.argv` are used.
However in this case you should modify the sys.argv variable before calling unittest.main:
if __name__ == '__main__':
options = ['name_of_module.py'] + sys.argv[-1:] # last argument as option for the test
with SysArgv(sys.argv[:-1]): # or modify how you want
unittest.main(argv=options)
Have you tried using just '-m' instead of '--mac'?
You may also try:
import optparse
parser = optparse.OptionParser()
parser.add_option("-m", "--mac",
dest="mac",
action="store_true",
help="Run as Mac")
parser.add_option("-w", "--win",
dest="win",
action="store_true",
help="Run as Win")
(options, args) = parser.parse_args()
Thank you all for your suggestions but I decided to go with this to limit the changes to my program.
Instead of calling 'unittest.main()', I just changed to call the following:
runner = unittest.TextTestRunner(stream=stderr_file)
itersuite = unittest.TestLoader().loadTestsFromTestCase(MyTest)
runner.run(itersuite)
Based on all the answers here, I originally did this simple hack, and it worked:
# Change sys.argv before unittest tries to parse our args
sys.argv = [sys.argv[0]] # Replace with only the first arg
unittest.main()
The I realized I could still use all the unitest command line args, and in my case I was just passing a bunch of paths, so anything starting with a "-" could just be passed on and there is no need to hack anything since unittest.main() has an argv argument
# Pass on options, and more importantly, don't pass on ALL args
options = [sys.argv[0]] + [a for a in sys.argv if a.startswith("-")]
unittest.main(argv=options)
Just filter out all argparse and not arparse parameters and send then ones not being argparse arguments to sys.argv which is the one unittest uses:
args, notknownargs = parser.parse_known_args()
sys.argv[1:] = notknownargs

Categories