Use mock or create real instance with workaround while testing - python

I have class MyClass in file main.py
class MyClass:
def __init__(self, a=1, b=2, c=3):
self.a = a
self.b = b
self.c = c
self.d = None
self.e = None
self.f = None
def method_1(self):
return self.a + self.b
def method_2(self):
return self.d + 4
def method_3(self):
return self.e + 10
def run(self):
self.d = self.method_1()
self.e = self.method_2()
self.f = self.method_3()
# a lot more stuff here that takes time
and I have the following tests in file test_file.py
import unittest
from unittest.mock import patch
from main import MyClass
class TestMyClass(unittest.TestCase):
def test_method_2_v1(self):
# This test creates a real instance, but the input self.d is created manually in the test,
# in a different way it is done in the production code (in the production code it is done through run())
instance = MyClass()
instance.d = instance.method_1()
instance.e = instance.method_2()
assert instance.e == 7
def test_method_2_v2(self):
# This test creates a real instance, but the input self.d is created manually in the test,
# in a different way it is done in the production code (in the production code it is done through run())
# This case does not run method_1, but gives an explicit value to self.d
instance = MyClass()
instance.d = 3
instance.e = instance.method_2()
assert instance.e == 7
#patch('main.MyClass', spec=True)
def test_method_2_v3(self, my_class_mock):
# This test uses a mock
mock_instance = my_class_mock()
mock_instance.configure_mock(d=3)
assert MyClass.method_2(mock_instance) == 7
I believe the comments and the code clearly explain the differences.
Which one is the best practice and why?
Is there a better solution?

Main difference between the 3 methods
I think that the main difference between the 3 test methods is:
test_method_2_v1() invokes method_1() of MyClass
test_method_2_v2() and test_method_2_v3() don't invoke method_1() of MyClass
This means that test_method_2_v1() executes an indirect test of method_1() while the other 2 test methods execute only a test of method_2().
Comparison between test_method_2_v2() and test_method_2_v3()
Between test_method_2_v2() and test_method_2_v3() I think that the best is test_method_2_v3() because the use of a mock object, when it is possible, is in general preferable because it helps to create unit tests (a mock object is used to simulate the behaviour of an object in a precise test case and not an whole object).
A unit test is a test which verifies a specific functionality. In this case test_method_2_v3() verifies that method_2() returns the value of the attribute d increase of 4.
In my opinion test_method_2_v2() does too many things so it's not a good unit test.
How test_method_2_v2() becomes a unit-test
I think is better to modify test_method_2_v2() and a possible code for it is the following:
def test_method_2_v2(self):
instance = MyClass()
instance.d = 3
# check that method_2 return 'd' increases of 4
assert instance.method_2() == 7
Previous code becomes similar to test_method_2_v3() without using of mock object. Now test_method_2_v2() only verifies the correctness of method_2() (as a test unit must do).

Related

Testable python class which depends on another class?

I have a class C which is used by X, for which I will write unit tests.
class C(metaclass=Singleton): # To be mocked
def __init__(self, a, b):
"""..."""
def f(self, x):
return ...
class X: # to be tested
def __init__(self, c: C=C(...)): # do I need parameter c for testability?
self.c = c
x = X() # self.c will be ready
To keep the same testability, do I need constructor parameter c: C=C(...)? How about just
class X: # to be tested
def __init__(self): # No parameter c
self.c = C(...)
def g(self):
x = self.c.f(...)
And use some approaches to "patch" C.f()? How are the difference of test code looks? I will use pytest.
Beware, instantiating an object as default value for a parameter will share the same object between deifferent calls (see this question).
As for your question, if you really to want the call to X the same, as in x = X() # self.c will be ready then you will need to patch the Config reference that your X.__init__ uses.
If you relax this constraint, meaning that you accept to do x = X(c=Config()) instead, it will be very easy to provide the "test double" of your choice (typically a "mock"), and ease testing. It is called dependency injection and facilitating testing is one of its main advantages.
EDIT: as you need to mock C.f, here is an example :
class C:
def __init__(self, a, b):
self.a = a
self.b = b
def f(self, z): # <-- renamed to `z` to avoid confusion with the `x` instance of class `X`
print(f"method F called with {z=}, {self.a=} and {self.b=}")
return z + self.a + self.b
class X:
def __init__(self, c: C = C(a=1, b=2)):
self.c = c
# /!\ to do some mocking, I had to `pip install pytest_mock` (version 3.6.1) as `pytest` provides no mocking support
def test_my_class_X_with_C_mock(mocker): # the `mocker` parameter is a fixture, it is automatically provided by the pytest runner
x = X() # create the X as always
assert x.c.f(z=3) == 6 # prints "method F called with z=3, self.a=1 and self.b=2"
def my_fake_function_f(z): # create a function to call instead of the original `C.f` (optional)
print(f"mock intercepted call, {z=}")
return 99
my_mock = mocker.Mock( # define a mock
**{"f.side_effect": my_fake_function_f}) # which has an `f`, which calls the fake function when called (side effect)
mocker.patch.object(target=x, attribute="c", new=my_mock) # patch : replace `x.c` with `my_mock` !
assert x.c.f(z=3) == 99 # prints "mock intercepted call, z=3"
def main():
# do the regular thing
x = X()
print(x.c.f(z=3)) # prints "method F called with z=3, self.a=1 and self.b=2" and the result "6"
# then do the tests
import pytest
pytest.main(["-rP", __file__]) # run the test, similar to the shell command `pytest <filename.py>`, and with `-rP` show the print
# it passes with no error !
if __name__ == "__main__":
main()
Because C already exists (it is in the same file) in your example, it was not possible to simply mock.patch, it was required to mock.patch.object.
Also, it is possible to write simpler mocks, or just let patch create a plain Mock, but for the example I preferred to be explicit.

