2016-04-04 140 views
2

写单元测试看看这个例子:为抽象类

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

使用具体的子类来测试它。 –

+0

并检查覆盖范围以确保抽象类代码在测试期间被覆盖。 –

回答

3

至少就语言而言,您确实没有抽象基类。没有什么阻止你实例化它。

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") 

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

+0

谢谢你的回应!我不知道是否有办法在Python中创建“真正的”抽象类,是的,在我的例子中,类“A”根本不抽象。'helper_method'的测试是必要的,但仍然存在一个误解。方法'do_stuff'仍然不能直接从类“A”调用。也许我只需要使用'A'的一些子类来测试'do_stuff'方法(就像@engineer指出的那样),或者直接在测试代码中覆盖'A'。 – irvind

+0

你已经实例化了'A',你可以用'a = A()来调用方法。 a.do_stuff()'。如果你没有实例化它,你就不能在没有*东西的情况下调用它来充当一个实例。你可以传递一个模拟对象:'m = unittest.mock.Mock(); A.do_stuff(米)'。 – chepner

1

您应该测试逻辑,而不是执行。 A的do_stuff()方法本身没有逻辑,对吧?它的作用取决于你是在处理B还是在C中。相反,在我看来,测试B和C的do_stuff()方法会更有意义 - 你知道他们应该做什么。

1

由于@chepner已经回答了您的问题,而不是离题,但您应该尽量避免在Python中使用抽象类。抽象类在动态DuckTyped语言(如Python,Ruby等)中没有或者不应该有太多目的。在Duck-typing中,只要特定实例响应特定行为,就不应该强制它成为孩子一个特定的抽象类。

1

do_stuff存在于A上,所以请在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()