how to parameterize unittest test methods - python

I have a set of python/selenium/unittest tests contained in a single class:
class TestProject(unittest.TestClass):
def test001_create_project(self):
...
def test002_project_wizard_page1(self, projectName, projectDescription):
....
def test003_project_wizard_page2(self):
....
def test004_project_wizard_finish(self):
....
I need to run the test methods in a block in the above order because they walk through a wizard on my web application. However, I want to be able to pass a variety of parameters to the test methods like projectName, projectDescription, etc.
Using unittest, how can I run a set of maybe 10 iterations of the tests passing in different parameters to test002_project_wizard_page1?

Your numbered tests are really just parts of a single test. What you should define as separate tests are functions that use your parameter sets.
class TestProject(unittest.TestCase):
def _create_project(self):
...
def _page1(self, projectName, projectDescription):
...
def _page2(self):
...
def _finish(self):
...
def _run(self, name, descr):
self._create_project()
self._page1(name, descr)
self._page2()
self._finish()
def test001(self):
self._run("foo", "do foo")
def test002(self):
self._run("bar", "do bar")
# etc
An interesting module that can reduce some of the boilerplate is the ddt project.
import ddt
#ddt.ddt
class TestProject(unittest.TestCase):
def _create_project(self):
...
def _page1(self, projectName, projectDescription):
...
def _page2(self):
...
def _finish(self):
...
#ddt.data(
("foo", "do foo"),
("bar", "do bar"),
# etc
)
#ddt.unpack
def test_run(self, name, descr):
self._create_project()
self._page1(name, descr)
self._page2()
self._finish()

Any reason that you cannot use pytest? It provides this functionality out of the box.

For me, it looks like you wanna test under a template, with different parameters. How about codes like the following:
import unittest
class TestProject(unittest.TestClass):
def mytest001_create_project(self):
...
def mytest002_project_wizard_page1(self, projectName, projectDescription):
....
def mytest003_project_wizard_page2(self):
....
def mytest004_project_wizard_finish(self):
....
def mytest_in_order(self, project_name, project_description):
self.mytest001_create_project()
self.mytest002_project_wizard_page1(project_name, project_description)
self.mytest003_project_wizard_page2()
self.mytest004_project_wizard_finish()
def test_in_batch(self):
project_names = ['a', 'b']
project_descriptions = ['aa', 'bb']
for project_name, project_description in zip(project_names, project_descriptions):
self.mytest_in_order(project_name, project_description)

Take a look at Python unit testing: parametrized test cases post on Eli Bendersky's blog. It states that
You can't easily pass arguments into a unittest.TestCase from outside.
However, it provides an implementation for class ParametrizedTestCase(unittest.TestCase) which can be used to add parameters to the unittest.TestCase class. That will solve your parameters problem and I believe, each individual test methods are already being run in order.

Related

Pytest setup generate product for all test

I am working on pytest API automation project, and I need to get a random product from the DB. Is there a way that I could use the same random product for all the test cases in my class? I am using a setup class method but it generates a different product every test. Thank you.
class TestCreateOrdersSmoke:
#classmethod
def setup(cls):
cls.products_db = ProductsDao()
cls.orders_db = OrdersDao()
cls.orders_helper = OrdersHelper()
#pytest.mark.tcid48
def test_create_order_as_guest(self):
random_product = self.products_db.select_random_product_from_db()
random_product_id = random_product[0]['ID']
UPDATE:
So i used a pytest session fixture like seggested and it works so thank you! but i want to make sure that this is right practice so here is the updated code:
class TestCreateOrdersSmoke:
#pytest.fixture(scope="session")
def helpers(self):
products_db = ProductsDao()
orders_db = OrdersDao()
orders_helper = OrdersHelper()
random_product = products_db.select_random_product_from_db()
yield {'products_db':products_db,
'orders_db':orders_db,
'orders_helper':orders_helper,
'random_product':random_product}
#pytest.mark.tcid48
def test_create_order_as_guest(self, helpers):
random_product = helpers['random_product']
random_product_id = random_product[0]['ID']
#pytest.mark.tcid88
def test_create_order_with_new_user(self, helpers):
random_product = helpers['random_product']
random_product_id = random_product[0]['ID']
As you say, you need a fixture for all methods in class, so you can use "class" or "session" scope fixture #pytest.fixture(scope="class").
Here some ways to organize the code with the "class" or "session" fixtures.
First way: fixture with "class" scope can be inside a class, example is the same as you did with "session" fixture:
class TestCreateOrdersSmoke:
#pytest.fixture(scope="class")
Second way: fixture with "class" scope can be outside of the class, so then you can use it in different classes, example:
import logging
#pytest.fixture(scope="class")
def h():
logging.info('h')
class TestOne:
def test_one_one(self, h):
logging.info('test_one_one')
def test_one_two(self, h):
logging.info('test_one_two')
class TestTwo:
def test_two_one(self, h):
logging.info('test_two_one')
Third way: you can annotate class with a fixture by marking #pytest.mark.usefixtrures('fixture_name') and you don't need to pass fixture to each method of the class, it will be passed automatically. Example:
import logging
#pytest.fixture(scope="class")
def h():
logging.info('h')
#pytest.mark.usefixtures('h')
class TestOne:
def test_one_one(self):
logging.info('test_one_one')
def test_one_two(self):
logging.info('test_one_two')
And you can try to use 'autouse' fixture param, so you don't need to pass fixture inside methods, or annotate class with it. Example:
#pytest.fixture(scope="class", autouse=True)
def h():
logging.info('h')
class TestOne:
def test_one_one(self):
logging.info('test_one_one')
def test_one_two(self):
logging.info('test_one_two')
But i don't recommend to use autouse, be aware from use it.

