Python - 使用 class 变量使单元测试独立于 classes
Python - Make unit tests independent for classes with class variables
我有一个 class 和一个字典,用于缓存服务器对特定输入的响应。由于这是用于缓存目的,因此将其保存为 class 变量。
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)
并且在编写单元测试时,由于字典是一个 class 变量,因此值会跨测试用例缓存。
测试用例
# 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')
如果test_caching
先执行,缓存的值将是一些模拟对象。如果test_do_something
先执行,那么测试用例只调用一次的断言就会失败。
如何使测试彼此独立,除了直接操作字典(因为这就像要求对代码的内部工作有深入的了解。如果内部工作将在以后更改。我需要验证的是 API 本身,而不是依赖于内部工作)?
您需要利用 Python 的内置 UnitTest 测试用例并实施设置和拆卸方法。
如果您在测试中定义了 setUp()
和 tearDown()
,则每次调用其中一个测试方法时(分别是之前和之后),它们都会执行
示例:
# 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
我可以建议的一件事是在测试中使用 setUp()
和 tearDown()
方法 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')
你没有明确地把它放在问题中,但我假设你的测试方法在 unittest.TestCase
的一个子类中,名为 MyClassTests
。
在被测方法中显式设置MyClass.cache_dict
。如果它只是一本字典,没有任何 getters / setters,你不需要 Mock。
如果要保证每个测试方法都是独立的,在MyClassTests.setup()
中设置MyClass.cache_dict = {}
。
您真的无法避免在此处重置您的缓存。如果你正在单元测试这个class,那么你的单元测试将需要对class的内部工作有深入的了解,所以只是重置缓存。无论如何,您很少可以在不调整单元测试的情况下更改 class 的工作方式。
如果您认为这仍然会造成维护负担,请通过添加 class 方法使缓存处理显式化:
class MyClass:
cache_dict = {}
@classmethod
def _clear_cache(cls):
# for testing only, hook to clear the class-level cache.
cls.cache_dict.clear()
请注意,我仍然给它起了一个带前导下划线的名称;这不是第 3 方应该调用的方法,它仅用于测试。但现在您可以集中清除缓存,让您可以控制它的实现方式。
如果您使用 unittest
框架来 运行 您的测试,请在 TestCase.setUp()
method 中的每个测试之前清除缓存。如果您使用不同的测试框架,该框架将具有类似的钩子。在每次测试 之前 清除缓存可确保您始终拥有干净的状态。
请注意您的缓存不是线程安全的,如果您运行测试与线程并行进行,您会遇到问题。由于这也适用于缓存实现本身,这可能不是您现在担心的事情。
我有一个 class 和一个字典,用于缓存服务器对特定输入的响应。由于这是用于缓存目的,因此将其保存为 class 变量。
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)
并且在编写单元测试时,由于字典是一个 class 变量,因此值会跨测试用例缓存。
测试用例
# 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')
如果test_caching
先执行,缓存的值将是一些模拟对象。如果test_do_something
先执行,那么测试用例只调用一次的断言就会失败。
如何使测试彼此独立,除了直接操作字典(因为这就像要求对代码的内部工作有深入的了解。如果内部工作将在以后更改。我需要验证的是 API 本身,而不是依赖于内部工作)?
您需要利用 Python 的内置 UnitTest 测试用例并实施设置和拆卸方法。
如果您在测试中定义了 setUp()
和 tearDown()
,则每次调用其中一个测试方法时(分别是之前和之后),它们都会执行
示例:
# 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
我可以建议的一件事是在测试中使用 setUp()
和 tearDown()
方法 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')
你没有明确地把它放在问题中,但我假设你的测试方法在 unittest.TestCase
的一个子类中,名为 MyClassTests
。
在被测方法中显式设置MyClass.cache_dict
。如果它只是一本字典,没有任何 getters / setters,你不需要 Mock。
如果要保证每个测试方法都是独立的,在MyClassTests.setup()
中设置MyClass.cache_dict = {}
。
您真的无法避免在此处重置您的缓存。如果你正在单元测试这个class,那么你的单元测试将需要对class的内部工作有深入的了解,所以只是重置缓存。无论如何,您很少可以在不调整单元测试的情况下更改 class 的工作方式。
如果您认为这仍然会造成维护负担,请通过添加 class 方法使缓存处理显式化:
class MyClass:
cache_dict = {}
@classmethod
def _clear_cache(cls):
# for testing only, hook to clear the class-level cache.
cls.cache_dict.clear()
请注意,我仍然给它起了一个带前导下划线的名称;这不是第 3 方应该调用的方法,它仅用于测试。但现在您可以集中清除缓存,让您可以控制它的实现方式。
如果您使用 unittest
框架来 运行 您的测试,请在 TestCase.setUp()
method 中的每个测试之前清除缓存。如果您使用不同的测试框架,该框架将具有类似的钩子。在每次测试 之前 清除缓存可确保您始终拥有干净的状态。
请注意您的缓存不是线程安全的,如果您运行测试与线程并行进行,您会遇到问题。由于这也适用于缓存实现本身,这可能不是您现在担心的事情。