Python unittest loading test data on the fly - python

I'm trying to load test data base on the application name from a configuration file.
I'm using ConfigParser which is a nosetest plug in.
Here is my code. I just don't know how to pass the app name while loading the tests on the fly. I tried the constructor, but could not figure out a way to pass parameters to the loadTestsFromTestCase method.
Any ideas?
import unittest
class TestMyApp(unittest.TestCase):
def test1(self):
print "test1: %s" % self.app
def test2(self):
print "test2: %s" % self.app
if __name__ == '__main__':
# here specify the app name
suite = unittest.TestLoader().loadTestsFromTestCase(TestMyApp)
# here specify the other app name
suite.addTests(unittest.TestLoader().loadTestsFromTestCase(TestMyApp))
unittest.TextTestRunner(verbosity=2).run(suite)

You are need a test parametrization! It is easy to done with pytest. Look at this code example:
import pytest
class AppImpl:
def __init__(self, app_name):
self.name = app_name
def get_name(self):
return self.name
#pytest.fixture(params=['App1', 'App2'])
def app(request):
return AppImpl(request.param)
def test_1(app):
assert app.get_name()
def test_2(app):
assert 'App' in app.get_name()
if __name__ == '__main__':
pytest.main(args=[__file__, '-v'])
By using class implementation of you logic AppImpl, you can create a fixture app, which can be parametrized by specific arg params=['App1', 'App2']. Then in your tests test_1 and test_2, use fixture name app in funcargs. This possibility provides more modularity and readability tests.

Related

Non-python tests with a command-line option in pytest

I'm implementing custom test cases that are based on external files using the tutorial from https://docs.pytest.org/en/latest/example/nonpython.html.
I need to parametrise them with one bool flag. I'd like to be able to run pytest with a commandline option, in my case --use-real-api, which would turn using mocks off and do the real talking to a remote network API.
I've tried using the cmdopt tutorial and blend them together, but can't find any way to read the parameter from within the custom pytest.Item subclass. Could you help? Here is a trivial example from the tutorial. I'd like to get it to change the test behaviour depending on the value of the cmdopt passed.
# content of conftest.py
import pytest
def pytest_collect_file(parent, path):
if path.ext == ".yml" and path.basename.startswith("test"):
return YamlFile(path, parent)
class YamlFile(pytest.File):
def collect(self):
import yaml
raw = yaml.safe_load(self.fspath.open())
for name, spec in sorted(raw.items()):
yield YamlItem(name, self, spec)
class YamlItem(pytest.Item):
def __init__(self, name, parent, spec):
super().__init__(name, parent)
self.spec = spec
def runtest(self):
for name, value in sorted(self.spec.items()):
# some custom test execution (dumb example follows)
if name != value:
raise YamlException(self, name, value)
def repr_failure(self, excinfo):
""" called when self.runtest() raises an exception. """
if isinstance(excinfo.value, YamlException):
return "\n".join(
[
"usecase execution failed",
" spec failed: %r: %r" % excinfo.value.args[1:3],
" no further details known at this point.",
]
)
def reportinfo(self):
return self.fspath, 0, "usecase: %s" % self.name
class YamlException(Exception):
""" custom exception for error reporting. """
def pytest_addoption(parser):
parser.addoption(
"--cmdopt", action="store", default="type1", help="my option: type1 or type2"
)
#pytest.fixture
def cmdopt(request):
return request.config.getoption("--cmdopt")
Each collection entity in pytest (File, Module, Function etc) is a subtype of the Node class which defines access to the config object. Knowing that, the task becomes easy:
def pytest_addoption(parser):
parser.addoption('--run-yml', action='store_true')
def pytest_collect_file(parent, path):
run_yml = parent.config.getoption('--run-yml')
if run_yml and path.ext == ".yml" and path.basename.startswith("test"):
return YamlFile(path, parent)
Running pytest --run-yml will now collect the YAML files; without the flag, they are ignored.
Same for accessing the config in custom classes, for example:
class YamlItem(pytest.Item):
def runtest(self):
run_yml = self.config.getoption('--run-yml')
...
etc.

How to do one setUp for a unit test suite

I have the following to do two unit tests:
import unittest
from unittest import TestCase
class TestUM(unittest.TestCase):
def setUp(self):
self.client = SeleniumClient()
def test_login(self):
self.client.login()
self.assertIn("my-data", self.client.driver.current_url)
print ('Log in successful.')
def test_logout(self):
self.client.logout()
print ('Log out successful.')
if __name__ == '__main__':
unittest.main()
However, it does setUp twice -- once for each of the unit tests. Is there a way I can do one setup across all the unittests for TestUM ? If so, how would I do that?
You can use setupClass for that:
class TestUM(unittest.TestCase):
#classmethod
def setUpClass(cls):
cls.client = SeleniumClient()
From the documentation, this method is called only once before tests in class are run.

Append the nose #attr to the test name