Shorter option names for pytest fixture variants with long names

I am testing a function with several incoming datasets defined as fixtures, but the fixture names get quite cumbersome to distinguish them from one another.
#pytest.fixture()
def dataset_with_foo():
pass
#pytest.fixture()
def dataset_with_bar():
pass
#pytest.fixture()
def dataset_with_foo_and_bar():
pass
def test_something(dataset_with_foo_and_bar):
pass
Is there a way to define some kind of alias for the option name to be shorter? For instance, something like:
#usesfixture("dataset_with_foo_and_bar", option_name="dataset")
def test_something(dataset):
pass
Create a super fixture and helper function to get desired fixture with one fixture.
import pytest
#pytest.fixture
def super_fixture(fixture1,fixture2,fixture3):
local_vars = locals()
def helper(fixture_name):
return local_vars.get(fixture_name)
return helper
def test_a(super_fixture):
# getting fixture1
assert super_fixture("fixture1")
Ok, the best way I could find to do it is by using deferred parametrized fixtures:
#pytest.fixture()
def dataset(request):
mapping = {
"with-foo": create_dataset_with_foo(),
"with-bar": create_dataset_with_bar(),
"with-foo-and-bar": create_dataset_with_foo_and_bar(),
}
return mapping[request.param]
def create_dataset_with_foo():
pass
def create_dataset_with_bar():
pass
def create_dataset_with_foo_and_bar():
pass
#pytest.mark.parametrize("dataset", ["with-foo"], indirect=True)
def test_something(dataset):
pass
#pytest.mark.parametrize("dataset", ["with-foo-and-bar"], indirect=True)
def test_something(dataset):
pass
There has been other attempts using pytest-lazy-fixture or specialized decorator, but I find it a bit too hacky..
https://gist.github.com/wcooley/7472b8de6edb1e8ceda560843c0519a8

Passing different values to decorators for unit tests in Python

