编写抽象类的单元测试。

3

考虑这个例子:

class A:
    def do_stuff(self):
        # ...
        some_var = self.helper_method()
        # ...

    def helper_method(self):
        # This method must be implemented by subclass
        raise NotImplementedError()

class B(A):
    def helper_method(self):
        # implementation for class B

class C(A):
    def helper_method(self):
        # implementation for class C

我的任务是为ABC类编写单元测试(特别是do_stuff方法)。
但如果我不能直接使用它的某些方法,该如何测试A类呢? 我应该只测试BC类(它们实现了helper_method) 还是Python中有一种通用的方法来测试抽象类?

1
使用一个具体的子类进行测试。 - ROMANIA_engineer
并检查覆盖率,确保在测试过程中已经覆盖了抽象类的代码。 - Łukasz Rogalski
4个回答

7

A存在do_stuff,因此在A上测试它。辅助方法存在于具体类中,因此在那里测试它们。您可以使用unittest.mock模块暂时修补抽象类,以便它能够与您的测试一起工作,并且还可以修补抽象方法以返回特定值--以确保其逻辑不在测试范围内。鉴于所有这些,这就是我如何测试抽象类。

假设有一个抽象类:

from abc import abstractmethod, ABC

class MyAbstract(ABC):
    def concrete_method(self):
        i = self.abstract_method()
        return i ** 2

    @abstractmethod
    def abstract_method(self):
        """return some int"""
        pass

这是我如何测试它。
from unittest import main, TestCase
from unittest.mock import patch, Mock

from module_under_test import MyAbstract

class TestMyAbstract(TestCase):

    def test_cannot_instantiate(self):
        """showing we normally can't instantiate an abstract class"""
        with self.assertRaises(TypeError):
            MyAbstract()

    @patch.multiple(MyAbstract,
        __abstractmethods__=set(),
        abstract_method=Mock(return_value=3))
    def test_concrete_method(self):
        """patch abstract class  and its abstract methods for duration of the test"""
        # given
        my_abstract = MyAbstract()
        expected = 9

        # when
        actual = my_abstract.concrete_method()

        # then
        self.assertEqual(actual, expected)

if __name__ == "__main__":
    main()

6

就语言而言,您并没有真正的抽象基类。但这并不妨碍您实例化它。

a = A()

如果你正在使用abc模块来定义不能实例化的类:
class A(metaclass=abc.ABCMeta):
    ...

那么,您可以通过重写抽象方法集使 A 可以实例化:
A.__abstractmethods__ = frozenset()
a = A()
# test away

无论哪种情况,您仍然可以测试抽象方法是否引发NotImplementedError
try:
    a.helper_method()
except NotImplementedError:
    print("Test passed")
else:
    print("Test failed")

或者根据需要测试其默认实现。


谢谢您的回复!我不知道在Python中有一种创建“真正”的抽象类的方法,是的,我的示例中的A类根本不是抽象的。对于helper_method的测试是必要的,但仍然存在一个误解。 do_stuff方法仍然无法直接从A类调用。也许我只需要使用'A'的某些子类来测试'do_stuff'方法(如@engineer所指出的),或者直接在测试代码中覆盖'A'。 - irvind
一旦你实例化了 A,你就可以像这样正常调用方法:a = A(); a.do_stuff()。如果你没有实例化它,你无法在没有充当实例角色的某物的情况下调用它。你可以传递一个模拟对象:m = unittest.mock.Mock(); A.do_stuff(m) - chepner

1
你应该测试逻辑,而不是实现细节。A的do_stuff()方法本身没有逻辑,对吧?它所做的取决于你是否处理B或C。相反,我认为更有意义的是测试B和C的do_stuff()方法 - 你知道它们应该做什么。

1

如@chepner已经回答了你的问题,不要离题,但你应该尽量避免在Python中使用抽象类。在动态鸭子类型语言(如Python、Ruby等)中,抽象类并不或者说不应该起到太多作用。在鸭子类型中,只要特定实例响应特定行为,就不应该强制它成为特定抽象类的子类。


网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接