How to test required argparse arguments during unittests? - python

I'm doing TDD tests for argparser. How can I test arguments with the option required?I need to test all options like:
too many arguments,
no arguments are given,
the wrong argument is given.
I can raise SystemExit, but this is not really what I need:
def test_no_arguments(self):
with patch.object(sys, 'exit') as mock_method:
self.parser.parse_arguments()
self.assertTrue(mock_method.called)
However, without raising system exit I have always errors like this:
zbx-check-mount.py
class CommandLine:
def __init__(self):
self.args_parser = argparse.ArgumentParser(description="Monitoring mounted filesystems",
formatter_class=argparse.RawTextHelpFormatter)
self.parsed_args = None
self.add_arguments()
def add_arguments(self):
"""
Add arguments to parser.
"""
try:
self.args_parser._action_groups.pop() # pylint: disable=protected-access
required = self.args_parser.add_argument_group('required arguments')
required.add_argument('--fs_name', required=True, help='Given filesystem')
except argparse.ArgumentError as err:
log.error('argparse.ArgumentError: %s', err)
sys.exit(1)
def parse_arguments(self, args=None):
"""
Parse added arguments. Then run private method to return values
"""
self.parsed_args = self.args_parser.parse_args()
return self.parsed_args.fs_name,
tests
from pyfakefs.fake_filesystem_unittest import TestCase
import os
import sys
try:
from StringIO import StringIO
except ImportError:
from io import StringIO
if sys.version_info[0] == 3:
from unittest.mock import MagicMock, patch
else:
from mock import MagicMock, patch
sys.path.extend([os.path.join(os.path.dirname(os.path.abspath(__file__)),'..','..', "bin")])
module_name = __import__('zbx-check-mount')
class TestCommandLine(TestCase):
def setUp(self):
"""
Method called to prepare the test fixture. This is called immediately before calling the test method
"""
self.parser = module_name.CommandLine()
def test_no_arguments(self):
opts = self.parser.parse_arguments([])
assert opts.fs_name
def tearDown(self):
"""
Method called immediately after the test method has been called and the result recorded.
"""
pass
How to avoid this situation and test other options?

In def parse_arguments(self, args=None):, you should pass args on to the parser, as in:
self.args_parser.parse_args(args)
parse_args() parses sys.argv[1:], or if the given argument is None. Otherwise it parses the provided list.
In a full distribution of python there's a unittest file for argparse (test_argparse.py). It's somewhat complex, defining a subclass of ArgumentParser that captures errors and redirects error messages.
Testing argparse is tricky because it looks at sys.argv, with the unittest scripts also use. And it usually tries to exit on errors. This has been discussed in a number of SO questions already.

If I'm interpreting your symptoms correctly, you are having problems in the test harness because your monkey patched implementation of sys.exit actually returns, which the argparse library is not expecting.
Introducing a side_effect that raises an exception, which you can then trap and verify in the unit test, may be sufficient to get around the problem.

Related

Python Mock: raise 3rd party exception for unit testing

Let's say i have a method is_validate, which internally calls validate method from library gateway.service
import gateway.service
from gateway.service.exception import ValidatorException
def is_validate():
try:
gateway.service.validate() # which throws ValidatorException
return True
except ValidatorException ex:
return False
How to unit test is_validate method, mocking gateway.service.validate to throw ValidatorException ?
You can do this with a combination of:
mocking a function (creating a fake version of the function dictating what it returns);
monkeypatching the actual function with your mock version;
and using pytest to actually run the test.
I've written a description of how to do this (pulled from my own work) here, in case an example I know works is useful.
But this is what I think you'll need to do in your code:
Define a pytest fixture to mock the scenario you want to test, using monkeypatch to fake the results you want from the parts of the is_validate().
And a test to check that a ValidatorException is raised; the code that raises the error in the test is in the pytest fixture. The entire pytest fixture defined there is passed as a parameter to the test.
import pytest
from unittest import TestCase
import gateway.service
from gateway.service.exception import ValidatorException
# Create object for accessing unittest assertions
assertions = TestCase("__init__")
#pytest.fixture
def validation_fails(monkeypatch):
"""
Mocks a call to gateway.service.validate().
"""
def mock_validate(*args, **kwargs):
"""Mock an absolute file path."""
raise ValidatorException
# Replace calls to existing methods with the mocked versions
monkeypatch.setattr(gateway.service, "validate", mock_validate)
def test_validation_fails(validation_fails):
"""Test validation."""
# check that the correct exception is raised
with assertions.assertRaises(ValidatorException):
is_validate()
Note: This does not include whatever setup is required to get pytest working for your project.
-------------------------------------
mymodule.py
-------------------------------------
import os
def remove(file_path):
if os.path.exists(file_path):
os.remove(file_path)
else:
print('File does not exist')
-------------------------------------
from mymodule import remove
import mock
import unittest
class RemoveTestCase(unittest.TestCase):
#mock.patch('mymodule.os.path')
#mock.patch('mymodule.os.remove')
def test_remove(self, mock_os_remove, mock_os_path):
mock_os_path.exists.return_value = True
#side_effect
remove("any path")
mock_os_remove.assert_called_with("any path")
I was able to mock gateway.service.validate by referencing it with module name where is_validate method is present.
ex: #mock.patch('mymodule.gateway.service.validate')
Reference this doc for more info