I am able to setup nose tests to run with the #attr tag. I am now interested in know if I can append to the end of the test name, the #attr tag? What we are trying to do is add a tag if our tests run into an issue and we write up a defect for it, we would then put the defect number as an #attr tag. Then when we run we could easily identify which tests have open defects against them.
Just wondering if this is even possible, and where to go to see how to set it up?
EDIT RESULTS RUNNING WITH ANSWER:
Test Results:
So I sort of know what is going on, if I have the #fancyattr() at the class level it picks it up and changes the name of the class. When I put the #fancyattr() at the test level it is not changing the name of the test, which is what I need for it to do.
For example - Changes the name of the class:
#dms_attr('DMSTEST')
#attr('smoke_login', 'smoketest', priority=1)
class TestLogins(BaseSmoke):
"""
Just logs into the system and then logs off
"""
def setUp(self):
BaseSmoke.setUp(self)
def test_login(self):
print u"I can login -- taking a nap now"
sleep(5)
print u"Getting off now"
def tearDown(self):
BaseSmoke.tearDown(self)
This is what I need and it isn't working:
#attr('smoke_login', 'smoketest', priority=1)
class TestLogins(BaseSmoke):
"""
Just logs into the system and then logs off
"""
def setUp(self):
BaseSmoke.setUp(self)
#dms_attr('DMSTEST')
def test_login(self):
print u"I can login -- taking a nap now"
sleep(5)
print u"Getting off now"
def tearDown(self):
BaseSmoke.tearDown(self)
Updated screenshot with what I am seeing with __doc__:
Here is how to do it with args type attributes:
rename_test.py:
import unittest
from nose.tools import set_trace
def fancy_attr(*args, **kwargs):
"""Decorator that adds attributes to classes or functions
for use with the Attribute (-a) plugin. It also renames functions!
"""
def wrap_ob(ob):
for name in args:
setattr(ob, name, True)
#using __doc__ instead of __name__ works for class methods tests
ob.__doc__ = '_'.join([ob.__name__, name])
#ob.__name__ = '_'.join([ob.__name__, name])
return ob
return wrap_ob
class TestLogins(unittest.TestCase):
#fancy_attr('slow')
def test_method():
assert True
#fancy_attr('slow')
def test_func():
assert True
Running test:
$ nosetests rename_test.py -v
test_method_slow ... ok
test_func_slow ... ok
----------------------------------------------------------------------
Ran 2 tests in 0.003s
OK
EDIT: For xunit reporting to work, test renaming should take place before running the test. You can do it on import, here is untested hack showing how to do it:
rename_test.py:
import unittest
def fancy_attr(*args, **kwargs):
"""Decorator that adds attributes to classes or functions
for use with the Attribute (-a) plugin. It also renames functions!
"""
def wrap_ob(ob):
for name in args:
setattr(ob, name, True)
ob.__doc__ = '_'.join([ob.__name__, name])
return ob
return wrap_ob
class TestLogins(unittest.TestCase):
#fancy_attr('slow')
def test_method(self):
assert True
def make_name(orig, attrib):
return '_'.join([orig, attrib])
def rename(cls):
methods = []
for key in cls.__dict__:
method = getattr(cls, key)
if method:
if hasattr(cls.__dict__[key], '__dict__'):
if 'slow' in cls.__dict__[key].__dict__:
methods.append(key)
print methods
for method in methods:
setattr(cls, make_name(method, 'slow'), cls.__dict__[key])
delattr(cls, method)
rename(TestLogins)
#fancy_attr('slow')
def test_func():
assert True

PyUnit: How to run all tests from different unittest.TestCase subclass present in a file

import unittest
import HTMLTestRunner
class TestClass1(unittest.TestCase):
def setUp(self):
pass
def case1(self):
assert 4 == 3
def case2(self):
assert 4 == 4
def tearDown(self):
pass
class TestClass2(unittest.TestCase):
def setUp(self):
pass
def case3(self):
assert 1 == 2
def tearDown(self):
pass
def suite():
suite = unittest.TestSuite()
suite.addTest(TestClass1(['case1','case2']))
suite.addTest(TestClass2('case4'))
return suite
test_suite = suite()
unittest.TextTestRunner(verbosity=2).run(test_suite)
fp = file('my_report.html', 'wb')
runner = HTMLTestRunner.HTMLTestRunner(
stream=fp,
title='My unit test',
description='This demonstrates the report output by HTMLTestRunner.'
)
runner.run(test_suite)
I am trying to run all the methods in both the classes in a single run. However, the code above did not do so. In the suite function, I tried to add multiple tests from the classes but that also did not work and was giving an error.
From this answer at the question "Is test suite deprecated in PyUnit?":
"unittest.TestSuite is not necessary if you want to run all the tests in a single module as unittest.main() will dynamically examine the module it is called from and find all classes that derive from unittest.TestCase."
There's more in that answer about when unittest.TestSuite is useful.
That said, I needed to make some changes to get these tests to work. Firstly, unittest looks for functions with "test_" at their start. Also, unittest's assertEqual and similar methods should be used, instead of just Python's assert statement. Doing that and eliminating some unneeded code led to:
import unittest
class TestClass1(unittest.TestCase):
def test_case1(self):
self.assertEqual(4, 3)
def test_case2(self):
self.assertEqual(4, 4)
class TestClass2(unittest.TestCase):
def test_case3(self):
self.assertEqual(1, 2)
unittest.main()
This produced appropriate output (3 tests run with 2 failures), which I won't reproduce here in the interest of space.

What's the right way to pass a var to a Python unittest TestCase and/or TestSuite?

I need to run a Python unittest test suite against multiple REST backend resources so I need to pass in a Resource object to the test suite and individual testcases.
Is setting a global var the right way to do this, or is there a better way?
resource = Resource('http://example.com')
class RestTestCase(unittest.TestCase):
def setUp(self):
self.resource = resource
def suite():
suite = unittest.TestSuite()
suite.addTest(unittest.makeSuite(RestTestCase))
return suite
if __name__ == '__main__':
unittest.main(defaultTest='suite')
Follow the example of the how the standard library writes its own unittests. Put the resource in a class variable and use inheritance to test the various resources:
class RestTestCase(unittest.TestCase):
resource = Resource('http://example.com')
def sometest(self):
r = self.resource
...
self.assertEqual(expectedresult, actualresult)
class SomeOtherRestTestCase(RestTestCase):
resource = Resource('http://someother.example.com')
class YetAnotherRestTestCase(RestTestCase):
resource = Resource('http://yetanother.example.com')
if __name__ == '__main__':
unittest.main()

Categories