【问题标题】:In Python, what's a good pattern for disabling certain code during unit tests?在 Python 中,在单元测试期间禁用某些代码的好模式是什么?
【发布时间】:2010-02-23 17:20:48
【问题描述】:

一般来说,我希望禁用尽可能少的代码,并且我希望它是明确的:我不希望被测试的代码决定它是否是测试,我希望测试告诉那个代码“嘿,顺便说一句,我正在运行一个单元测试,你能不能不要打电话给 solr,而是请你把你要发送给 solr 的东西贴在这个地方,这样我就可以检查了”。我有我的想法,但我不喜欢其中任何一个,我希望有一个好的 Pythonic 方法来做到这一点。

【问题讨论】:

    标签: python unit-testing testing dependency-injection


    【解决方案1】:

    您可以使用Mock objects 拦截您不想执行的方法调用。 例如。你有一些类A,你不希望在测试期间调用方法no()

    class A:
      def do(self):
        print('do')
      def no(self):
        print('no')
    

    模拟对象可以从 A 继承并覆盖 no() 以不执行任何操作。

    class MockA(A):
      def no(self):
        pass
    

    然后,您将在测试代码中创建 MockA 对象而不是 As。另一种进行模拟的方法是让AMockA 实现一个公共接口,比如InterfaceA

    有大量可用的模拟框架。见StackOverflow: Python mocking frameworks

    具体见:Google's Python mocking framework

    【讨论】:

    • 子类化来实现模拟可能会带来各种你不需要的东西。此外,您的方法不采用所需的第一个参数
    • 哇,我真的在挖掘 Michael Foord 的 Mock。在 python 中创建 Mock 对象是多么容易,这真是太棒了。如果您必须在 C++ 中使用接口和继承手动执行此操作,您就会知道这可能是多么痛苦。编辑:顺便说一句,这个答案并没有真正正确地模拟,但指出了您可能正在寻找的方式。见 [voidspace.org.uk/python/mock/…
    • @Mike Graham,感谢您指出缺失的论点。我并不是说子类化是进行 Mocking 的最佳方式,我只是举个例子。
    【解决方案2】:

    使用 Michael Foord 的 Mock 在你的单元测试中这样做:

    from mock import Mock
    
    class Person(object):
        def __init__(self, name):
            super(Person, self).__init__()
            self.name = name
    
        def say(self, str):
            print "%s says \"%s\"" % (self.name, str)
    
    
    ...
    
    #In your unit test....
    #create the class as normal
    person = Person("Bob")
    #now mock all of person's methods/attributes
    person = Mock(spec=person)
    #talkto is some function you are testing
    talkTo(person)
    #make sure the Person class's say method was called
    self.assertTrue(person.say.called, "Person wasn't asked to talk")
    
    #make sure the person said "Hello"
    args = ("Hello")
    keywargs = {}
    self.assertEquals(person.say.call_args, (args, keywargs), "Person did not say hello")
    

    【讨论】:

    • 哇,Mock 太棒了!谢谢。在我的 C++/Java 时代,我曾经花费太多时间手动编写模拟类。
    • 是的,不开玩笑!我刚刚在阅读这个问题时了解到这些东西.. 肯定会将它添加到技巧书中
    • 从Python 3.3开始,有一个模块unittest.mockin the standard library
    【解决方案3】:

    我遇到的最大问题是依赖注入的机制。我现在已经弄清楚了那部分。

    我需要在两个地方以完全相同的方式导入模块才能成功注入新代码。例如,如果我想禁用以下代码:

    from foo_service.foo import solr
    solr.add(spam)
    

    我似乎无法在我的测试运行器中执行此操作:

    from foo import solr
    solr = mock_object
    

    python 解释器必须将模块 foo_service.foofoo 视为不同的条目。我将from foo import solr 更改为更明确的from foo_service.foo import solr,并且我的模拟对象已成功注入。

    【讨论】:

      【解决方案4】:

      通常,当出现此类情况时,您会使用 Monkey Patching(也称为 Duck Punching)来获得所需的结果。查看this link 了解更多关于猴子补丁的信息。

      在这种情况下,例如,您将覆盖 solr 以仅打印您正在寻找的输出。

      【讨论】:

        【解决方案5】:

        您有两种方法可以做到这一点,没有,或者在DI 的情况下最少, 修改您的源代码

        cleanest 的方式是使用dependency injection,但I don't really like extensive monkeypatching,有些事情是不可能/难以做到的,dependency injection 很容易。

        【讨论】:

          【解决方案6】:

          我知道这是模拟对象的典型用例,但这也是一个古老的论点...... Mock 对象是必需的还是它们邪恶

          我站在那些认为模拟是邪恶并且会尽量避免更改测试代码的人的一边。我什至认为修改测试代码的这种需要是一种代码味道......

          如果您希望更改或拦截内部函数调用以进行测试,您还可以将此函数设置为在实例化时设置的显式外部依赖项,该依赖项将由您的生产代码和测试代码提供。如果你这样做了,问题就会消失,你最终会得到一个更干净的界面。

          请注意,这样做既不需要在内部也不需要通过正在执行的测试来更改测试代码。

          【讨论】:

            猜你喜欢
            • 2011-11-18
            • 2013-12-05
            • 2019-06-30
            • 2014-02-24
            • 1970-01-01
            • 1970-01-01
            • 1970-01-01
            • 1970-01-01
            • 2010-09-08
            相关资源
            最近更新 更多