pytest : How to write pytest code to detect a "func" called without actually executing the "func"

pytest : How to write pytest code to detect a "func" called without actually executing the "func" ,
target.py:
import requests
import threading
CFG=None
targets={}
def add_target(func):
targets[func.__name__] = func
return func
def parent_func(url):
for funct in set(targets):
threading.Thread(target=targets[funct], args=(CFG[url],)).start()
#add_target
def child_func(url):
try:
response = requests.request("POST", url,
headers={"Content-Type": "application/json"},
data="{\"text\": \"target py test\"}")
response.raise_for_status()
except Exception as err:
raise RuntimeError(err)
test_target.py:
During testing, I want child_func() to get called but the child_func() body should not get executed. Rather, after running tests, child_func() gets executed & test run results in "AssertionError: False is not true "
from unittest.mock import Mock, patch
import target
# Third-party imports...
from nose.tools import assert_true, assert_is_not_none
target.CFG={'url':"some valid url"}
#patch('target.child_func')
def test_child_func(mock_child_func):
parent_func("url")
assert_true(mock_child_func.called)
First (for the sake of my own sanity) let's whittle this down to an actual MRE -- we don't actually need threading or requests or any of the implementation details of child_func to demonstrate the problem here.
target.py:
targets={}
def add_target(func):
targets[func.__name__] = func
return func
def parent_func(url):
for func in targets.values():
func(url)
#add_target
def child_func(url):
raise NotImplementedError("The test shouldn't actually call this function!")
test_target.py:
from unittest.mock import Mock, patch
from target import parent_func
#patch('target.child_func')
def test_parent_func(mock_child_func):
parent_func("url")
assert mock_child_func.call_count == 1
Within our test, we want parent_func to call mock_child_func, not the real child_func. But when we run our test, we can quickly see that our patch didn't work:
============================================== short test summary info ===============================================
FAILED test_target.py::test_parent_func - NotImplementedError: The test shouldn't actually call this function!
The reason for this is that parent_func doesn't call child_func, it calls targets['child_func']. Patching 'target.child_func' doesn't modify the actual function, it modifies what the name child_func points to in the target module -- and that name isn't in use in the code under test!
The easiest fix here is to patch target.targets:
from unittest.mock import Mock, patch
from target import parent_func
#patch('target.targets', new_callable=dict)
def test_parent_func(mock_targets):
mock_targets['child_func'] = Mock()
parent_func("url")
assert mock_targets['child_func'].call_count == 1
Now our test passes because when parent_func iterates through its targets, it gets our mock_targets dict, which we can populate with whatever functions/mocks we want.

How to unittest validation of arguments in argparse using assertRaises() in Python?

