如何为外部库的类添加自定义方法?

2 投票
1 回答
986 浏览
提问于 2025-04-17 01:36

我在使用Django的时候,有一些特殊的情况需要测试。我想通过写自己的测试用例来扩展现有的Django测试。现在我正在这样做。

from django.tests import TestCase

# define my own method as a function
def assertOptionsEqual(self, first, second):
    # logic here
    pass

# Attach the method to the TestCase class. This feels inelegant!
TestCase.assertOptionsEqual = assertOptionsEqual

# tests go here
class KnownGoodInputs(TestCase):
    def test_good_options(self):
        self.assertOptionsEqual(...)

虽然这样可以工作,但把一个方法定义为以self作为第一个参数的函数,然后再把它附加到TestCase上,感觉有点不太优雅。我想知道有没有更好的方法来给TestCase类添加我自己的方法?我可以这样做...

class MyTestCase(TestCase):
    def assertOptionsEqual(self, first, second):
        ...

然后在所有测试中使用MyTestCase,但我在想有没有更好的选择。谢谢!

1 个回答

1

我觉得你已经提到了两种选择。你可以选择子类化或者猴子补丁。通常来说,猴子补丁就是在运行时直接修改第三方类,这种做法是不太被推荐的,但有时候为了修复一个bug或者确保每次使用这个类时都有你新增的方法,这可能是唯一的解决办法。

因为只有你的测试会用到你的方法,所以猴子补丁其实没必要,子类化 TestCase 是个合理的选择。一般来说,当你需要增强一个已有类的方法时,才会用到猴子补丁。比如,如果你想在现有的测试用例中对 TestCase.assertEqual 的调用增加一些逻辑,以便比较 Option 对象,你可以通过猴子补丁来修改 TestCase.assertEqual,让它包含你的自定义逻辑和正常逻辑,像这样:

originalAssertEqual = TestCase.assertEqual
def newAssertEqual(self, first, second):
    result = originalAssertEqual(first, second)
    if isinstance(first, Option) and isinstance(second, Option):
        # do your custom comparison
    return result
TestCase.assertEqual = newAssertEqual 

不过在这个例子中,看来子类化和猴子补丁都是不必要的。

假设问题是调用 self.assertEqual(firstOptions, secondOptions) 失败了,尽管 Option 实例是相等的,你其实不需要写一个新的 assertOptionsEqual 方法。你可能只需要确保你的 Option 对象正确地定义了 __eq__ 方法。

所以假设你有:

class KnownGoodInputs(TestCase):
    def test_good_options(self):
        first, second = systemUnderTestGetOptions(...)
        self.assertOptionsEqual(first, second)

那么上面 firstsecond 的类是什么呢?

对于所有Python内置类型,assertEqual 应该都能正常工作。对于自定义的 Option 类,你只需要这样做:

class Option(object): def init(self): use_foo = False use_bar = True

def __eq__(self, other):
    if (self.use_foo == other.use_foo and
        self.use_bar == other.use_bar):
        return True
    return False

然后假设 firstsecondOption 的实例,你可以这样写你的测试:

class KnownGoodInputs(TestCase):
    def test_good_options(self):
        first, second = systemUnderTestGetOptions(...)
        self.assertEqual(first, second)

撰写回答