How can I test methods in a large class with references to `self` using mock?

Let's suppose I have a big class with methods that make many references to self. For example:
from unittest import mock
import json
import Gamma
class Alpha:
def __init__(self):
self.a = json.loads('file_A')
self.b = json.loads('file_B')
self.c = None
self.d = None
self.e = Gamma()
def foo(self):
json.dumps(self.d)
def bar(self, dummy):
self.c = dummy.x
self.d = dummy.y
self.foo()
class Beta:
def __init__(self):
self.x = json.loads('file_X')
self.y = json.loads('file_Y')
Let's say I want to test the bar method in the above example utilizing mock. This is what I'm trying but I'm obviously missing something here:
#mock.patch('Beta', autospec=True)
#mock.patch('Alpha', autospec=True)
def test_bar(mock_alpha, mock_beta):
# Set mock values so the Beta class gets instantiated properly
mock_beta.x = 8
mock_beta.y = 9
# Call the bar method from mocked Alpha class
mock_alpha.bar(mock_beta)
# Test whether bar method updated the Alpha class as desired
assert mock_alpha.c == 8
assert mock_alpha.foo.called
These are the errors I get for the two asserts:
> raise AttributeError("Mock object has no attribute %r" % name)
E AttributeError: Mock object has no attribute 'c'
> assert mock_alpha.foo.called
E AssertionError: assert False
E + where False = <MagicMock name='Alpha.foo' spec='function' id='5324111568'>.called
E + where <MagicMock name='Alpha.foo' spec='function' id='5324111568'> = <MagicMock name='Alpha' spec='Alpha' id='5323992784'>.foo
How do I go about testing everything that foo does using mock?
PS: This is a stripped down example; it might make more sense to mock json object here in this particular example. However, in my real use case, the Alpha class is very complicated and would be a lot of work to mock all the endpoints. My question is confined to whether there's a way to mock instance of a class the way I'm attempting (unsuccessfully) to do in the example.
I will patch json.loads() method instead of patching Beta class. Besides, I patched .foo() method of Alpha class.
E.g.
main.py:
import json
class Alpha:
def __init__(self):
self.a = json.loads('file_A')
self.b = json.loads('file_B')
self.c = None
self.d = None
def foo(self):
json.dumps(self.d)
def bar(self, dummy):
self.c = dummy.x
self.d = dummy.y
self.foo()
class Beta:
def __init__(self):
self.x = json.loads('file_X')
self.y = json.loads('file_Y')
test_main.py:
from unittest import mock
import unittest
from main import Alpha, Beta
class TestAlpha(unittest.TestCase):
#mock.patch('main.json.loads')
#mock.patch('main.Alpha.foo')
def test_bar(self, mocked_foo, mocked_json_loads):
def json_loads_side_effect(s):
if s == 'file_X':
return 'a'
if s == 'file_Y':
return 'b'
mocked_json_loads.side_effect = json_loads_side_effect
alpha = Alpha()
beta = Beta()
alpha.bar(beta)
self.assertEqual(alpha.c, 'a')
self.assertEqual(alpha.d, 'b')
mocked_foo.assert_called_once()
if __name__ == '__main__':
unittest.main()
test result:
.
----------------------------------------------------------------------
Ran 1 test in 0.001s
OK
Name Stmts Miss Cover Missing
-----------------------------------------------------------------------
src/stackoverflow/68402729/main.py 17 1 94% 12
src/stackoverflow/68402729/test_main.py 21 0 100%
-----------------------------------------------------------------------
TOTAL 38 1 97%
After researching this for a while and considering #slideshowp2 's answer to this question. I have found a solution to be most close to what I was looking for. It seems like my initial idea of mocking entire class Alpha is unfeasible, instead I can mock any endpoints of Alpha but will need to have Alpha instantiated one way or another.
Here's my final solution that works.
#pytest.fixture
def set_up_alpha():
"""
Use this fixture to set-up an instance of Alpha object
Mock any endpoints as necessary
"""
with mock.patch.object(json, 'loads') as mock_json_loads:
mock_json_loads.side_effect = lambda x: "test_string"
a = Alpha()
yield a
#mock.patch.object(Alpha, 'foo', autospec=True)
def test_bar(mock_foo, set_up_alpha):
"""
GIVEN an Alpha object
AND a mocked Beta object
WHEN the bar method is invoked
THEN corresponding instances of Alpha object are updated
AND method foo is called
AND the method does not return anything
"""
# Use a mocked object for Beta instance
class MockBeta:
x = "test_x"
y = "test_y"
# Instantiate an Alpha object
a = set_up_alpha
output = a.bar(MockBeta)
# Test whether bar method updated the Alpha class as desired
assert a.c == "test_x"
assert a.d == "test_y"
assert mock_foo.called
assert output is None