When trying to Unittest validations of arguments in argparse the following works:
mymodule:
def validate_mac_addr(mac_addr):
regex = re.compile(r'^((([a-f0-9]{2}:){5})|(([a-f0-9]{2}-){5}))[a-f0-9]{2}$', re.IGNORECASE)
if re.match(regex, mac_addr) is not None:
return mac_addr
msg = f"[-] Invalid MAC address: '{mac_addr}'"
raise argparse.ArgumentTypeError(msg)
test:
import mymodule
import unittest
def test_mac_address_false(self):
self.assertRaises(Exception, mymodule.validate_mac_addr,"n0:ma:ca:dd:re:ss:here")
But I wanted to catch a the more specific 'ArgumentTypeError' but this is apparently not possible with arssertRaises() in this example!? What is going on with the general usage of Exception in assertRaises()?
BTW
isinstance(argparse.ArgumentTypeError, Exception)
Returns False?!
Ref.: class ArgumentTypeError(Exception):
argparse.ArgumentTypeError is a subclass, not an instance, of Exception, and is the type of exception you should be asserting gets raised.
import argparse
def test_mac_address_false(self):
self.assertRaises(argparse.ArgumentTypeError, mymodule.validate_mac_addr, "n0:ma:ca:dd:re:ss:here")

stdout not captured by pytest capsys when using fixture

I am using a pytest fixture to mock up command-line arguments for testing a script. This way the arguments shared by each test function would only need to be declared in one place. I'm also trying to use pytest's capsys to capture output printed by the script. Consider the following silly example.
from __future__ import print_function
import pytest
import othermod
from sys import stdout
#pytest.fixture
def shared_args():
args = type('', (), {})()
args.out = stdout
args.prefix = 'dude:'
return args
def otherfunction(message, prefix, stream):
print(prefix, message, file=stream)
def test_dudesweet(shared_args, capsys):
otherfunction('sweet', shared_args.prefix, shared_args.out)
out, err = capsys.readouterr()
assert out == 'dude: sweet\n'
Here, capsys does not capture sys.stderr properly. If I move from sys import stdout and args.out = stdout directly into the test function, things work as expected. But this makes things much messier, as I have to re-declare these statements for each test. Am I doing something wrong? Can I use capsys with fixtures?
Fixture is invoked before test is run. In your example, shared_args fixture is reading stdout before otherfunction can write anything to stdout.
One way to fix your problem is to make your fixture return a function which can do what you want it to do. You can scope the fixture according to your use case.
from __future__ import print_function
import pytest
from sys import stdout
import os
#pytest.fixture(scope='function')
def shared_args():
def args_func():
args = type('', (), {})()
args.out = stdout
args.prefix = 'dude:'
return args
return args_func
def otherfunction(message, prefix, stream):
print(prefix, message, file=stream)
def test_dudesweet(shared_args, capsys):
prefix, out = shared_args().prefix, shared_args().out
otherfunction('sweet', prefix, out)
out, err = capsys.readouterr()
assert out == 'dude: sweet\n'
You are not using capsys.readouterr() correctly. See the correct usage of capsys here: https://stackoverflow.com/a/26618230/2312300

Python 3 unittest: How to extract results of tests?

