2012-03-18 37 views
20

Tôi đang tìm cách/thực tiễn tốt nhất về các phương pháp thử được xác định trong một lớp cơ sở trừu tượng. Một điều tôi có thể nghĩ trực tiếp là thực hiện bài kiểm tra trên tất cả các lớp con cụ thể của lớp cơ sở, nhưng điều đó dường như quá mức vào một số lần.Python - Thử nghiệm một lớp cơ sở trừu tượng

Hãy xem xét ví dụ sau:

import abc 

class Abstract(object): 

    __metaclass__ = abc.ABCMeta 

    @abc.abstractproperty 
    def id(self): 
     return 

    @abc.abstractmethod 
    def foo(self): 
     print "foo" 

    def bar(self): 
     print "bar" 

Có thể kiểm tra bar mà không làm bất kỳ subclassing?

Trả lời

15

Khi được đặt bởi lunaryon, điều đó là không thể. Mục đích chính của ABC chứa các phương thức trừu tượng là chúng không thể khởi tạo được như đã khai báo.

Tuy nhiên, bạn có thể tạo một hàm tiện ích để quan sát một ABC và tạo một lớp trừu tượng, không trừu tượng khi đang di chuyển. Hàm này có thể được gọi trực tiếp bên trong phương thức/hàm kiểm thử của bạn và cho phép bạn phải trích dẫn mã đĩa nồi hơi trên tệp thử nghiệm chỉ để thử nghiệm một vài phương thức.

def concreter(abclass): 
    """ 
    >>> import abc 
    >>> class Abstract(metaclass=abc.ABCMeta): 
    ...  @abc.abstractmethod 
    ...  def bar(self): 
    ...  return None 

    >>> c = concreter(Abstract) 
    >>> c.__name__ 
    'dummy_concrete_Abstract' 
    >>> c().bar() # doctest: +ELLIPSIS 
    (<abc_utils.Abstract object at 0x...>,(), {}) 
    """ 
    if not "__abstractmethods__" in abclass.__dict__: 
     return abclass 
    new_dict = abclass.__dict__.copy() 
    for abstractmethod in abclass.__abstractmethods__: 
     #replace each abc method or property with an identity function: 
     new_dict[abstractmethod] = lambda x, *args, **kw: (x, args, kw) 
    #creates a new class, with the overriden ABCs: 
    return type("dummy_concrete_%s" % abclass.__name__, (abclass,), new_dict) 
+0

Tuyệt. Tôi sẽ thử với mã này trên một số thử nghiệm của tôi :). Cảm ơn! – bow

3

Không, không phải. Mục đích chính của abc là tạo các lớp không thể được khởi tạo trừ khi tất cả các thuộc tính trừu tượng được ghi đè bằng các triển khai cụ thể. Do đó bạn cần bắt nguồn từ lớp cơ sở trừu tượng và ghi đè lên tất cả các phương thức và thuộc tính trừu tượng.

+0

Hm..Đó là như vậy, nó sẽ là khôn ngoan để làm cho một lớp con giả của lớp trừu tượng với các phương pháp trừu tượng và các thuộc tính được xác định, sau đó làm bài kiểm tra trên nó? – bow

+3

@bow: Vâng, đó là cách bạn sẽ làm điều này. – lunaryorn

15

Đây là những gì tôi đã tìm thấy: Nếu bạn đặt thuộc tính là __abstractmethods__ thành bộ trống, bạn có thể khởi tạo lớp trừu tượng. Hành vi này được quy định tại PEP 3119:

Nếu kết quả __abstractmethods__ bộ không bị để trống, lớp được coi là trừu tượng, và cố gắng để nhanh chóng nó sẽ nâng TypeError.

Vì vậy, bạn chỉ cần xóa thuộc tính này trong thời gian thử nghiệm.

>>> import abc 
>>> class A(metaclass = abc.ABCMeta): 
...  @abc.abstractmethod 
...  def foo(self): pass 

Bạn không thể nhanh chóng A:

>>> A() 
Traceback (most recent call last): 
TypeError: Can't instantiate abstract class A with abstract methods foo 

Nếu bạn ghi đè __abstractmethods__ bạn có thể:

>>> A.__abstractmethods__=set() 
>>> A() #doctest: +ELLIPSIS 
<....A object at 0x...> 

Nó hoạt động cả hai cách:

>>> class B(object): pass 
>>> B() #doctest: +ELLIPSIS 
<....B object at 0x...> 

>>> B.__abstractmethods__={"foo"} 
>>> B() 
Traceback (most recent call last): 
TypeError: Can't instantiate abstract class B with abstract methods foo 

Bạn cũng có thể sử dụng unittest.mock (từ 3.3) để ghi đè hành vi ABC tạm thời.

>>> class A(metaclass = abc.ABCMeta): 
...  @abc.abstractmethod 
...  def foo(self): pass 
>>> from unittest.mock import patch 
>>> p = patch.multiple(A, __abstractmethods__=set()) 
>>> p.start() 
{} 
>>> A() #doctest: +ELLIPSIS 
<....A object at 0x...> 
>>> p.stop() 
>>> A() 
Traceback (most recent call last): 
TypeError: Can't instantiate abstract class A with abstract methods foo 
14

Trong các phiên bản mới hơn của Python bạn có thể sử dụng unittest.mock.patch()

class MyAbcClassTest(unittest.TestCase): 

    @patch.multiple(MyAbcClass, __abstractmethods__=set()) 
    def test(self): 
     self.instance = MyAbcClass() # Ha! 
+0

Tại sao chúng ta thực sự cần 'nhiều'? –

+0

Bạn có thể thử nhiều thuộc tính cùng một lúc với 'multiple()' –

+0

Có nhưng điều đó dường như không biện minh cho việc sử dụng nó ở đây vì chỉ có một thuộc tính được vá. Có cái gì đơn giản hơn có thể được sử dụng? –

2

Có lẽ một phiên bản nhỏ gọn hơn của concreter bởi @jsbueno đề xuất có thể là:

def concreter(abclass): 
    class concreteCls(abclass): 
     pass 
    concreteCls.__abstractmethods__ = frozenset() 
    return type('DummyConcrete' + abclass.__name__, (concreteCls,), {}) 

Kết quả là lớp vẫn có tất cả các phương thức trừu tượng ban đầu (có thể được gọi ngay bây giờ, ngay cả khi điều này không có khả năng hữu ích ...) và có thể được mô phỏng là n eeded.

Các vấn đề liên quan