Home > Net >  Test of the sequence of calling of some methods of a Python class
Test of the sequence of calling of some methods of a Python class

Time:11-18

I need to check the sequence of calling of some methods of a class.

I suppose that my production class A is stored in the following file class_a.py:

class A:
    __lock = None

    def __init__(self, lock):
        self.__lock = lock

    def method_1(self):
        self.__lock.acquire()
        self.__atomic_method_1()
        self.__lock.release()

    def __atomic_method_1(self):
        pass

I need to write a unit test that checks the sequence of calling of the methods of the class A when it is invoked the method_1().

The check must verify that method_1() calls:

  1. as first method: self.__lock.acquire()
  2. after that it calls self.__atomic_method_1()
  3. and that the last method called is self.__lock.release()

A useful hint but not enough

before mocking

Your SUT has two references :

  • one named __locked which points to its Lock instance, which itself has (for our concerns) 2 references : acquire and release
  • the other named __atomic_method_1 which points to its A.__atomic_method_1 method

What we want is to observe the calls made to __lock.acquire, __lock.release, __atomic_method_1 and their relative order.

The simpler way to do that I could think of is to replace each of these three by "spy functions", which records they were being called (simply by appending in a list) then forward the call to the actual function.

But then we need these functions to be called, so we will have to mock things. Because they are not "importable", we can't after mocking

The calls to __lock.acquire and __lock.release are sort of diverted (thanks to mocking) through our spy, while any other still gets through ordinarily.

(We could have done without creating a Mock for __aotmic_method_1, and mock.patch.object with the spy function)

CodePudding user response:

The solution of @Lenormju is obviously rich of concepts about Mocking and, in my opinion, is preferable than this. In fact I have accepted it.
However I propose an other answer that can be used to solve some testing problem and my specific test case.

The test method that I have written is based on the following ideas:

  1. create a Mock instance by the method mock.create_autospec():
mock_a = mock.create_autospec(A)
  1. invoke the instance method method_1() by the class A and passing it the Mock instance:
A.method_1(mock_a)

The complete test file is the following:

import unittest
from unittest import mock
from class_a import A

class MyTestCase(unittest.TestCase):
    def test_call_order(self):
        mock_a = mock.create_autospec(A)
        expected = [mock.call._A__lock.acquire(),
                    mock.call._A__atomic_method_1(),
                    mock.call._A__lock.release()]
        A.method_1(mock_a)
        self.assertEqual(expected, mock_a.mock_calls)

if __name__ == '__main__':
    unittest.main()

The test doesn't use an instance of the class under test

One of the problem of this test is that it doesn't create an instance of class A and so it invokes method_1() in a different way respect of the production code.

On the other hand this is a specific test that I have to use to check the static structure of the method_1() code so, in my opinion and only in this specific case, the trick could be acceptable.

  • Related