I am using Python's (3.4.1) unittest module for my unit tests.
I load all my testing module files using imports and then run unittest.main():
import unittest
import testing_module1
import testing_module2
# [...]
if __name__ == '__main__':
unittest.main()
This works perfectly for me as it is simple and respect the command line arguments I use to control verbosity or which test(s) to run.
I want to continue to output the same information, but I would like to generate an XML file from the results. I tried xmlrunner (https://github.com/xmlrunner/unittest-xml-reporting/) but:
it does not output as much info to stdout as the standard runner;
it uses a specific format of the XML that doesn't suites me.
I would like to generate the XML (I don't mind doing it manually) with the format I need but with minimal change to how the tests are run.
What are my options?
I could write my own TestRunner but I don't want to re-write everything, I just want to add extra output to the actual runner with minimal code change.
I could inherit unittest.TextTestRunner but I fear that adding XML output to it would require re-writing every methods, loosing the advantage of inheritance in the first place.
I could try to extract the test results after the call to unittest.main() and parse it. The problem here is that unittest.main() seems to exit when it's done so any code after it is not executed.
Any suggestion?
Thanks!
I ended up writing two new classes, inheriting from unittest.TextTestResult and unittest.TextTestRunner. That way, I could run main like that:
unittest.main(testRunner=xmlrunner.XMLTestRunner(...))
I overloaded unittest.TextTestRunner's __init__ and those from unittest.TextTestResult:
addSuccess()
addError()
addFailure()
addSubTest()
For example:
def addSuccess(self, test):
super().addSuccess(test)
[... store the test into list, dictionary, whatever... ]
Since these add*() functions are called with the actual test, I can store them in a global list and parse them at the end of my XMLTestRunner.run():
def run(self, test):
result = super().run(test)
self.save_xml_report(result)
return result
Note that these functions are normally defined in /usr/lib/python3.4/unittest/runner.py.
Warning note: By using an actual object passed unittest.main()'s testRunner argument as shown, the command line arguments given when launching python are ignored. For example, increasing verbose level with -v argument is ignored. This is because the TestProgram class, defined in /usr/lib/python3.4/unittest/main.py, detects if unittest.main() was run with testRunner being a class or an object (see runTests() near the end of the file). If you give just a class like that:
unittest.main(testRunner=xmlrunner.XMLTestRunner)
then command line arguments are parsed. But you pass an instantiated object (like I need), runTests() will just use it as is. I thus had to parse arguments myself in my XMLTestRunner.__init__():
# Similar to what /usr/lib/python3.4/unittest/main.py's TestProgram._getParentArgParser() does.
import argparse
parser = argparse.ArgumentParser(add_help=False)
parser.add_argument('-v', '--verbose', dest='verbosity',
action='store_const', const=2, default=1, # Add default=1, not present in _getParentArgParser()
help='Verbose output')
parser.add_argument('-q', '--quiet', dest='verbosity',
action='store_const', const=0,
help='Quiet output')
parser.add_argument('-f', '--failfast', dest='failfast',
action='store_true',
help='Stop on first fail or error')
parser.add_argument('-c', '--catch', dest='catchbreak',
action='store_true',
help='Catch ctrl-C and display results so far')
parser.add_argument('-b', '--buffer', dest='buffer',
action='store_true',
help='Buffer stdout and stderr during tests')
How does this work for you. Capture the output of unittest, which goes to sys.stderr, in a StringIO. Continue after unittest.main by adding `exit=False'. Read the captured output and process as you want. Proof of concept:
import contextlib
import io
import sys
import unittest
class Mytest(unittest.TestCase):
def test_true(self):
self.assertTrue(True)
#contextlib.contextmanager
def err_to(file):
old_err = sys.stderr
sys.stderr = file
yield
sys.stderr = old_err
if __name__ == '__main__':
result = io.StringIO()
with err_to(result):
unittest.main(exit=False)
result.seek(0)
print(result.read())
This prints (to sys.stdout)
----------------------------------------------------------------------
Ran 1 test in 0.000s
OK
Note: contextlib has redirect_stdout, but not redirect_stderr. The above is simpler that the contextlib code. The above assumes that there are no exceptions not caught by unittest. See the contextlib.contextmanager doc for adding try: except: finally. I leave that to you.
I have faced the same issue with catching FAIL events from unittest lib. Following big_gie's answer, this code appeared:
File testFileName_1.py
import unittest
class TestClassToTestSth(unittest.TestCase):
def test_One(self):
self.AssertEqual(True, False, 'Hello world')
import unittest
from io import StringIO
import testFileName_1
def suites():
return [
# your testCase classes, for example
testFileName_1.TestClassToTestSth,
testFileName_445.TestClassToTestSomethingElse,
]
class TextTestResult(unittest.TextTestResult):
def __init__(self, *args, **kwargs):
super().__init__(*args, **kwargs)
self.slack = Slack('data-engineering-tests')
def addFailure(self, test, err):
super().addFailure(test, err)
# Whatever you want here
print(err, test)
print(self.failures)
class TextTestRunner(unittest.TextTestRunner):
resultclass = TextTestResult
def __init__(self, *args, **kwargs):
super().__init__(*args, **kwargs)
loader = unittest.TestLoader()
suite = unittest.TestSuite()
stream = StringIO()
for test_case in suites():
suite.addTests(loader.loadTestsFromTestCase(test_case))
runner = TextTestRunner(stream=stream)
result = runner.run(suite)
stream.seek(0)
print(stream.read())

Categories