Tombola子类的测试方法

Tombola子类的测试方法

  • Tombola子类的测试方法
  • Python使用register的方式
  • 鹅的行为有可能像鸭子

Tombola子类的测试方法

编写的Tombola示例测试脚本用到两个类属性,用它们内省类的继承关
__subclasses__()
这个方法返回类的直接子类列表,不含虚拟子类。
_abc_registry
只有抽象基类有这个数据属性,其值是一个WeakSet对象,即抽象类注册的虚拟子类的弱引用。

#示例1:Tombola子类的测试运行程序
import doctest 
 
from tombola import Tombola 
 
# 要测试的模块 
import bingo, lotto, tombolist, drum  #➊ 
 
TEST_FILE = 'tombola_tests.rst' 
TEST_MSG = '{0:16} {1.attempted:2} tests, {1.failed:2} failed - {2}' 
def main(argv): 
    verbose = '-v' in argv 
    real_subclasses = Tombola.__subclasses__()  #➋ 
    virtual_subclasses = list(Tombola._abc_registry)  #➌ 
 
    for cls in real_subclasses + virtual_subclasses:  #➍ 
        test(cls, verbose) 
 
 
def test(cls, verbose=False): 
 
    res = doctest.testfile( 
            TEST_FILE, 
            globs={
     'ConcreteTombola': cls},  #➎ 
            verbose=verbose, 
            optionflags=doctest.REPORT_ONLY_FIRST_FAILURE) 
    tag = 'FAIL' if res.failed else 'OK' 
    print(TEST_MSG.format(cls.__name__, res, tag))  #➏ 
 
 
if __name__ == '__main__': 
    import sys 
    main(sys.argv)

➊ 导入包含Tombola真实子类和虚拟子类的模块,用于测试。
➋ __subclasses__()返回的列表是内存中存在的直接子代。即便源码中用不到想测试模块,也要将其导入,因为要把那些类载入内存。
➌ 把_abc_registry(WeakSet对象)转换成列表,这样方能与__subclasses__()的结果拼接起来。
➍ 迭代找到的各个子类,分别传给test函数。
➎ 把cls参数(要测试的类)绑定到全局命名空间里的ConcreteTombola名称上,供doctest使用。
➏ 输出测试结果,包含类的名称、尝试运行的测试数量、失败的测试数量,以及’OK’或’FAIL’标记。

Python使用register的方式

#示例2:TomboList是Tombola的虚拟子类
from random import randrange 
 
from tombola import Tombola 
 
@Tombola.register  # ➊ 
class TomboList(list):  # ➋ 
 
    def pick(self): 
        if self:  # ➌ 
            position = randrange(len(self)) 
            return self.pop(position)  # ➍ 
        else: 
            raise LookupError('pop from empty TomboList') 
 
    load = list.extend  # ➎ 
 
    def loaded(self): 
        return bool(self)  # ➏ 
 
    def inspect(self): 
        return tuple(sorted(self)) 
 
# Tombola.register(TomboList)  # ➐

➊ 把Tombolist注册为Tombola的虚拟子类。
➋ Tombolist扩展list。
➌ Tombolist从list中继承__bool__方法,列表不为空时返回True。
➍ pick调用继承自list的self.pop方法,传入一个随机的元素索引。
➎ Tombolist.load与list.extend一样。
➏ loaded方法委托bool函数。
➐ 如果是Python 3.3或之前的版本,不能把.register当作类装饰器使用,必须使用标准的调用句法。

虽然上述示例中可以把register 当作装饰器使用了。但更常见的做法是把它当作函数使用,用于注册其他地方定义的类。
例如,在collections.abc模块的源码中,是这样把内置类型tuple、str、range和memoryview注册为Sequence的虚拟子类的:
Sequence.register(tuple)
Sequence.register(str)
Sequence.register(range)
Sequence.register(memoryview)

其他几个内置类型在_collections_abc.py文件中注册为抽象基类的虚拟子类。这些类型在倒入模块时注册,这样做是可以的,因为必须导入才能使用抽象基类:能访问MutableMapping才能编写isinstance(my_dict,MutableMapping)。

鹅的行为有可能像鸭子

即便不注册,抽象基类也能把一个类识别为虚拟子类。

#示例3: Sized类的源码
 class Sized(metaclass=ABCMeta): 
 
    __slots__ = () 
 
    @abstractmethod 
    def __len__(self): 
        return 0 
 
    @classmethod 
    def __subclasshook__(cls, C): 
        if cls is Sized: 
            if any("__len__" in B.__dict__ for B in C.__mro__):  # ➊ 
                return True  # ➋ 
        return NotImplemented  # ➌

➊ 对C.mro(即C及其超类)中所列的类来说,如果类的__dict__属性中有名为 __len__的属性……
➋ ……返回True,表明C是Sized的虚拟子类。
➌ 否则,返回NotImplemented,让子类检查。

你可能感兴趣的:(python)