返回介绍

11.8 Tombola 子类的测试方法

发布于 2024-02-05 21:59:47 字数 3858 浏览 0 评论 0 收藏 0

我编写的 Tombola 示例测试脚本用到两个类属性,用它们内省类的继承关系。

__subclasses__()

这个方法返回类的直接子类列表,不含虚拟子类。

_abc_registry

只有抽象基类有这个数据属性,其值是一个 WeakSet 对象,即抽象类注册的虚拟子类的弱引用。

为了测试 Tombola 的所有子类,我编写的脚本迭代 Tombola.__subclasses__() 和 Tombola._abc_registry 得到的列表,然后把各个类赋值给在 doctest 中使用的 ConcreteTombola。

这个测试脚本成功运行时输出的结果如下:

$ python3 tombola_runner.py
BingoCage    24 tests,  0 failed - OK
LotteryBlower  24 tests,  0 failed - OK
TumblingDrum   24 tests,  0 failed - OK
TomboList    24 tests,  0 failed - OK

测试脚本的代码在示例 11-15 中,doctest 在示例 11-16 中。

示例 11-15 tombola_runner.py: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' 标记。

doctest 文件如示例 11-16 所示。

示例 11-16 tombola_tests.rst:Tombola 子类的 doctest

==============
Tombola tests
==============

Every concrete subclass of Tombola should pass these tests.


Create and load instance from iterable::

  >>> balls = list(range(3))
  >>> globe = ConcreteTombola(balls)
  >>> globe.loaded()
  True
  >>> globe.inspect()
  (0, 1, 2)


Pick and collect balls::

  >>> picks = []
  >>> picks.append(globe.pick())
  >>> picks.append(globe.pick())
  >>> picks.append(globe.pick())


Check state and results::

  >>> globe.loaded()
  False
  >>> sorted(picks) == balls
  True


Reload::

  >>> globe.load(balls)
  >>> globe.loaded()
  True
  >>> picks = [globe.pick() for i in balls]
  >>> globe.loaded()
  False


Check that `LookupError` (or a subclass) is the exception
thrown when the device is empty::

  >>> globe = ConcreteTombola([])
  >>> try:
  ... globe.pick()
  ... except LookupError as exc:
  ... print('OK')
  OK


Load and pick 100 balls to verify that they all come out::

  >>> balls = list(range(100))
  >>> globe = ConcreteTombola(balls)
  >>> picks = []
  >>> while globe.inspect():
  ... picks.append(globe.pick())
  >>> len(picks) == len(balls)
  True
  >>> set(picks) == set(balls)
  True


Check that the order has changed and is not simply reversed::
  >>> picks != balls
  True
  >>> picks[::-1] != balls
  True

Note: the previous 2 tests have a *very* small chance of failing
even if the implementation is OK. The probability of the 100
balls coming out, by chance, in the order they were inspect is
1/100!, or approximately 1.07e-158. It's much easier to win the
Lotto or to become a billionaire working as a programmer.

THE END

我们对 Tombola 抽象基类的分析到此结束。下一节说明 Python 如何使用抽象基类的 register 函数。

如果你对这篇内容有疑问,欢迎到本站社区发帖提问 参与讨论,获取更多帮助,或者扫码二维码加入 Web 技术交流群。

扫码二维码加入Web技术交流群

发布评论

需要 登录 才能够评论, 你可以免费 注册 一个本站的账号。
列表为空,暂无数据
    我们使用 Cookies 和其他技术来定制您的体验包括您的登录状态等。通过阅读我们的 隐私政策 了解更多相关信息。 单击 接受 或继续使用网站,即表示您同意使用 Cookies 和您的相关数据。
    原文