Testable python class which depends on another class? - python

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.

Related

Use mock or create real instance with workaround while testing

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).

correct way to return variables as arguments to method

A noob confused question,
I have two methods in a class as :
from example import sample2
class sample1:
def m1():
a='apple'
b='ball'
return sample2.m3(a,b)
def m2():
a='ant'
b='bat'
c='cat'
return sample2.m3(a,b,c)
in example.py:
class sample2:
def m3("here I want to access any `a`,`b`,`c` of respective m1 and m2"):
.....
Iam sorry if this question makes no sense, but when I try to access only this as:
class sample1:
def m1():
a='apple'
b='ball'
return sample2.m3(a,b)
in example.py:
class sample2:
def m3(a,b):
print(a)
a has value apple, so similar way why can't I access any value of a,b,c from that particular m1,m2 returned ?
This is how you use decorators. More information how decorator works can be found in for example here: https://www.datacamp.com/community/tutorials/decorators-python
I would suggest you to first try to better understand concept of class and objects. Example tutorial: https://www.w3schools.com/python/python_classes.asp
This post could also help you to understand how staticmethod decorator works - What is the difference between #staticmethod and #classmethod?
from example import sample2
class sample1:
#staticmethod
def m1():
a='apple'
b='ball'
return sample2.m3(a,b)
#staticmethod
def m2():
a='ant'
b='bat'
c='cat'
return sample2.m3(a,b,c)
example.py file with explanation:
class sample2:
#staticmethod
def m3(a, b, c=None): # it works exactly the same as m3 function that is outside the class
print(a)
# this can be used without creating an object of sample2 class, example:
# sample2.m3(a="apple, b="ball")
def m3_method(self, a, b): # this one requires object on which it can be called
print(a)
# you have access to sample2 class object via self parameter, example of code:
# sample2_object = sample2() # you create object of sample2 class here
# sample2_object.m3_method(a="apple", b="ball") # you call m3_method on sample2_object here
def m3(a, b, c=None): # default value of c is add so you can either call it with 2 or 3 arguments
# example calls:
# m3("a", "b")
# m3("a", "b", "c")
print(a)
You should be able to run this code and I think it gives you an idea how Python classes can be used.
Variables in Python always apply to a specific scope, such as a class, function or closure. Python uses lexical scoping, which means scopes are only connected by nesting in the source code. Most importantly, variables in different scopes are not connected at all.
When you "pass a variable" to a function, you are actually passing only the value around. The variable does not exist in other functions (unless they are nested) nor the surrounding scope.
def nested(a):
a = 3
print('a =', a) # a = 3
def parent():
a = 4
nested(a)
print('a =', a) # a = 4
parent()
print(a) # NameError: name 'a' is not defined
Functions should primarily exchange data by calling with input and returning results:
def nested(a): # receive input
a = 3
print('a =', a) # a = 3
return a # return output
def parent():
a = 4
a = nested(a) # replace a with result of nested(a)
print('a =', a) # a = 3
parent()
Note that only values are passed in and returned. The above could would behave exactly the same if you renamed a in either function.
When working with class instances, the instance itself works as a namespace (similar to a scope). Methods of that instance can exchange data by modifying attributes of the instance. The instance is always passed as the first argument to methods:
class Example():
"""An example for setting attributes on an instance"""
def __init__(self):
self.a = 0
def nested(self):
self.a = 3
print('self.a =', self.a) # self.a = 3
def parent(self):
self.a = 4
print('self.a =', self.a) # self.a = 4
self._nested()
print('self.a =', self.a) # self.a = 3
instance = Example()
print(instance.a) # 0
instance.parent() # self.a = 4
# self.a = 3
To exchange data between objects, methods should also primarily exchange data by calling with input and returning results :
class Example():
"""An example for setting attributes on an instance"""
def __init__(self, a):
self.a = a
def multiply(self, value):
return self.a * value
instance = Example(6)
print(instance.multiply(10)) # 60

Python - method of a class with an optional argument and default value a class member

I have something like this (I know this code doesn't work, but it's the closer to what I want to achieve):
class A:
def __init__(self):
self.a = 'a'
def method(self, a=self.a):
print a
myClass = A()
myClass.method('b') # print b
myClass.method() # print a
What I've done so far, but I do not like it, is:
class A:
def __init__(self):
self.a = 'a'
def method(self, a=None):
if a is None:
a = self.a
print a
myClass = A()
myClass.method('b') # print b
myClass.method() # print a
Default arguments are evaluated at definition time. By the time the class and method are defined self.a is not.
Your working code example is actually the only clean way of achieving this behavior.
The default is evaluated at method definition time, i.e. when the interpreter executes the class body, which usually happens only once. Assigning a dynamic value as default can only happen within the method body, and the approach you use is perfectly fine.

Mocking a class method and changing some object attributes in Python

