Python - make unit tests independent for classes with class variables

I have a class with a dictionary that is used to cache the response from the server for specific input. Since this is used for caching purposes, it is saved as a class variable.

class MyClass: cache_dict = {} def get_info_server(self, arg): if arg not in self.cache_dict: self.cache_dict[arg] = Client.get_from_server(arg) return cache_dict[arg] def do_something(self, arg): # Do something based on get_info_server(arg) 

And when writing unit tests, since the dictionary is a class variable, values ​​are cached in test cases.

Control cases

 # Assume that Client is mocked. def test_caching(): m = MyClass() m.get_info_server('foo') m.get_info_server('foo') mock_client.get_from_server.assert_called_with_once('foo') def test_do_something(): m = MyClass() mock_client.get_from_server.return_value = 'bar' m.do_something('foo') # This internally calls get_info_server('foo') 

If test_caching is executed first, some object layout will be the cached value. If test_do_something is executed first, then the statement that the test case is called exactly once will not be executed.

How to make tests independent of each other, except for direct manipulation of the dictionary (since this is similar to requiring a deep knowledge of the internal workings of the code). What if the inner work was to change later. All I need to check is the API itself, and not rely on internal work)?

+5
source share
4 answers

You cannot avoid flushing the cache here. If you are fond of this class, then your unittest should have a deep knowledge of the internal workings of the class, so just reset the cache. You rarely can change the way your class works without changing your unittests.

If you feel that this will create a load on the service anyway, then make the cache processing explicit by adding a class method:

 class MyClass: cache_dict = {} @classmethod def _clear_cache(cls): # for testing only, hook to clear the class-level cache. cls.cache_dict.clear() 

Notice that I nevertheless gave him a name with a leading underscore; this is not a method that a third-party should call, it is available only for tests. But now you have centralized cache flushing, which gives you control over how it is implemented.

If you use the unittest framework to run tests, clear the cache before each test in the TestCase.setUp() method. If you use a different testing structure, this structure will have a similar hook. Clearing the cache before each test ensures that you always have a clean state.

Please note that your cache is not thread safe, if you use tests in parallel with stream processing, you will have problems. Since this also applies to the cache implementation itself, this is probably not what you are worried about right now.

+3
source

You need to use Python built into UnitTest TestCase and implement customization and deletion methods.

If you define setUp() and tearDown() in your tests, they will be executed every time one of the methods of one method is called (before and after, respectively)

Example:

 # set up any global, consistent state here # subclass unit test test case here. def setUp(self): # prepare your state if needed for each test, if this is not considered "fiddling", use this method to set your cache to a fresh state each time your_cache_dict_variable = {} ### Your test methods here def tearDown(self): # this will handle resetting the state, as needed 

Check out the docs for more info: https://docs.python.org/2/library/unittest.html

0
source

You did not explicitly ask the question, but I assume that your test methods are in a subclass of unittest.TestCase called MyClassTests .

Explicitly set MyClass.cache_dict in the test method. If it's just a dictionary, without any getters / settings for it, you don't need a layout.

If you want to ensure that each test method is independent, set MyClass.cache_dict = {} to MyClassTests.setup() .

0
source

One thing I can suggest is to use the setUp() and tearDown() methods in your test class.

 from unittest import TestCase class MyTest(TestCase): def setUp(self): self.m = MyClass() //anything else you need to load before testing def tearDown(self): self.m = None def test_caching(self): self.m.get_info_server('foo') self.m.get_info_server('foo') mock_client.get_from_server.assert_called_with_once('foo') 
-1
source

Source: https://habr.com/ru/post/1274603/


All Articles