Indeed. If you can refactor the BigBoyClass.__init__
method, you can do better than mocks !
Based on your example, here is my initial situation :
class BigBoyClass:
def __init__(self, big_number: int):
sum(range(big_number)) # something slow
self.init_attr1 = big_number # whatever
self.init_attr2 = big_number + 1 # whatever
def method1(self):
self.new_attr1 = 4 # whatever
self.new_attr2 = 5 # whatever
def test_method1():
# given
instance = BigBoyClass(10**9) # takes a few seconds on my computer
# when
instance.method1()
# then
assert (instance.new_attr1 == 4) and (instance.new_attr2 == 5)
What I want is a dumb __init__
: it is given parameters, it just sets them to the corresponding self
fields. The complex part is moved to a classmethod
that will handle the complex part.
class BigBoyClass:
def __init__(self, attr1: int, attr2: int):
# removed the slow part here
self.init_attr1 = attr1
self.init_attr2 = attr2
def method1(self):
self.new_attr1 = 4 # whatever
self.new_attr2 = 5 # whatever
@classmethod
def init_with_config_object(cls, big_number: int):
sum(range(big_number)) # something slow
return cls(big_number, big_number+1)
def test_method1():
# given
instance = BigBoyClass(10**9, 10**9+1) # lightning-fast !
# when
instance.method1()
# then
assert (instance.new_attr1 == 4) and (instance.new_attr2 == 5)
I did not change my test at all, but because I changed the __init__
signature, now I have to adapt all the other existing instantiations of the BigBoy
class, to call the init_with_config_object
instead of just the class itself.
What you gain from doing that :
- no need to mock, so it's easier to write tests
- no mocks, so that you don't have to maintain them when your actual class change
- you can check separately that the configuration initializes correctly, and that the method does what expected
- you can add other classmethods if you need to be able to instantiate the same class but in different ways
- if providing the values for the
init_attr
of your object is complex, you can create test factories : functions that will handle that for you, using a direct call to __init__
anyway
I don't know if this technique has a name, but it looks a bit like a "java beans" because it provides the way to be constructed in any way, without restrictions on signatures/protocols.