【问题标题】:How can I avoid dynamically generated classes derived from metaclass not to end up as the same class?如何避免从元类派生的动态生成的类不最终成为同一个类?
【发布时间】:2015-05-28 14:23:31
【问题描述】:

我要做的是对一个函数执行数百个单元测试,我可以从字典中派生这些单元测试。不幸的是,我不能使用任何现有的包进行参数化测试(如鼻子),所以我试图获得自己的解决方案。

我对以下示例代码的意图是创建 3 个类(每个测试一个),它们将存在于全局范围内,以便单元测试可以选择它并运行相应的测试。

tests = [
    {'text': 'text1fdskla3fsda4',
     'result': [1, 3, 4],
     },
    {'text': 'fdsg45tg5b',
     'result': [4, 5, 5,5 ],
     },
    {'text': 'fsddf4',
     'result': [4, 2],
   }
]

def evaluate(text):
    out = []
    for char in text:
        if char.isdigit():
            out.append(int(char))
    return out

class TestMeta(type):
    def __new__(cls, name, bases, attrs):
        name = str(test['text'])
        return type.__new__(cls, name, (unittest.TestCase,), attrs)

for test in tests:
    class T(object):
        __metaclass__ = TestMeta
        def testOne(self):
            self.assertEqual(test['result'], evaluate(test['text']))
    globals()[(test['text'])] = copy.deepcopy(T)

unittest.main()

当我运行上面的代码时,我得到了四个单元测试,这比预期的多一个,但最重要的是,我创建的类似乎总是相同的,而不是每个单元测试的输出(即使我实际上为每个设置了不同的名称和参数):

======================================================================
FAIL: testOne (__main__.fsddf4)
----------------------------------------------------------------------
Traceback (most recent call last):
  File "ble.py", line 45, in testOne
    self.assertEqual(test['result'], evaluate(test['text']))
AssertionError: [4, 2] != [4]

======================================================================
FAIL: testOne (__main__.fdsg45tg5b)
----------------------------------------------------------------------
Traceback (most recent call last):
  File "ble.py", line 45, in testOne
    self.assertEqual(test['result'], evaluate(test['text']))
AssertionError: [4, 2] != [4]

======================================================================
FAIL: testOne (__main__.fsddf4)
----------------------------------------------------------------------
Traceback (most recent call last):
  File "ble.py", line 45, in testOne
    self.assertEqual(test['result'], evaluate(test['text']))
AssertionError: [4, 2] != [4]

======================================================================
FAIL: testOne (__main__.text1fdskla3fsda4)
----------------------------------------------------------------------
Traceback (most recent call last):
  File "ble.py", line 45, in testOne
    self.assertEqual(test['result'], evaluate(test['text']))
AssertionError: [4, 2] != [4]

----------------------------------------------------------------------
Ran 4 tests in 0.002s

FAILED (failures=4)

deepcopy 试图获得不同的输出,但没有帮助。在 globals dict 中显式创建类是因为简单地在 for 循环上创建类只会产生一个单元测试。

【问题讨论】:

    标签: python unit-testing metaclass


    【解决方案1】:

    除了对元一切的深入尝试之外,您还遇到了一个典型的 Python 初学者问题:全局名称查找是在运行时完成的。

    所以,在你的代码中:

    for test in tests:
        class T(object):
            __metaclass__ = TestMeta
            def testOne(self):
                self.assertEqual(test['result'], evaluate(test['text']))
    

    当testOne 运行时,它会在globals 字典中查找test当时 -- 它运行的时间 -- 到那时@当然,987654325@ 设置为最近设置的值。

    你需要强制绑定提前发生,你可以这样做,例如简单地改变

            def testOne(self):
    

    进入

            def testOne(self, test=test):
    

    此更改强制在 def 执行时查找全局 test(而不是稍后执行该方法的 body 时),这是同一时间class 语句执行 - 即,当全局变量 test 设置为列表 tests 的当前项时,每条循环执行一次。

    【讨论】:

    • 这绝对有道理,指出似乎很明显,非常感谢。
    • 我想知道作为后续你是否可以告诉我是否有更好的方法来创建所有类而不使用 "globals()[(test['text'])] = T" .没有它,对于每个循环周期,都会创建一个新的类 get,但它会在下一次迭代中被覆盖,因为每个实例 get 都是在全局变量中使用标签“T”创建的。
    • @user4709759,但您没有在您的示例代码中这样做 - 您正在使用 这样做的 test['text'] 全局名称每次循环都不同!无论如何,unittest.TestCase.__subclasses__ 应该也工作(但你也需要在某个地方保留对每个类对象的引用,以免它被垃圾收集,globals 可以)。
    • 也许我不够清楚。我的问题是我是否可以以某种方式避免手动将创建的类复制到带有自定义标签的全局变量中。我问这个的原因是因为这看起来不是很干净,例如,在我运行这段代码之后,我在循环的最后一次迭代中在全局变量中有一个带有标签“T”的剩余类,这是创建的最后一个类的重复实例。这就是我最后有 4 个测试而不是 3 个的原因。所以要清理它,我需要添加一个附加行“globals().pop('T')”。但也许这样就好了。
    • @user4709759, unittest.main 将在模块的globals 中查找TestCase 子类——我虽然你想依赖那个发现机制!如果不是这种情况,那么您需要显式构建一个unittest.TestSuite(只需将您的TestCase 子类列表给它的ctor),使用result 参数调用.run,对最终值进行自己的报告这个结果——或者许多其他方法,太多了,无法挤进 cmets,所以,你可能想打开一个单独的 Q!
    猜你喜欢
    • 2012-03-05
    • 2018-11-30
    • 1970-01-01
    • 2017-05-04
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 2017-10-07
    相关资源
    最近更新 更多