How to patch the __new__ method of a class

My goal is to patch a class' __new__ method in order to control the exact object it creates in a test case. See the code below.
Each of the two tests test_1 and test_2 work as expected when run individually. When they are both run consecutively, however, test_2 fails with:
TypeError: object.__new__() takes exactly one argument (the type to instantiate)
Even if I store away the original __new__ method pointer and restore it at the end of test_1 (something that pytest takes care of anyway) it still produces the same result.
Any idea what is going on here? And what a working approach could be to achieve my goal?
import pytest
# The class:
class A:
def __init__(self, x):
self.x = x
def test_1(mocker):
"""First test patches the new method."""
# Create object:
a = A(x=3)
# Make sure new method always returns this object:
mocker.patch.object(A, "__new__", return_value=a)
# Switch off the initializer (so it doesn't overwrite values in the pre-made object):
mocker.patch.object(A, "__init__", return_value=None)
# Do the test:
res = A(x=1)
assert res.x == 3
def test_2():
"""Second test uses the class as intended."""
A(2) # <- Fails (when run right after test_1)
Update
Thanks to #OctaveL for pointing out that with this class definition, the tests work fine:
class A(object):
def __new__(cls, *_, **__):
return super(A, cls).__new__(cls)
def __init__(self, x):
self.x = x

Polymorphism and Overriding in Python

I have two classes: A and B. I would like to build a class C which is able to overrides some common methods of A and B.
The methods which I would like to override they should be able to call the method of the base class.
In practice I would like to collect some statistics about class A and B, but being transparent to the rest of my code. Now, A and B have some methods in common (obviously implemented in a different way). I would like to have a class C which shares the interface of A and B, and simoultaneously do some other operations (i.e. measure the run time of some shared methods).
I can make this example:
import time
class A:
def __init__(self):
pass
def common_method(self):
return "A"
class B:
def __init__(self):
pass
def common_method(self):
return "B"
class C:
def __init__(self, my_obj):
self.my_obj
self.time_avg = 0
self.alpha = 0.1
pass
def common_method(self):
start = time.time()
ret = self.my_obj.common_method()
stop = time.time()
self.time_avg = (1. - self.alpha) * self.time_avg + self.alpha * (stop - start)
return ret
I hope that from this example is clear that A and B inheriting from C is not working.
However, this method unfortunately require me to redefine all the methods of classes A and B... Which is tedious and dirty!
What is the proper way to implement this situation in python? And how it is called the design pattern (I am almost sure that there is but I cannot recall).
Thanks in advance.
You could solve this with composition instead of polymorphism, meaning that a C object will hold either a A object or a B one:
class C:
def __init__(self, obj):
self._obj = obj
def common_method(self):
return self._obj.common_method()
You can then use it:
>>> ca = C(A())
>>> cb = C(B())
>>> ca.common_method()
'A'
>>> cb.common_method()
'B'
Beware: if you pass an object that does not declare a common_method method, you will get an AttributeError

