如何在Python中为测试虚拟一个模块中的类?

4 投票
1 回答
12913 浏览
提问于 2025-04-17 03:22

我有一个模块在用,它依赖于一个叫做RealClass的东西,但我没有权限去查看这个类。

我想创建一个FakeClass,来替代RealClass的功能,以便进行测试。我不想只替换某个方法,而是想替换整个类。

我查了一下stubble,看起来它能满足我的需求,但我想知道mox或者其他的模拟框架是否也有这个功能?或者你有什么建议可以用的?也许是fudge,或者是猴子补丁?我只是想了解一下这方面的最佳实践。如果能提供一些有用的例子就更好了。

伪代码:

from module import RealClass

class FakeClass
    methodsFromRealClassOverridden

class Test(unittest.TestCase):
    setup()
    teardown()

test1()
    stub(RealClass, FakeClass) // something like this, but really just want the functionality
    classThatUsesRealClass // now will use FakeClass

更新:

我找到了一种方法来实现这个功能。虽然不是完美的,但确实有效。

示例:

fake = FakeClass()
stub = stubout.StubOutForTesting()
stub.Set(RealClass, 'method_1', fake.method_1)
stub.Set(RealClass, 'method_2', fake.method_2)

1 个回答

10

我觉得你想听听大家的看法和经验,所以我来分享一下我的想法。

正如你所注意到的,Python有很多测试工具、类和框架,但大多数情况下,由于Python简单、灵活和开放,你可能会选择使用一些临时的测试案例,这些测试通常是在接口层面进行一些简单的替换和一点unittest的使用……直到你开始使用框架。

在进行测试或替换时,使用“猴子补丁”并没有什么不好的地方:

#!/usr/bin/env python
# minimal example of library code

class Class:
    """ a class """
    def method(self, arg):
        """ a method that does real work """
        print("pouet %s" % arg)

#!/usr/bin/env python
# minimal example for stub and tests, overriding/wrapping one method
from Class import Class

Class._real_method = Class.method
def mymethod(self, arg):
    # do what you want
    print("called stub")
    # in case you want to call the real function...
    self._real_method(arg)
Class.method = mymethod

# ...

e = Class()
e.method("pouet")

命名空间可以让你在导入的模块中进行补丁操作……

需要注意的是,上面的方法在C模块的类中是行不通的。对于这些情况,你可以使用一个包装类,通过getattr/setattr来过滤类成员的名称,并从包装类返回重新定义的成员。

#!/usr/bin/env python
# Stupid minimal example replacing the sys module
# (not very useful / optimal, it's just an example of patching)

import sys

class SysWrap():
    real = sys
    def __getattr__(self, attr):
        if attr == 'stderr':
            class StdErr():
                def write(self, txt):
                    print("[err: %s]" % txt)
            return StdErr()
        print("Getattr %s" % attr)
        return getattr(SysWrap.real, attr)

sys = SysWrap()
# use the real stdout
sys.stdout.write("pouet")
# use fake stderr
sys.stderr.write("pouet")

一旦你厌倦了临时测试,你会发现一些更高级的工具,比如你提到的那些(stubble, fudge)会很有用,但要想高效地使用它们,你首先得理解它们解决了什么问题,并接受它们在背后自动做的事情。

可能会有一部分临时的猴子补丁仍然存在,因为它更容易理解,而且所有工具都有一些局限性。

工具可以增强你的能力,但你必须深入理解它们,才能高效使用。

在决定是否使用某个工具时,一个重要的方面是,当你传递一段代码时,你也在传递整个环境(包括测试工具)。下一个人可能没有你那么聪明,可能会因为你的测试工具太复杂而跳过测试。一般来说,你会希望在软件中避免使用太多的依赖。

最后,我认为如果你只是使用unittest和临时测试/猴子补丁,只要你的代码能正常工作,没人会在意。毕竟你的代码可能并没有那么复杂。

撰写回答