在Python单元测试用例混合中使用Singleton元类时的怪异行为



背景

我们有一个特殊的设置,我想为它编写一个TestCase基类/mixin,它可以用来为每个TestCase提供开箱即用的功能。为TestCase提供功能的类需要是单个实例,因此实现为singleton。


观察

当使其成为单例时,

  • 测试发现不再正常工作,只检测TestCase的单个测试
  • 并且所发现的单个测试被执行N次,其中N是TestCase中的测试数量

这是一个非常奇怪的行为,我目前无法解释。


复制

我确实测试了一个最小工作示例(MWE(来重现这个问题,它可以用下面的最小代码重现。请注意,我尽量减少。因此,该代码除了复制之外没有任何其他目的,也不遵循任何编码标准。

test_reproduction.py

import unittest
from unittest import TestCase

class Singleton(type):
_instances = {}
def __call__(cls, *args, **kwargs):
if cls not in cls._instances:
cls._instances[cls] = super(Singleton, cls).__call__(*args, **kwargs)
return cls._instances[cls]

class SpecialMethods(metaclass=Singleton):
pass

class SpecialTestCase(TestCase, SpecialMethods):
def __init__(self, methodName="runTest") -> None:
TestCase.__init__(self, methodName)
SpecialMethods.__init__(self)

class TestSomething(SpecialTestCase):
def test_something1(self):
print('print test_something1')
self.assertTrue(True)
def test_something2(self):
print('print test_something2')
self.assertTrue(True)
def test_something3(self):
print('print test_something3')
self.assertTrue(True)

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

使用:python3 -m unittest -v test_reproduction执行时这将产生以下输出:

test_something1 (test_reproduction.TestSomething) ... print test_something1
ok
test_something1 (test_reproduction.TestSomething) ... print test_something1
ok
test_something1 (test_reproduction.TestSomething) ... print test_something1
ok
----------------------------------------------------------------------
Ran 3 tests in 0.000s
OK

请注意,只执行testrongomething1,但执行了三次。则不执行test_ something2和test_。


问题

有人对发生的事情有解释吗?以及任何我可以通过Mixin使其工作的机会(我已经有了其他解决方案,我只对通过Mixins使其工作感兴趣(。

你的设计有一些地方感觉不对劲,但你得到了你想要的:你创建了一个ICBM,用多个符号来完成一个"singleton";通过使用元类来创建单例。Than,这就是你得到的。

我们通常不需要关心有多少实例,也不需要关心unittest对我们为运行所有测试而创建的测试类做了什么:但很明显,您修改了简单而常见的类行为,这只会让它崩溃。换句话说:unittest显然有内部机制,可以为它将运行的每个测试创建一个类的新实例,而这不适用于您的安排。

与其试图弄清楚unittest的内部工作原理,以及它如何在每个实例中标记它正在运行的测试,不如远离你的混乱。

第一:单身人士被高估了。用于创建单例的元类被高估了,而且是错误的。

第二:多重继承在非常特殊的情况下是很好的。想要组合一个类;必须是singleton";对于需要多个实例来运行测试的类,显然不是其中之一。请改用合成。

所以,从开始

class SpecialMethods:  # <- that, just create the class
def my_special_method_1(self):
# do special things
...
SpecialMethods = SpecialMethods()  # <- here is your singleton. 
# no one is creating another instance of this by accident
class TestSomething(TestCase):  # no even need for a intermediate class or a custom __init__
def test_something1(self):
print('print test_something1')
SpecialMethods.my_special_method1()  # <surprise: this just works!!
self.assertTrue(True)

作为最后一个不完全相关的提示:也许你可以使用";pytest";对于您的测试而不是unittest:测试需要更少的锅炉测试代码,包括不需要创建人工类来命名测试函数。

最新更新