There's a situation where I want to check how many times an internal class method has been called. I have a sensitive cloud task that must be done in a count that depends upon some circunstances. I would like to strengthen my application with an unittest to assert the number of times a specific function has been called.
To do so in a much simpler scenario, I would like to make a test in the following script:
class HMT:
def __init__(self):
self.buildedList = []
def handle(self, string_to_explode: str):
for exploded_part in string_to_explode.split(","):
self.doCoolThings(exploded_part)
def doCoolThings(self, fetched_raw_string: str):
self.buildedList.append("Cool thing done: " + fetched_raw_string)
Depending on the string that I deliver to the handle
function, the doCoolThings
will be called N times (in this simple case, depends solely in the number of comas inside the string).
I can make a test works by just counting the number of resulting elements inside builderList
:
import unittest
from HMT import HMT
class test_HMT(unittest.TestCase):
def setUp(self):
self.hmt = HMT()
def test_hmt_3(self):
string_to_test = "alpha,beta,gamma"
self.hmt.handle(string_to_test)
self.assertEqual(3, len(self.hmt.buildedList))
def test_hmt_2(self):
string_to_test = "delta,epsilon"
self.hmt.handle(string_to_test)
self.assertEqual(2, len(self.hmt.buildedList))
But in the real scenario, there's will not have an available public class list that always will match its number of elements to the times the function doCoolThings
was called.
So, how do I check how many times the doCoolThings
was called without needing to check the list elements count?
I know that I can just put a counter in the class that is increased each time doCoolThings
is called and expose it externally to be checked afterwards. But I don't would like to mess up the code putting lines that is not directly related to my business rule.
After @jarmod comment, I came into this code's version:
def mydecorator(func):
def wrapped(*args, **kwargs):
wrapped.calls += 1
return func(*args, **kwargs)
wrapped.calls = 0
return wrapped
class HMT:
def __init__(self):
self.buildedList = []
def handle(self, string_to_explode: str):
for exploded_part in string_to_explode.split(","):
self.doCoolThings(exploded_part)
@mydecorator
def doCoolThings(self, fetched_raw_string: str, *args, **kwargs):
self.buildedList.append("Cool thing done: " + fetched_raw_string)
And the test:
import unittest
from HMT import HMT
class test_HMT(unittest.TestCase):
def test_hmt_3_dec(self):
hmt = HMT()
string_to_test = "epsilon,ota,eta"
hmt.handle(string_to_test)
self.assertEqual(3, hmt.doCoolThings.calls)
def test_hmt_3(self):
hmt = HMT()
string_to_test = "alpha,beta,gamma"
hmt.handle(string_to_test)
self.assertEqual(3, len(hmt.buildedList))
But still are not working properly. When I run tests, I receive:
.F
======================================================================
FAIL: test_hmt_3_dec (myTest.test_HMT)
----------------------------------------------------------------------
Traceback (most recent call last):
File "D:\Users\danil\tmp\myDec\myTest.py", line 10, in test_hmt_3_dec
self.assertEqual(3, hmt.doCoolThings.calls)
AssertionError: 3 != 6
----------------------------------------------------------------------
Ran 2 tests in 0.001s
FAILED (failures=1)
More tests shows that the tests runs twice but the counter do not reset either in new instantiation.
Anyway, the initial idea was to dynamically put an observer in a internal class method and externaly fetches each time when those method is triggered (still, not something that seems to be solved using decorator).
Thanks alot for answers (and as a plus to leave powered up, if someone knows how to reset the counter in the decorator I will appreciate as well).