I have a situation where I'm trying to modify the arguments passed to a decorator on one of my class methods. The code looks something like this:
class MyClass(object):
#tryagain(retries=3)
def mymethod(self, arg):
... do stuff ...
My problem is I'd like to alter the "retries" variable to something less than 3 when running my unit tests, but keep it at "3" for the production code. Unfortunately, it doesn't look like I can do something like this:
#tryagain(retries=self.retries)
def mymethod(self, arg):
... do stuff ...
or
#tryagain(retries=MyClass.retries)
def mymethod(self, arg):
... do stuff ...
because the class isn't defined at the point the arguments are passed to the decorator (as near as I can tell).
I also tried to add the variable within the module like so:
retries = 1
def MyClass(object):
#tryagain(retries=retries)
def mymethod(self, arg):
... do stuff ...
but then I can't seem to modify the value of "retries" from within my unit tests. Is there another way to accomplish what I'm trying to do?
I assume you try to reduce the number of retrials to increase test speed.
If so, modifying the number of retries variable doesn't seem to be the best approach. Instead, you could unit test the function mymethod without decorator first, and then create a mock function of mymethod. Let's call it mock_mymethod, decorate it with #tryagain and test if the logic of `tryagain actually works.
Check the mock module to see how to create a mock instance, this article about mock is also worth reading.
You could use an environment variable, set from your calling code (it might be good to put a default in here
import os
# ...
class MyClass(object):
#tryagain(retries=int(os.environ['project_num_retries']))
def mymethod(self, arg):
print("mymethod")
Or use a "globals"-type module, for example: project_settings.py containing:
num_retries = 3
Then
import project_settings
class MyClass(object):
#tryagain(retries=project_settings.num_retries)
def mymethod(self, arg):
print("mymethod")
But I'm not sure decorating your code with test information is how you really should go about it -- what about:
class MyClass(object):
def mymethod(self, arg):
print("mymethod")
Then in something like unittests.py:
DEV_TESTS = True # Change to False for production
num_retries = 3 if not DEV_TESTS else 1
import <your class>
class UnitTests():
def __init__(self):
self.c = <your_class>.MyClass()
#tryagain(retries=num_retries)
def test_mymethod(self):
self.c.mymethod("Foo")
t = UnitTests()
t.test_mymethod()
If you were so inclined, this unittests.py could be used with something like python's unittest package with:
DEV_TESTS = True # Change to False for production
num_retries = 3 if not DEV_TESTS else 1
import unittest
import <your class>
class UnitTests(unittest.TestCase):
def setUp(self):
self.c = <your class>.MyClass()
#tryagain(retries=num_retries)
def test_mymethod(self):
self.c.mymethod("Foo")
Note, I used the following simple example of a #tryagain decorator, yours may be more complicated and require some tuning of the examples:
def tryagain(retries):
def wrap(f):
def wrapped_f(*args,**kwargs):
for _ in xrange(retries):
f(*args,**kwargs)
return wrapped_f
return wrap

pytest 2.3 adding teardowns within the class

I'm researching new version of pytest (2.3) and getting very excited about the new functionality where you
"can precisely control teardown by registering one or multiple
teardown functions as soon as they have performed some actions which
need undoing, eliminating the no need for a separate “teardown”
decorator"
from here
It's all pretty clear when it's used as function, but how to use it in the class?
class Test(object):
#pytest.setup(scope='class')
def stp(self):
self.propty = "something"
def test_something(self):
... # some code
# need to add something to the teardown
def test_something_else(self):
... # some code
# need to add even more to the teardown
Ok, I got it working by having a 'session'-wide funcarg finalizer:
#pytest.fixture(scope = "session")
def finalizer():
return Finalizer()
class Finalizer(object):
def __init__(self):
self.fin_funcs = []
def add_fin_func(self, func):
self.fin_funcs.append(func)
def remove_fin_func(self, func):
try:
self.fin_funcs.remove(func)
except:
pass
def execute(self):
for func in reversed(self.fin_funcs):
func()
self.fin_funcs = []
class TestSomething(object):
#classmethod
#pytest.fixture(scope = "class", autouse = True)
def setup(self, request, finalizer):
self.finalizer = finalizer
request.addfinalizer(self.finalizer.execute)
self.finalizer.add_fin_func(lambda: some_teardown())
def test_with_teardown(self):
#some test
self.finalizer.add_fin_func(self.additional_teardown)
def additional_teardown(self):
#additional teardown
Thanks #hpk42 for answering e-mails and helping me get the final version.
NOTE: together with xfailing the rest of the steps and improved scenarios this now makes a pretty good Test-Step structure
Indeed, there are no good examples for teardown yet. The request object has a addfinalizer method. Here is an example usage:
#pytest.setup(scope=...)
def mysetup(request):
...
request.addfinalizer(finalizerfunction)
...
The finalizerfunction will be called when all tests withing the scope finished execution.

How can I define one setup function for all nosetests tests?

I'm using google app engine with python and want to run some tests using nosetest.
I want each test to run the same setup function. I have already a lot of tests, so I don't want to go through them all and copy&paste the same function. can I define somewhere one setup function and each test would run it first?
thanks.
You can write your setup function and apply it using the with_setup decorator:
from nose.tools import with_setup
def my_setup():
...
#with_setup(my_setup)
def test_one():
...
#with_setup(my_setup)
def test_two():
...
If you want to use the same setup for several test-cases you can use a similar method.
First you create the setup function, then you apply it to all the TestCases with a decorator:
def my_setup(self):
#do the setup for the test-case
def apply_setup(setup_func):
def wrap(cls):
cls.setup = setup_func
return cls
return wrap
#apply_setup(my_setup)
class MyTestCaseOne(unittest.TestCase):
def test_one(self):
...
def test_two(self):
...
#apply_setup(my_setup)
class MyTestCaseTwo(unittest.TestCase):
def test_one(self):
...
Or another way could be to simply assign your setup:
class MyTestCaseOne(unittest.TestCase):
setup = my_setup

Categories