Python单元测试内存消耗
Python unittest memory consumption
在使用 python 的 unittest 框架时,我注意到在我的案例中导致一些问题的行为。为了演示它,请看下面的代码:
import unittest
import time
class TC_Memory(unittest.TestCase):
def setUp(self):
unittest.TestCase.setUp(self)
self.__result = False
def test_unittest_mem1(self):
list1 = [9876543210] * 2048*2048*9
time.sleep(1)
self.assertTrue(self.__result, "Failed")
def test_unittest_mem2(self):
list1 = [9876543210] * 2048*2048*9
time.sleep(1)
self.assertTrue(self.__result, "Failed")
def test_unittest_mem3(self):
list1 = [9876543210] * 2048*2048*9
time.sleep(1)
self.assertTrue(self.__result, "Failed")
def test_unittest_mem4(self):
list1 = [9876543210] * 2048*2048*9
time.sleep(1)
self.assertTrue(self.__result, "Failed")
def test_unittest_mem5(self):
list1 = [9876543210] * 2048*2048*9
time.sleep(1)
self.assertTrue(self.__result, "Failed")
def test_unittest_mem6(self):
list1 = [9876543210] * 2048*2048*9
time.sleep(1)
self.assertTrue(self.__result, "Failed")
def test_unittest_mem7(self):
list1 = [9876543210] * 2048*2048*9
time.sleep(1)
self.assertTrue(self.__result, "Failed")
def test_unittest_mem8(self):
list1 = [9876543210] * 2048*2048*9
time.sleep(1)
self.assertTrue(self.__result, "Failed")
def test_unittest_mem9(self):
list1 = [9876543210] * 2048*2048*9
time.sleep(1)
self.assertTrue(self.__result, "Failed")
if __name__ == "__main__":
unittest.main()
这些测试方法做的都是一样的事情。生成一个巨大的列表,等待一秒钟,然后通过或失败取决于 __result 变量。
现在测试通过的时候,没什么大不了的,但是测试失败的时候,list的内存好像没有释放。这会导致巨大的内存消耗,因为每个测试似乎都保留了它的内存。最终,每次测试运行并打印结果后,释放内存,一切恢复正常。
虽然上面的代码有些夸张,但真实案例包含 200 多个测试,每个测试使用大约 20-30 MB 的内存。如果这些没有被释放,我将 运行 陷入内存短缺。
似乎 unittest 保留测试方法变量以在测试失败时报告值,或者至少在这种情况下提供对变量的报告。我不知道,也许我在这里忽略了一些东西。
但是,我需要摆脱这些多余的内存。到目前为止,我的选择是:
- 对我不再需要的任何变量调用 del。然而,这在某种程度上破坏了垃圾收集器和 "not having to worry about memory stuff"
的优点
- 获取更多 RAM。
我很想听听设置某种标志的可能性。我更希望听到有人指出我犯的一个明显错误,或者有人告诉我,使用 python 或 unittest 的 x.y 版本不会发生这种情况。
使用的版本:python3.3.5 final 64bit
所以,如果还有其他问题,我很乐意回答。如果您有任何想法,或者在黑暗中拍摄,请告诉我,我会尝试一下。
提前致谢。
问题可能是测试运行程序(或结果 class)保留了抛出的异常,该异常包含对引用大对象的帧的引用。您可能想要做的是编写一个不显示此行为的自定义运行程序。类似的东西(对 python2 感到抱歉,但这就是我目前所拥有的):
class CustomTestResult(TextTestResult):
def addError(self, test, err):
tp, vl, tb = err
super(CustomTestResult, self).addError(test, (tp, vl, placeholder))
def addFailure(self, test, err):
tp, vl, tb = err
super(CustomTestResult, self).addFailure(test, (tp, vl, placeholder))
class CustomTestRunner(TextTestRunner):
resultclass = CustamTestResult
if __name__ == "__main__":
import sys
try:
raise Exception
except Exception as err:
placeholder = sys.exc_info()[2]
unittest.main(testRunner = CustomTestRunner)
虽然这里可能还有一些改进的余地。例如,您 可以 递归地检查回溯并确定它是否足够大以促使它被替换(或者甚至可能从框架中删除有问题的对象)。对于被测代码 raise
s 异常的情况尤其如此(在这种情况下,您可能会对回溯感兴趣,而不仅仅是占位符回溯)。
另一种解决方案可能是不在同一堆栈帧中进行分配,因为从失败测试创建的堆栈帧仅包含其上的帧。喜欢:
def mem1(self):
list1 = [9876543210] * 2048*2048*9
time.sleep(1)
def test_unittest_mem1(self):
self.mem1()
self.assertTrue(self.__result, "Failed")
在使用 python 的 unittest 框架时,我注意到在我的案例中导致一些问题的行为。为了演示它,请看下面的代码:
import unittest
import time
class TC_Memory(unittest.TestCase):
def setUp(self):
unittest.TestCase.setUp(self)
self.__result = False
def test_unittest_mem1(self):
list1 = [9876543210] * 2048*2048*9
time.sleep(1)
self.assertTrue(self.__result, "Failed")
def test_unittest_mem2(self):
list1 = [9876543210] * 2048*2048*9
time.sleep(1)
self.assertTrue(self.__result, "Failed")
def test_unittest_mem3(self):
list1 = [9876543210] * 2048*2048*9
time.sleep(1)
self.assertTrue(self.__result, "Failed")
def test_unittest_mem4(self):
list1 = [9876543210] * 2048*2048*9
time.sleep(1)
self.assertTrue(self.__result, "Failed")
def test_unittest_mem5(self):
list1 = [9876543210] * 2048*2048*9
time.sleep(1)
self.assertTrue(self.__result, "Failed")
def test_unittest_mem6(self):
list1 = [9876543210] * 2048*2048*9
time.sleep(1)
self.assertTrue(self.__result, "Failed")
def test_unittest_mem7(self):
list1 = [9876543210] * 2048*2048*9
time.sleep(1)
self.assertTrue(self.__result, "Failed")
def test_unittest_mem8(self):
list1 = [9876543210] * 2048*2048*9
time.sleep(1)
self.assertTrue(self.__result, "Failed")
def test_unittest_mem9(self):
list1 = [9876543210] * 2048*2048*9
time.sleep(1)
self.assertTrue(self.__result, "Failed")
if __name__ == "__main__":
unittest.main()
这些测试方法做的都是一样的事情。生成一个巨大的列表,等待一秒钟,然后通过或失败取决于 __result 变量。
现在测试通过的时候,没什么大不了的,但是测试失败的时候,list的内存好像没有释放。这会导致巨大的内存消耗,因为每个测试似乎都保留了它的内存。最终,每次测试运行并打印结果后,释放内存,一切恢复正常。
虽然上面的代码有些夸张,但真实案例包含 200 多个测试,每个测试使用大约 20-30 MB 的内存。如果这些没有被释放,我将 运行 陷入内存短缺。
似乎 unittest 保留测试方法变量以在测试失败时报告值,或者至少在这种情况下提供对变量的报告。我不知道,也许我在这里忽略了一些东西。
但是,我需要摆脱这些多余的内存。到目前为止,我的选择是:
- 对我不再需要的任何变量调用 del。然而,这在某种程度上破坏了垃圾收集器和 "not having to worry about memory stuff" 的优点
- 获取更多 RAM。
我很想听听设置某种标志的可能性。我更希望听到有人指出我犯的一个明显错误,或者有人告诉我,使用 python 或 unittest 的 x.y 版本不会发生这种情况。
使用的版本:python3.3.5 final 64bit
所以,如果还有其他问题,我很乐意回答。如果您有任何想法,或者在黑暗中拍摄,请告诉我,我会尝试一下。
提前致谢。
问题可能是测试运行程序(或结果 class)保留了抛出的异常,该异常包含对引用大对象的帧的引用。您可能想要做的是编写一个不显示此行为的自定义运行程序。类似的东西(对 python2 感到抱歉,但这就是我目前所拥有的):
class CustomTestResult(TextTestResult):
def addError(self, test, err):
tp, vl, tb = err
super(CustomTestResult, self).addError(test, (tp, vl, placeholder))
def addFailure(self, test, err):
tp, vl, tb = err
super(CustomTestResult, self).addFailure(test, (tp, vl, placeholder))
class CustomTestRunner(TextTestRunner):
resultclass = CustamTestResult
if __name__ == "__main__":
import sys
try:
raise Exception
except Exception as err:
placeholder = sys.exc_info()[2]
unittest.main(testRunner = CustomTestRunner)
虽然这里可能还有一些改进的余地。例如,您 可以 递归地检查回溯并确定它是否足够大以促使它被替换(或者甚至可能从框架中删除有问题的对象)。对于被测代码 raise
s 异常的情况尤其如此(在这种情况下,您可能会对回溯感兴趣,而不仅仅是占位符回溯)。
另一种解决方案可能是不在同一堆栈帧中进行分配,因为从失败测试创建的堆栈帧仅包含其上的帧。喜欢:
def mem1(self):
list1 = [9876543210] * 2048*2048*9
time.sleep(1)
def test_unittest_mem1(self):
self.mem1()
self.assertTrue(self.__result, "Failed")