I am new to mock in Python. I want to know how to replace (mock) a class method while testing with another one, knowing that the original just changes some attributes of self without returning any value. For example:
def some_method(self):
self.x = 4
self.y = 6
So here I can't just change the return_value of the mock. I tried to define a new function (that should replace the original) and give it as side_effect to the mock. But how can I make the mocking function change attributes of the object in the class.
Here is my code:
#patch('path.myClass.some_method')
def test_this(self,someMethod):
def replacer(self):
self.x = 5
self.y = 16
some_method.side_effect = replacer
So how does Python now understands the self argument of replacer? Is that the self of the test class, or the self as the object of the class under test?
Apologies in advance if I don't understand what you are trying to do, but I think this might work:
import unittest
from unittest.mock import patch
class MyClass:
def __init__(self):
self.x = 0
self.y = 0
def some_method(self):
self.x = 4
self.y = 6
class OtherClass:
def other_method(self):
self.x = 5
self.y = 16
class MyTestClass(unittest.TestCase):
#patch('__main__.MyClass.some_method', new=OtherClass.other_method)
def test_patched(self):
a = MyClass()
a.some_method()
self.assertEqual(a.x, 5)
self.assertEqual(a.y, 16)
def test_not_patched(self):
a = MyClass()
a.some_method()
self.assertEqual(a.x, 4)
self.assertEqual(a.y, 6)
if __name__ == "__main__":
unittest.main()
This replaces some_method() with other_method() when patched, which sets different values for attributes x, y, and when the test is run, it gives the results:
..
----------------------------------------------------------------------
Ran 2 tests in 0.020s
OK
EDIT: to answer question about how to do inside the test function without mocking a class...
def test_inside_patch(self):
def othermethod(self):
self.x = 5
self.y = 16
patcher = patch('__main__.MyClass.some_method', new=othermethod)
patcher.start()
a = MyClass()
a.some_method()
self.assertEqual(a.x, 5)
self.assertEqual(a.y, 16)
patcher.stop()
Make sure you call start() and stop() on the patcher otherwise you can get into a situation where the patch is active and you don't want it to be. Note that to define the mock function inside the test code function, I didn't use patch as a decorator, because the mock function has to be defined before using the 'new' keyword in patch. If you want to use patch as a decorator you have to define the mock function someplace before the patch, defining it inside of MyTestClass also works, but it seems you really want to have the mock function defined inside your test function code.
EDIT: added summary of 4 ways I see to do this...
# first way uses a class outside MyTest class
class OtherClass:
def other_method(self):
...
class MyTest(unittest.TestCase):
#patch('path_to_MyClass.some_method', new=OtherClass.other_method)
def test_1(self)
...
# 2nd way uses class defined inside test class
class MyOtherClass:
def other_method(self):
...
#patch('path_to_MyClass.some_method', new=MyOtherClass.other_method)
def test_2(self):
...
# 3rd way uses function defined inside test class but before patch decorator
def another_method(self):
...
#patch('path_to_MyClass.some_method', new=another_method)
def test_3(self):
...
# 4th way uses function defined inside test function but without a decorator
def test_4(self):
def yet_another_method(self):
...
patcher = patch('path_to_MyClass.some_method', new=yet_another_method)
patcher.start()
...
patcher.stop()
None of these uses a side_effect, but they all solve the problem of mocking a class method and changing some attributes. Which one you choose depends on the application.

How can i let class methods interact with a context manager in python?

My code contains some objects which are used via Pythons "with" statement to ensure that they get savely closed.
Now i want to create a class where the methods can interact with these objects.
For example my code actually looks like this:
with ... as a, with ... as b:
# do something with a and b here
call_method(a, b) # pass a and b here
I'd like to put it into a class, so it Looks "like" this:
class Something(object):
def __init__(self):
with ... as a:
self.a = a
with ... as b:
self.b = b
def do_something(self):
# do something with self.a and self.b
self.call_method(self.a, self.b)
def call_method(self, a, b)
# do more with a, b
The objects need to stay "opened" all the time.
I don't know how to achieve this, so how can i do this?
You don't have a 'context' in your class to manage, don't use with in __init__. You'll have to close the files in some other manner.
You can always use try:, finally: if you want the file objects to be closed if there is an exception within the method:
def call_method(self, a, b)
try:
# do more with a, b
finally:
self.a.close()
self.b.close()
but it depends heavily on what you wanted to do with the files if you really wanted them to be closed at that point.
If your instances themselves should be used in a specific context (e.g. there is a block of code than starts and ends during which your instance should have the file open), then you can make the class a context manager by implementing the context manager special methods.
You alone as designer of the class API will know how long the files need to stay open for. It depends heavily on how the instance is used when it is time to close the file.
You could make your class itself a context manager:
class Something(object):
def __init__(self):
self.a = a
self.b = b
def __enter__(self):
self.a_e = a.__enter__(self)
self.b_e = b.__enter__(self)
def __exit__(self, *x):
xb = False
try:
xb = self.b_e(*x)
finally:
xa = self.a_e(*x)
return xa or xb # make sure both are called.
def do_something(self):
# do something with self.a and self.b
# - or, if present, with a_e, b_e
self.call_method(self.a, self.b)
def call_method(self, a, b)
# do more with a, b
This is just the raw idea. In order to make it work properly, you must do even more with try: except: finally:.
You can use it then with
with Something(x, y) as sth:
sth.do_something()
and it gets properly __enter__()ed and __exit__()ed.

Categories