Weird behavior of nosetest

I try to use nose test
But when i run the testcase below
import unittest
class TestSuite(unittest.TestCase):
b = []
def setUp(self):
self.b.extend([10, 20])
def tearDown(self):
self.b = []
def test_case_1(self):
self.b.append(30)
assert len(self.b) == 3
assert self.b == [10, 20, 30]
def test_case_2(self):
self.b.append(40)
assert len(self.b) == 3
assert self.b == [10, 20, 40]
But all test cases is not pass
$> nosetest test_module.py
.F
======================================================================
FAIL: test_case_2 (test_module2.TestSuite)
----------------------------------------------------------------------
Traceback (most recent call last):
File "/Users/knt/test_module2.py", line 19, in test_case_2
assert len(self.b) == 3
AssertionError
----------------------------------------------------------------------
Ran 2 tests in 0.001s
FAILED (failures=1)
What 's happend ??? I expect after run test_case_1, tearDown will be called, so self.b is []. So with next test case test_case_2, setUp run and self.b is [10, 20].
But in fact, at setUp value of self.b is [10, 20, 30].
I dont know why. I think there must be some problems with statement self.b = [].
Anything related pointers, I guess?
I still didn't figure it out, but i find way to fix this bug. Just change self.b = [] to del self.b[:].
Anyone can help me find out the problem?
Thank you so much.
As far as I can tell the problem maybe with the way unitests works and how class fields work in python here is a simple test:
class A:
b = []
def reset(self):
self.b = []
a = A()
a.b.append(3) # we are actually accessing the class variable here
print A.b is a.b # True
print a.b # [3] same here
a.reset() # We just hid the class variable with our own which points to []
print A.b is a.b # False as expected.
print a.b # [] we think its being clear but rather we are using our own not the class variable
b = A()
print b.b # [3] b here is using the class that a previously modified but is no longer pointing to
print b.b is A.b # True
# Also note
c = A()
d = A()
print c.b is d.b # True, since both are using the same class variable.
I think unittest is creating the object multiple times, for each test function, creates object, fires setup which access the class variable, the test runs, calls teardown which simply hides it, creates another object, calls setup which access the same class variable that the previous object modified and didn't affect since its tear down simply created a new instance binded to self, hiding the class version.
we always declare member fields inside the __init__ using self.
def __init__(self):
self.b = []
this way every instance will have its own copy, though we can't do this here since we are inheriting from unittest.TestCase thats why we have setUp
import unittest
class TestSuite(unittest.TestCase):
def setUp(self):
self.b = [10, 20]
def tearDown(self):
self.b = []
def test_case_1(self):
self.b.append(30)
assert len(self.b) == 3
assert self.b == [10, 20, 30]
def test_case_2(self):
self.b.append(40)
assert len(self.b) == 3
assert self.b == [10, 20, 40]
The problem is your class attribute in line 2:
b = []
That is an attribute on the class TestSuite. Nosetests creates a new instance of the class TestSuite, and then calls setUp. In setUp, you modify the class attribute on the class TestSuite:
self.b.extend([10, 20])
Then, in tearDown, after your first test has been run, you create a new list and assign it to a new instance attribute which is also happens to be called b:
self.b = []
This does not modify the class attribute at all. Further attempts to access self.b from this instance will return the instance attribute, not the class attribute.
But this has no effect, because the very next thing that happens is that nosetests throws away the current instance of TestSuite, including your new, empty list in the instance attribute b that you have set in tearDown.
Then nosetests creates a brand new instance of the class TestSuite, to run your second test. But the class TestSuite still has a b class attribute containing [10, 20, 30], because it was modified while your first test was running.
Then nosetests setUp method runs, adds 10 and 20 to TestSuite's class attribute b. Then your second test runs, adds 40 to TestSuite's class attribute, and your second test fails, because it finds six items in that class attribute:
[10,20,30,10,20,40]
The reason that del self.b[:] works is because, just like append, del is modifying the class attribute, rather than creating a new instance attribute.
Make sure you understand the difference between classes and instances, and between class attributes and instance attributes, otherwise you will be plagued with similar problems for as long as you are working with Python.
Well, try to replace the:
self.b.extend([10,20])
with
self.b = [10,20]
and maybe throw out the class variable, you don't need it and it might be the reason this happens.

Categories