How to test a class' inherited methods in pytest - python

house.py:
class House:
def is_habitable(self):
return True
def is_on_the_ground(self):
return True
conftest.py:
import pytest
from house import House
#pytest.fixture(scope='class')
def house():
return House()
test_house.py:
class TestHouse:
def test_habitability(self, house):
assert house.is_habitable()
def test_groundedness(self, house):
assert house.is_on_the_ground()
Up to that point, everything is being tested.
Now I add a subclass and override a method in house.py:
class House:
def is_habitable(self):
return True
def is_on_the_ground(self):
return True
class TreeHouse(House):
def is_on_the_ground(self):
return False
I also add a new fixture for that class in conftest.py:
import pytest
from house import House
from house import TreeHouse
#pytest.fixture(scope='class')
def house():
return House()
#pytest.fixture(scope='class')
def tree_house():
return TreeHouse()
I add a new test class for tree house in test_house.py:
class TestHouse:
def test_habitability(self, house):
assert house.is_habitable()
def test_groundedness(self, house):
assert house.is_on_the_ground()
class TestTreeHouse:
def test_groundedness(self, tree_house):
assert not tree_house.is_on_the_ground()
At that point, the code works, but there are cases that are not tested. For example, to be complete, I would need to test again the methods inherited from House in TreeHouse.
Rewriting the same tests from the TestHouse would not be DRY.
How do I test the inherited method of TreeHouse (in this case is_habitable) without duplicating code?
I would like something like re-testing the TreeHouse with the same tests that its super class runs through, but not for the methods / properties that are new or overridden.
After some research I came across contradictory sources. And after digging in the pytest documentation I fail to understand what applies to this scenario.
I am interested in the pytest way to do this. Please reference the docs and explain how that applies here.

