I'm trying to implement an integration test framework using nose. At the core, I'd like a base class that all test classes inherit. I'd like to have a class setup function that is called as well as the per test setup function. When I use nosetests a_file.py -vs where a_file.py looks like this:
from nose import tools
class BaseClass(object):
def __init__(self):
print 'Initialize Base Class'
def setup(self):
print "\nBase Setup"
def teardown(self):
print "Base Teardown"
#tools.nottest
def a_test(self):
return 'This is a test.'
#tools.nottest
def another_test(self):
return 'This is another test'
class TestSomeStuff(BaseClass):
def __init__(self):
BaseClass.__init__(self)
print 'Initialize Inherited Class'
def setup(self):
BaseClass.setup(self)
print "Inherited Setup"
def teardown(self):
BaseClass.teardown(self)
print 'Inherited Teardown'
def test1(self):
print self.a_test()
def test2(self):
print self.another_test()
Outputs this:
Initialize Base Class
Initialize Inherited Class
Initialize Base Class
Initialize Inherited Class
cases.nose.class_super.TestSomeStuff.test1 ...
Base Setup
Inherited Setup
This is a test.
Base Teardown
Inherited Teardown
ok
cases.nose.class_super.TestSomeStuff.test2 ...
Base Setup
Inherited Setup
This is another test
Base Teardown
Inherited Teardown
ok
----------------------------------------------------------------------
Ran 2 tests in 0.001s
OK
How do I make the __init__, setup, and teardown functions class methods? When I attempt this:
from nose import tools
class BaseClass(object):
def __init__(self):
print 'Initialize Base Class'
#classmethod
def setup_class(self):
print "\nBase Setup"
#classmethod
def teardown_class(self):
print "Base Teardown"
#tools.nottest
def a_test(self):
return 'This is a test.'
#tools.nottest
def another_test(self):
return 'This is another test'
class TestSomeStuff(BaseClass):
def __init__(self):
BaseClass.__init__(self)
print 'Initialize Inherited Class'
#classmethod
def setup_class(self):
BaseClass.setup_class(self)
print "Inherited Setup"
#classmethod
def teardown_class(self):
BaseClass.teardown_class(self)
print 'Inherited Teardown'
def test1(self):
print self.a_test()
def test2(self):
print self.another_test()
I get this:
Initialize Base Class
Initialize Inherited Class
Initialize Base Class
Initialize Inherited Class
ERROR
======================================================================
ERROR: test suite for <class 'cases.nose.class_super.TestSomeStuff'>
----------------------------------------------------------------------
Traceback (most recent call last):
File "/usr/lib/python2.7/dist-packages/nose/suite.py", line 208, in run
self.setUp()
File "/usr/lib/python2.7/dist-packages/nose/suite.py", line 291, in setUp
self.setupContext(ancestor)
File "/usr/lib/python2.7/dist-packages/nose/suite.py", line 314, in setupContext
try_run(context, names)
File "/usr/lib/python2.7/dist-packages/nose/util.py", line 478, in try_run
return func()
File "/home/ryan/project/python_testbed/cases/nose/class_super.py", line 30, in setup_class
BaseClass.setup_class(self)
TypeError: setup_class() takes exactly 1 argument (2 given)
----------------------------------------------------------------------
Ran 0 tests in 0.001s
FAILED (errors=1)
Removing the self from the super class calls (BaseClass.setup_class(self) -> BaseClass.setup_class()) seems to fix it...which I don't understand:
Initialize Base Class
Initialize Inherited Class
Initialize Base Class
Initialize Inherited Class
Base Setup
Inherited Setup
cases.nose.class_super.TestSomeStuff.test1 ... This is a test.
ok
cases.nose.class_super.TestSomeStuff.test2 ... This is another test
ok
Base Teardown
Inherited Teardown
----------------------------------------------------------------------
Ran 2 tests in 0.001s
OK
However, this doesn't help with the __init__ function. How can I make this a class method? Why does passing in self to the super class fail?
Does anyone have some info on this?
Class methods take a single implicit argument, (called cls by convention, although you have called it self too), like instance methods take self.
When you call
BaseClass.setup_class(self)
It's really more like
BaseClass.setup_class(BaseClass, self)
hence the warning over two arguments. Therefore it's fixed when you ditch self; as a reminder, change the definitions:
#classmethod
def setup_class(cls):
Oh, and __init__ makes no sense as a #classmethod; it's for setting up instances.
Looks like #Jonrsharpe already beat me, but yeah. I'll post it anyway.
This one might explain the setup_class() error. It is passing in an instance of BaseClass as well as 'self' (or TestSomeStuff) when you include the self. Nose must be hard coded to not allow more then 1 parameter in that function (or Unittest, not sure which one has that requirement).
As for the init part, after reading through the Unittest documentation, it appears that the code would be better printed as:
def __init__(self):
print 'Initialize Base Class Object'
And:
def __init__(self):
BaseClass.__init__(self)
print 'Initialize Inherited Class Object'
Since it's basically creating an object for every test case and running that init function to make sure the test case is ready.
Related
Some cases classes should not call the constructor of their super class, like those inherited from abstract classes:
class Father:
def __init__(self):
pass
class Son(Father):
def __init__(self):
self.salary = 700
def __repr__(self):
return f"my salary is {self.salary}"
print(Son())
Still, my legacy code contains a linter that complains about that:
$ pylint3 --disable=too-few-public-methods,missing-docstring main.py
No config file found, using default configuration
************* Module main
W: 5, 4: __init__ method from base class 'Father' is not called (super-init-not-called)
Is there any way to convey this fact to pylint?
If the Father class is abstract you should not have an __init__ (well except if the init does something then you should call it) and you can explicitly make it inherits from ABC like this:
import abc
class Father(abc.ABC):
#abc.abstractmethod
def interface(self):
...
class Son(Father):
def __init__(self):
self.salary = 700
def __repr__(self):
return f"my salary is {self.salary}"
def interface(self):
print(repr(self))
class BadSon(Father):
"""interface not implemented here"""
print(Son())
print(BadSon())
pylint understands what's going on:
a.py:26:6: E0110: Abstract class 'BadSon' with abstract
methods instantiated (abstract-class-instantiated)
But when you launch with python there's an error too:
my salary is 700
Traceback (most recent call last):
File "b.py", line 26, in <module>
print(BadSon())
TypeError: Can't instantiate abstract class BadSon with abstract methods interface
See the documentation for the abc module
I have a code structure where we have a lot of sub classes from a common base class.
I want in an automated fashion test each subclass without having a separate class definition inheriting from TestCase for each subclass.
with a classs like
class MyBaseClass:
...
I can get a list of all subclasses that inherits from MyBaseClass with
import all_module_that_includes_subclasses
list_of_all_subclasses = MyBaseClass.__subclasses__()
But how do I connect to this list of scubclasses to avoid manually createing TestCases for each subclass like:
class TestSubClass1(TestCase):
def test_method_1(self):
...
...
class TestSubClass2(TestCase):
def test_method_1(self):
...
...
etc.
I thought of setting up an instance of all classes in setUp() method and looping through them in each def test_method_#(). This will somewhat work, I think, but it will break as soon as one single class fails. I want to be able to run through all classes and get a full report what class failed and in what tests.
Thankful for help :)
You can create those test cases dynamically using the load_tests protocol. For this you simply define a top-level function called load_tests which should return a TestSuite.
import unittest
class MyBaseClass:
pass
class Foo(MyBaseClass):
pass
class Bar(MyBaseClass):
pass
class TestBases:
# Wrapped into other class, so it won't be discovered by unittest.
class TestCase(unittest.TestCase):
subclass: MyBaseClass
def test(self):
self.assertIsInstance(self.subclass(), MyBaseClass)
def load_tests(loader, tests, pattern):
for cls in MyBaseClass.__subclasses__():
test_cls = type(f'Test{cls.__name__}', (TestBases.TestCase,), dict(subclass=cls))
tests.addTests(loader.loadTestsFromTestCase(test_cls))
return tests
if __name__ == '__main__':
unittest.main()
This runs the two dynamically generated test cases TestFoo and TestBar:
$ python /tmp/test.py -v
test (__main__.TestFoo) ... ok
test (__main__.TestBar) ... ok
----------------------------------------------------------------------
Ran 2 tests in 0.000s
I have a class that I want to patch in my unittests.
class OriginalClass():
def method_a():
# do something
def method_b():
# do another thing
Now I created another class to patch it with, so the code for patching it is like
class MockClass(OriginalClass):
def method_a():
# This will override the original method and return custom response for testing.
patcher = patch('OriginalClass', new=MockClass)
mock_instance = patcher.start()
This works exactly as I want it to and I can return whatever responses required for my unittests.
Now this issue is when I want to verify that a method is called with the right parameters in the unittests.
I tried
mock_instance.method_a.assert_called_once()
But it fail with error AttributeError: 'function' object has no attribute 'assert_called_once'.
How can I test the method calls here?
AttributeError: 'function' object has no attribute 'assert_called_once'.
Once mock object is created, there is no method_a exists, you have to call once m.method_a() before assert.
m = mock.create_autospec(OriginalClass)
m.method_a()
m.method_a.assert_called_once()
patch mock entire class
I took it as mock the whole class and all its methods, I would take an example from here
https://docs.python.org/3.3/library/unittest.mock-examples.html
Applying the same patch to every test method, Here is my example, patch the entire Primary class as MockPrimay for every methods and every tests, setup or SetupClass could be added for the methods needed, even the whole class is mocked, but not every methods to be used in the tests.
from tests.lib.primary_secondary import Secondary
#mock.patch('tests.lib.primary_secondary.Primary')
class TestSecondaryMockPrimary(unittest.TestCase):
def test_method_d(self, MockPrimary):
MockPrimary().process()
MockPrimary().process.return_value = 1
oc = Secondary()
self.assertEqual(oc.method_d(), 1)
import tests
self.assertIs(tests.lib.primary_secondary.Primary, MockPrimary)
The Primary is needed for the Secondary for this test
class Primary(object):
def __init__(self, param):
self._param = param
def process(self):
if self._param == 1:
self._do_intermediate_process()
self._do_process()
class Secondary(object):
def __init__(self):
self.scl = Primary(1)
def method_d(self):
return self.scl.process
I think wraps can be useful here:
from unittest.mock import patch
class Person:
name = "Bob"
def age(self):
return 35
class Double(Person):
def age(self):
return 5
with patch('__main__.Person', wraps=Double()) as mock:
print(mock.name) # mocks data
print(mock.age()) # runs real methods, but still spies their calls
mock.age.assert_not_called()
Output:
<MagicMock name='Person.name' id='139815250247536'>
5
...
raise AssertionError(msg)
AssertionError: Expected 'age' to not have been called. Called 1 times.
Calls: [call()].
I'm working with unittest and selenium for the first time but thats also my first bigger code in python. I found some answers for this problem but there was no explaination for classes with __ init __ inheritance and method super().__ init __()
TL;DR
I have classes that inherits one by one. The first class StartInstance that creates chrome instance inherits from unittest.TestCase and the problem is with inheritance and super().init() in other classes, becouse when i remove it test normaly starts
All looks like:
class StartInstance(unittest.TestCase):
#classmethod
def setUpClass(cls): pass
class A(StartInstance):
def __init__(self):
super().__init__() adding variables etc to init
class B(A):
def __init__(self):
super().__init__() adding variables etc to init
class C(A):
def __init__(self):
super().__init__() adding variables etc to init
class PrepareTests(B, C, D):
def all tests(self):
self.tests_B
self.tests_C
self.tests_D
class Tests(PrepareTests):
def test_click:
click()
all_tests()
#and finally somewhere a test runner
suite = loader.loadTestsFromTestCase(Tests)
runner.run(suite())
#when i run this i get this error and it only raises when i
add classes with their own init
#heh TL;DR almost as long as normal text sorry :(
ALL:
FULL ERROR MESSAGE:
Traceback (most recent call last):
File "xyz\main_test.py", line 24,
in <module>
runner.run(suite())
File "xyz\main_test.py", line 11,
in suite
about_us = loader.loadTestsFromTestCase(AboutUs)
File
"xyz\Python36\lib\unittest\loader.py", line 92, in loadTestsFromTestCase
loaded_suite = self.suiteClass(map(testCaseClass, testCaseNames))
File "xyz\Python36\lib\unittest\suite.py", line 24, in __init__
self.addTests(tests)
File "xyz\Python36\lib\unittest\suite.py", line 57, in addTests
for test in tests:
TypeError: __init__() takes 1 positional argument but 2 were given
Thats my code:
I have a settings.py file with variables in dict of dicts accessed by settings.xyz["layer1"]["KEY"]
setup.py - setup class for selenium
class StartInstance(unittest.TestCase):
#classmethod
def setUpClass(cls):
cls.driver = webdriver.Chrome()
cls.driver.get(settings.URLS['MAIN_URL'])
cls.driver.implicitly_wait(2)
cls.driver.maximize_window()
#classmethod
def tearDownClass(cls):
cls.driver.quit()
main_tests_config.py - next layer - now very basic configs
class MainTestConfig(StartInstance):
def __init__(self):
super().__init__()
self.language = settings.TEST_LANGUAGE
self.currency = settings.TEST_CURRENCY
header.py (got few more files like this) - next layer translates code from config.py to class variables, inherits from previous classes becouse it needs global language
class HeaderPath(MainTestConfig):
def __init__(self):
super().__init__()
self.logo_path = settings.PAGE_PATHS["HEADER"]["LOGO"]
self.business_path = settings.PAGE_PATHS["HEADER"]["BUSINESS"]
class HeaderText(MainTestConfig):
def __init__(self):
super().__init__()
self.business_text = settings.PAGE_CONTENT[self.language]["HEADER"]["BUSINESS"]
self.cart_text = settings.PAGE_CONTENT[self.language]["HEADER"]["CART"]
header_tests.py - next layer, inherits variables(HeadetText, HeaderPath, Urls(class Urls it is class with page urls variables)), language(from MainTestConfig), and selenium driver (from first class StartInstance), example build of class
class HeaderStaticTest(HeaderText, HeaderPath, Urls):
def header_static(self):
self.logo_display()
self.method2()
# etc..
def logo_display(self):
self.driver.find_element_by_xpath(self.logo_path)
def self.method2(self):
pass
static_display.py - next layer, class that inherits all classes with tests like previous one and uses their methods that runs all tests but not as test_
class StaticDisplay(HeaderStaticTest, HorizontalStaticTest, VerticalStaticTest):
def static_display(self):
self.header_static()
self.horizontal_static()
self.vertical_static()
test_about_us.py - next layer, a normal unittest testcase it inherits only a previous one but in general it inherits all prevous classes that i wrote, now i can test all "static views" on page that dont change when i click on button
class AboutUs(StaticDisplay):
def test_horizontal_menu_click(self):
about_us_element = self.driver.find_element_by_id(self.hor_about_path)
about_us_element.click()
self.assertIn(
self.about_url,
self.driver.current_url
)
def test_check_static_after_horizontal(self):
self.static_display()
(finally)
main_cases.py - runner with this error, and it only raises when I add classes with their own init... have no idea how to repair it... please help
def suite():
loader = unittest.TestLoader()
about_us = loader.loadTestsFromTestCase(AboutUs)
return unittest.TestSuite([about_us])
if __name__ == '__main__':
runner = unittest.TextTestRunner()
runner.run(suite())
As i said there is somewhere problem with this new def __ init __ and super().__ init __() in new classes... where I'm making a mistake?
When i start this test case i get an error:
TypeError: __ init __() takes 1 positional argument but 2 were given Full error message above, probably it is needed
Can someone help me please?
TestCase instances take an optional keyword argument methodName; I'm guessing the unittest module passes this explicitly behind the scenes at some point. Typically when I'm subclassing classes I didn't make myself I'll use this pattern; this should fix your problem:
class SubClass(SupClass):
def __init__(self, *args, **kwargs):
super().__init__(*args, **kwargs)
Especially when you're not passing any arguments to your __init__ method, passing through the arguments in this manner is a good way to avoid the error you're getting. If you do want to pass something custom to your __init__ method, you can do something like this:
class SubClass(SupClass):
def __init__(self, myarg, *args, **kwargs):
super().__init__(*args, **kwargs)
# do something with your custom argument
Ok, I found the problem... It is something else than you wrote the solution that you wrote is good too and I also used It in my code.
Problem from my question was here:
suite = loader.loadTestsFromTestCase(Tests)
runner.run(suite())
I should change to:
something = Tests
suite = loader.loadTestsFromTestCase(something)
runner.run(suite())
BUT another thing is that I had to rebuild it completly to work properly
At start: inheritance __ init __ from previous class with super was stupid becouse I didn't have acces to the class before... so I couldn't access my variables. So I've changed:
class SubClass(SupClass):
def __init__(self, *args, **kwargs):
super().__init__(*args, **kwargs)
with:
class SubClass(SupClass):
def __init__(self, *args, **kwargs):
super(SubClass, self).setUpClass(*args, **kwargs)
Then I understood that __ init __ inheritance from unittest.TestCase is impossible. To imagine what happened...
My new way to start test cases:
something = Tests
it was creating a new error... that said i have to use Tests() but when I used this form I got and previous error so both ways was bad.
So I just created class with variables, but with no __ init __ and no super()
class Something(StartInstance):
a = thing1
b = thing2
c = thing3
I'm writing this becouse maybe someone in future will try to use __ init __ with unittest... It doesn't work... or I just didn't find the solution, but I'm new :P
I have unittest code like the following:
import unittest
class MyUnitTest(unittest.TestCase):
def setUpClass(self):
do_something_expensive_for_all_sets_of_tests()
class MyFirstSetOfTests(MyUnitTest):
def setUpClass(self):
super(MyFirstSetOfTests, self).setUpClass()
do_something_expensive_for_just_these_first_tests()
def test_one(self):
...
def test_two(self):
...
class MySecondSetOfTests(MyUnitTest):
def setUpClass(self):
super(MySecondSetOfTests, self).setUpClass()
do_something_expensive_for_just_these_second_tests()
def test_one(self):
...
def test_two(self):
...
if __name__ == '__main__':
unittest.main()
When I try to run this code, I get an error like this:
======================================================================
ERROR: setUpClass (__main__.MyFirstSetOfTests)
----------------------------------------------------------------------
TypeError: unbound method setUpClass() must be called with MyFirstSetOfTests instance as first argument (got nothing instead)
----------------------------------------------------------------------
setUpClass must be a class method. From the documentation:
A class method called before tests in an individual class run. setUpClass is called with the class as the only argument and must be decorated as a classmethod():
#classmethod
def setUpClass(cls):
...
See Class and Module Fixtures for more details.
Your version is missing the #classmethod decorator:
class MyUnitTest(unittest.TestCase):
#classmethod
def setUpClass(cls):
do_something_expensive_for_all_sets_of_tests()
class MyFirstSetOfTests(MyUnitTest):
#classmethod
def setUpClass(cls):
super(MyFirstSetOfTests, cls).setUpClass()
do_something_expensive_for_just_these_first_tests()
The error is thrown because MyFirstSetOfTests.setUpClass() is called on the class, not on an instance, but you didn't mark your method as a classmethod and thus it was not passed in the automatic self argument. In the above updated code I used cls instead, to reflect that the name references the class object.