One way to do this would be to use the fixture name house for all test methods (even if it's testing a TreeHouse), and override its value in each test context:
class TestTreeHouse(TestHouse):
#pytest.fixture
def house(self, tree_house):
return tree_house
def test_groundedness(self, house):
assert not house.is_on_the_ground()
Also note TestTreeHouse inherits from TestHouse. Since pytest merely enumerates methods of classes (i.e. there is no "registration" done with, say, a #pytest.test() decorator), all tests defined in TestHouse will be discovered in subclasses of it, without any further intervention.

You can use pytest parameterization to pass multiple arguments to the same test, in this case, the argument would most likely be the class being tested.

Related

How to override a mock for an individual test within a class that already has a mock

I have a test class that has a mock decorator, and several tests. Each test receives the mock, because mock is defined on the class level. Great. Here's what it looks like:
#mock.patch("foo", bar)
class TestMyThing(TestCase):
def test_A(self):
assert something
def test_B(self):
assert something
def test_C(self):
assert something
def test_D(self):
assert something
I now want test_D to get a have a different value mocked for foo. I first try:
#mock.patch("foo", bar)
class TestMyThing(TestCase):
def test_A(self):
assert something
def test_B(self):
assert something
def test_C(self):
assert something
#mock.patch("foo", baz)
def test_D(self):
assert something
This doesn't work. Currently to get unittest to take the mock.patch that decorates test_D, I have to remove the mock.patch that decorates the class. This means creating lots of DRY and doing the following:
class TestMyThing(TestCase):
#mock.patch("foo", bar)
def test_A(self):
assert something
#mock.patch("foo", bar)
def test_B(self):
assert something
#mock.patch("foo", bar)
def test_C(self):
assert something
#mock.patch("foo", baz)
def test_D(self):
assert something
This is non ideal due to DRY boilerplate, which makes it error prone and violates open-closed principle. Is there a better way to achieve the same logic?
Yes! You can leverage the setUp/tearDown methods of the unittest.TestCase and the fact that unittest.mock.patch in its "pure" form (i.e. not as context manager or decorator) returns a "patcher" object that has start/stop methods to control when exactly it should do its magic.
You can call on the patcher to start inside setUp and to stop inside tearDown and if you keep a reference to it in an attribute of your test case, you can easily stop it manually in selected test methods. Here is a full working example:
from unittest import TestCase
from unittest.mock import patch
class Foo:
#staticmethod
def bar() -> int:
return 1
class TestMyThing(TestCase):
def setUp(self) -> None:
self.foo_bar_patcher = patch.object(Foo, "bar", return_value=42)
self.mock_foo_bar = self.foo_bar_patcher.start()
super().setUp()
def tearDown(self) -> None:
self.foo_bar_patcher.stop()
super().tearDown()
def test_a(self):
self.assertEqual(42, Foo.bar())
def test_b(self):
self.assertEqual(42, Foo.bar())
def test_c(self):
self.assertEqual(42, Foo.bar())
def test_d(self):
self.foo_bar_patcher.stop()
self.assertEqual(1, Foo.bar())
This patching behavior is the same, regardless of the different variations like patch.object (which I used here), patch.multiple etc.
Note that for this example it is not necessary to keep a reference to the actual MagicMock instance generated by the patcher in an attribute, like I did with mock_foo_bar. I just usually do that because I often want to check something against the mock instance in my test methods.
It is worth mentioning that you can also use setUpClass/tearDownClass for this, but then you need to be careful with re-starting the patch, if you stop it because those methods are (as the name implies) only called once for each test case, whereas setUp/tearDown are called once before/after each test method.
PS: The default implementations of setUp/tearDown on TestCase do nothing, but it is still good practice IMO to make a habit of calling the superclass' method, unless you deliberately want to omit that call.

How to use an object's __subclasses__() method in bulk pytest execution?

I'm looking to write unit tests for some code that uses an object's __subclasses__() method. Ultimately, I was trying to use __subclassess__() to keep track of classes dynamically imported into my application through a namespace package.
In some cases, my code base has test classes created within a pytest file. My problem is that when I run pytest in bulk on my source directory I find failures or errors in my code due to import polutions from these one-off test classes. This is because the pytest run maintains all of the imports as it runs through the source tree. On their own the tests pass fine, but in a sequence they fail, sometimes depending on the order in which they ran.
In my current code branch these __subclasses__() invocations are in the application code, but I have moved them out to tests here to demonstrate with a MVE:
In my/example.py
class MyClass(object):
def __init__(self):
pass
class MySubClass(MyClass):
def __init__(self):
super().__init__()
In my/test_subclass.py
from my.example import MyClass
class TestSubClass(MyClass):
def __init__(self):
super().__init__()
def test_TestSubClass():
assert issubclass(TestSubClass, MyClass)
In my/test_subclasses.py
from my.example import MySubClass, MyClass
def test_find_subclasses():
assert all([cls == MySubClass for cls in MyClass.__subclasses__()])
The result, when run on my machine, is that the test_find_subclasses() test fails due to the discovery of the TestSubClass when running after test_subclass.py:
def test_find_subclasses():
> assert all([cls == MySubClass for cls in MyClass.__subclasses__()])
E assert False
E + where False = all([True, False])
What is the best way to maintain a "clean" state during sequenced pytest runs so that I can avoid mangling imports?
As discussed in the comments, you probably don't want to hard-code the types that may extend MyClass, since you really can't predict what your application will need in the future. If you want to check subclassing, just check that it works at all:
def test_find_subclasses():
assert MySubClass in MyClass.__subclasses__()
Even more concisely, you could simply do
def test_find_subclasses():
assert issubclass(MySubClass, MyClass)
That being said, you can technically filter the classes you are looking through. In your particular case, you have a distinctive naming convention, so you can do something like
def only_production_classes(iterable):
return [cls for cls in iterable if not cls.__name__.lower().startswith('test')]
def test_find_subclasses():
assert all([cls == MySubClass for cls in only_production_classes(MyClass.__subclasses__())])
You can define only_production_classes using other criteria, like the module that the class appears in:
def only_production_classes(iterable):
return [cls for cls in iterable if not cls.__module__.lower().startswith('test_')]
You can't easily unlink class objects that have been loaded, so your idea of a "clean" test environment is not quite feasible. But you do have the option of filtering the data that you work with based on where it was imported from.

Python-mock: how to test if super() was called

I have the following structure:
class A(Object):
def method(self):
return 'a'
class B(A):
def __init__(self, test):
self.test = test
def method(self):
if self.test:
return super(A, self).method(self)
else:
return 'b'
What I want to do is write a test-case which would test that if self.test is true then the super function was called and class A's method function was called.
How I can achieve this? What should I mock?
Advanced Q: What about when Class A and B are in a separate module, and they have the same name.
So instead of class B I would write: class A(module1.A):
does that change the mocking?
As #MartijnPieters points out, testing that super is called is typically a test of an implementation detail, rather than a test of a contract. Nonetheless, testing the specific implementation may still be desirable -- or the parent class may have a contract requiring the call to super. To test that super is called, use a mock, as mentioned by #mgilson. The answer in detail:
import mock, unittest
class TestB(unittest.TestCase):
#mock.patch("A.method")
def test_super_method(self, mock_super):
B(True).method()
self.assertTrue(mock_super.called)
#mock.patch("A.method")
def test_super_method(self, mock_super):
B(False).method()
self.assertFalse(mock_super.called)
You'll need to specify the full namespace in the patch. E.g., #mock.patch("module.submodule.A.method"). This can be done for any method in A, including __init__; syntax is exactly the same.
To build off of #Malina's excellent answer, if you can't patch the method on module A (e.g. A is in a third party library, part of a nasty legacy system, dynamically created at runtime, etc.) you can still test this functionality by creating another method on class B explicitly for the purpose of testing.
Revised Class B
import A
class B(A):
def __init__(self, test):
self.test = test
def method(self):
if self.test:
self._delegate_to_A() # Move the super call to a separate method
else:
return 'b'
def _delegate_to_A(self)
return super(A, self).method(self)
Revised Test Code for Class B
import mock, unittest
class TestB(unittest.TestCase):
# Patch B's `delegate` method instead of A
#mock.patch("B._delegate_to_A")
def test_super_method(self, mock_super):
B(True).method()
self.assertTrue(mock_super.called)
#mock.patch("B._delegate_to_A")
def test_super_method(self, mock_super):
B(False).method()
self.assertFalse(mock_super.called)

Python mock: mocking base class for inheritance

I am testing a class that inherits from another one very complex, with DB connection methods and a mess of dependences. I would like to mock its base class so that I can nicely play with the method defined in the subclass, but in the moment I inherit from a mocked class, the object itself turns a mock and loses all its methods.
How can I mock a superclass?
More or less the situation can be summed up in this:
import mock
ClassMock = mock.MagicMock()
class RealClass(ClassMock):
def lol(self):
print 'lol'
real = RealClass()
real.lol() # Does not print lol, but returns another mock
print real # prints <MagicMock id='...'>
This is a simplified case. What is actually happening is that RealClass extends AnotherClass, but I managed to intercept the AnotherClass and replace it with a mock.
This is something I've been struggling with for a long time, but I think I've finally found a solution.
As you already noticed, if you try to replace the base class with a Mock, the class you're attempting to test simply becomes the mock, which defeats your ability to test it. The solution is to mock only the base class's methods rather than the entire base class itself, but that's easier said than done: it can be quite error prone to mock every single method one by one on a test by test basis.
What I've done instead is created a class that scans another class, and assigns to itself Mock()s that match the methods on the other class. You can then use this class in place of the real base class in your testing.
Here is the fake class:
class Fake(object):
"""Create Mock()ed methods that match another class's methods."""
#classmethod
def imitate(cls, *others):
for other in others:
for name in other.__dict__:
try:
setattr(cls, name, Mock())
except (TypeError, AttributeError):
pass
return cls
So for example you might have some code like this (apologies this is a little bit contrived, just assume that BaseClass and SecondClass are doing non-trivial work and contain many methods and aren't even necessarily defined by you at all):
class BaseClass:
def do_expensive_calculation(self):
return 5 + 5
class SecondClass:
def do_second_calculation(self):
return 2 * 2
class MyClass(BaseClass, SecondClass):
def my_calculation(self):
return self.do_expensive_calculation(), self.do_second_calculation()
You would then be able to write some tests like this:
class MyTestCase(unittest.TestCase):
def setUp(self):
MyClass.__bases__ = (Fake.imitate(BaseClass, SecondBase),)
def test_my_methods_only(self):
myclass = MyClass()
self.assertEqual(myclass.my_calculation(), (
myclass.do_expensive_calculation.return_value,
myclass.do_second_calculation.return_value,
))
myclass.do_expensive_calculation.assert_called_once_with()
myclass.do_second_calculation.assert_called_once_with()
So the methods that exist on the base classes remain available as mocks you can interact with, but your class does not itself become a mock.
And I've been careful to ensure that this works in both python2 and python3.
This should work for you.
import mock
ClassMock = mock.MagicMock # <-- Note the removed brackets '()'
class RealClass(ClassMock):
def lol(self):
print 'lol'
real = RealClass()
real.lol() # Does not print lol, but returns another mock
print real # prints <MagicMock id='...'>
You should'nt pass an instance of the class as you did. mock.MagicMock is a class, so you pass it directly.
In [2]: inspect.isclass(mock.MagicMock)
Out[2]: True
I was facing a similar problem and was able to do this via #patch.object. See examples for patch decorators in the official python doc.
class MyTest(unittest.TestCase):
#patch.object(SomeClass, 'inherited_method')
def test_something(self, mock_method):
SomeClass.static_method()
mock_method.assert_called_with()
Just exemplifying #Akash's answer, which was the one that in fact solved my inheritance mock challenge:
#patch.object(SomeClassInheritingAnother, "inherited_method")
def test_should_test_something(self, mocked_inherited_method, mocker, caplog):
#Mocking an HTTP result status code
type(mocked_inherited_method.return_value).status_code = mocker.PropertyMock(return_value=200)
#Calling the inherited method, that should end up using the mocked method
SomeClassInheritingAnother.inherited_method()
#Considering that the request result is being logged as 'Request result: {response.status_code}'
assert "Request result: 200" in caplog.text

Is it possible to make Nose only run tests which are sub-classes of TestCase or TestSuite (like unittest.main())

My test framework is currently based on a test-runner utility which itself is derived from the Eclipse pydev python test-runner. I'm switching to use Nose, which has many of the features of my custom test-runner but seems to be better quality code.
My test suite includes a number of abstract test-classes which previously never ran. The standard python testrunner (and my custom one) only ran instances of unittest.TestCase and unittest.TestSuite.
I've noticed that since I switched to Nose it's running just about anything which starts withthe name "test" which is annoying... because the naming convention we used for the test-mixins also looks like a test class to Nose. Previously these never ran as tests because they were not instances of TestCase or TestSuite.
Obviously I could re-name the methods to exclude the word "test" from their names... that would take a while because the test framework is very big and has a lot of inheritance. On the other hand it would be neat if there was a way to make Nose only see TestCases and TestSuites as being runnable... and nothing else.
Can this be done?
You could try to play with -m option for nosetests. From documentation:
A test class is a class defined in a
test module that matches testMatch or
is a subclass of unittest.TestCase
-m sets that testMatch, this way you can disable testing anything starting with test.
Another thing is that you can add __test__ = False to your test case class declaration, to mark it “not a test”.
If you want a truly abstract test class, you could just inherit the abstract class from object, then inherit in the testcases later.
For example:
class AbstractTestcases(object):
def test_my_function_does_something(self):
self.assertEquals("bar", self.func())
And then use it with:
class TestMyFooFunc(AbstractTestcases, unittest.TestCase):
def setUp(self):
self.func = lambda: "foo"
Then nosetests will pick up only the testcases in TestMyFooFunc and not those in AbstractTestCases.
You could use nose's --attr argument to specify an attribute posessed by the unittest.TestCase. For instance, I use:
nosetests --attr="assertAlmostEqual"
You could get even more careful by using and and or matching:
nosetests -A "assertAlmostEqual or addTest"
See unittest's documentation for a full list of methods/attributes, and Nose's description of the capabilities of the --attr plugin.
One addendum to #nailxx 's answer:
You could set __test__ = False in the parent class and then use a metaclass (see This question with some brilliant explanations) to set it back to True when subclassing.
(Finally, I found an excuse to use a metaclass!)
Although __test__ is a double underscore attribute, we have to explicitly set it to True, since not setting it would cause python just to lookup the attribute further up the MRO and evaluate it to False.
Thus, we need to check at class instantiation whether one of the parent classes has __test__ = False. If this is the case and the current class definition has not set __test__ itself, we shall add '__test__': True to the attributes dict.
The resulting code looks like this:
class TestWhenSubclassedMeta(type):
"""Metaclass that sets `__test__` back to `True` when subclassed.
Usage:
>>> class GenericTestCase(TestCase, metaclass=TestWhenSubclassed):
... __test__ = False
...
... def test_something(self):
... self.fail("This test is executed in a subclass, only.")
...
...
>>> class SpecificTestCase(GenericTestCase):
... pass
"""
def __new__(mcs, name, bases, attrs):
ATTR_NAME = '__test__'
VALUE_TO_RESET = False
RESET_VALUE = True
values = [getattr(base, ATTR_NAME) for base in bases
if hasattr(base, ATTR_NAME)]
# only reset if the first attribute is `VALUE_TO_RESET`
try:
first_value = values[0]
except IndexError:
pass
else:
if first_value == VALUE_TO_RESET and ATTR_NAME not in attrs:
attrs[ATTR_NAME] = RESET_VALUE
return super().__new__(mcs, name, bases, attrs)
One could extend this to some more implicit behaviour like “if the name starts with Abstract, set __test__ = False automatically”, but I for myself would keep the explicit assignment for clarity.
Let me paste simple unittests to demonstrate the behavior – and as a reminder that everybody should take the two minutes to test their code after introducing a feature.
from unittest import TestCase
from .base import TestWhenSubclassedMeta
class SubclassesTestCase(TestCase):
def test_subclass_resetted(self):
class Base(metaclass=TestWhenSubclassedMeta):
__test__ = False
class C(Base):
pass
self.assertTrue(C.__test__)
self.assertIn('__test__', C.__dict__)
def test_subclass_not_resetted(self):
class Base(metaclass=TestWhenSubclassedMeta):
__test__ = True
class C(Base):
pass
self.assertTrue(C.__test__)
self.assertNotIn('__test__', C.__dict__)
def test_subclass_attr_not_set(self):
class Base(metaclass=TestWhenSubclassedMeta):
pass
class C(Base):
pass
with self.assertRaises(AttributeError):
getattr(C, '__test__')
You can also use multiple inheritance on the test case level and let the base class inherit from object only. See this thread:
class MyBase(object):
def finishsetup(self):
self.z=self.x+self.y
def test1(self):
self.assertEqual(self.z, self.x+self.y)
def test2(self):
self.assert_(self.x > self.y)
class RealCase1(MyBase, unittest.TestCase):
def setUp(self):
self.x=10
self.y=5
MyBase.finishsetup(self)
class RealCase2(MyBase, unittest.TestCase):
def setUp(self):
self.x=42
self.y=13
MyBase.finishsetup(self)

Categories