You need to enable JavaScript to run this app.
优惠活动
大模型
产品
解决方案
定价
更多

如何在Python Hypothesis中实现测试示例的GUI可视化展示

Hypothesis测试示例可视化需求
  • 针对每个测试用例(如def test_a(a: int)、def test_b(b: str)),收集测试过程中生成的随机示例
  • 单个测试的所有示例执行完成后,随机抽取5个示例生成GUI窗口展示,阻塞至窗口关闭后再执行下一个测试
  • 若测试捕获到异常,立即基于已收集的示例弹出GUI

现有实现的问题

目前通过execute_example方法实现,但存在明显缺陷:

  • 单个测试会多次触发GUI展示
  • 最后一个测试无法触发GUI展示

现有实现代码:

# It adds the scenario returned by the test into a collection of scenarios
# to be used for a GUI demo, and displays some of the related scenarios whenever
# the test changes or an exception occurs.
def execute_example(self, fn):
  def viewScens(viewDesc):   
    scens = self.scenarios[viewDesc]
    if scens:
      numScens = min(NUM_VIZ, len(scens))
      print(f"showing {numScens} '{viewDesc}' scenarios")
      testGUI = TestGUI(random.sample(scens, numScens))
      testGUI.run()

  self.curDesc = None
  self.curScen = None
  try:
    ret = fn()
    if self.curDesc != self.lastDesc:
      if self.curDesc in self.seenDescs:
        # PROBLEM: this happens several times per test
        print(f"'{self.curDesc}' has been seen before")
      # PROBLEM: this won't ever be called for the last test!
      viewScens(self.lastDesc)
    return ret  
                                 
  except Exception as e:
    # UnsatisfiedAssumption is used to break out of an example where the preconditions aren't met
    if not isinstance(e, UnsatisfiedAssumption):
      print(f"exception with '{self.curDesc}'")
      viewScens(self.curDesc)
    raise e

  finally:
    self.seenDescs.add(self.curDesc)
    if isinstance(self.curScen, TestScenario):
      self.scenarios[self.curDesc].append(self.curScen)
    self.lastDesc = self.curDesc

优化实现方案

1. 基于Hypothesis生命周期钩子实现

Hypothesis提供了测试生命周期相关的事件钩子,这是最可靠的实现方式:

  • 注册start_test事件:新测试开始时,先展示上一个测试的示例(如果有),再初始化当前测试的示例集合
  • 注册failed_test事件:测试抛出非UnsatisfiedAssumption异常时,立即展示当前测试已收集的示例
  • 注册finish_test事件:测试正常结束时记录示例;结合测试运行器的会话结束钩子,处理最后一个测试的GUI展示

示例代码框架:

from hypothesis import core
import random

class TestScenarioCollector:
    def __init__(self):
        self.current_test = None
        self.test_scenarios = {}
        self.NUM_VIZ = 5

    def start_test(self, test):
        # 处理上一个测试的示例展示
        if self.current_test and self.current_test in self.test_scenarios:
            self._show_scenarios(self.current_test)
        # 初始化当前测试的存储
        self.current_test = test.__qualname__
        self.test_scenarios.setdefault(self.current_test, [])

    def failed_test(self, test, exc_info):
        e = exc_info[1]
        if not isinstance(e, UnsatisfiedAssumption):
            self._show_scenarios(test.__qualname__)

    def add_scenario(self, scenario):
        if self.current_test# 问题背景与需求
针对Python Hypothesis测试,有以下需求:
- 针对每个测试用例(如`def test_a(a: int)`、`def test_b(b: str)`),收集测试过程中使用的随机示例
- 单个测试的所有示例执行完毕后,随机抽取5个示例生成GUI窗口展示,阻塞至窗口关闭后再执行下一个测试
- 若测试中捕获到异常,立即基于已收集的示例弹出GUI

# 现有实现的问题
目前通过`execute_example`方法实现,但存在明显缺陷:单个测试会多次触发GUI展示,且最后一个测试无法触发GUI。代码如下:
```python
# It adds the scenario returned by the test into a collection of scenarios
# to be used for a GUI demo, and displays some of the related scenarios whenever
# the test changes or an exception occurs.
def execute_example(self, fn):
  def viewScens(viewDesc):   
    scens = self.scenarios[viewDesc]
    if scens:
      numScens = min(NUM_VIZ, len(scens))
      print(f"showing {numScens} '{viewDesc}' scenarios")
      testGUI = TestGUI(random.sample(scens, numScens))
      testGUI.run()

  self.curDesc = None
  self.curScen = None
  try:
    ret = fn()
    if self.curDesc != self.lastDesc:
      if self.curDesc in self.seenDescs:
        # PROBLEM: this happens several times per test
        print(f"'{self.curDesc}' has been seen before")
      # PROBLEM: this won't ever be called for the last test!
      viewScens(self.lastDesc)
    return ret  
                                 
  except Exception as e:
    # UnsatisfiedAssumption is used to break out of an example where the preconditions aren't met
    if not isinstance(e, UnsatisfiedAssumption):
      print(f"exception with '{self.curDesc}'")
      viewScens(self.curDesc)
    raise e

  finally:
    self.seenDescs.add(self.curDesc)
    if isinstance(self.curScen, TestScenario):
      self.scenarios[self.curDesc].append(self.curScen)
    self.lastDesc = self.curDesc
待解决的问题
  1. 更优实现方案:如何正确实现需求,规避现有实现的问题?
  2. 测试后置操作方法:如果原需求无法直接实现,有没有办法在所有Hypothesis测试(或特定类/文件的测试)执行完毕后执行自定义操作?
  3. Hypothesis运行机制疑问:为什么Hypothesis会先运行一批test_a的示例,重置状态后再运行更多test_a的示例?即使只有一个测试,也会多次检测到相同desc(意味着测试类被多次初始化),这个机制的运行逻辑是什么?

内容的提问来源于stack exchange,提问作者Nick Collins

相关产品推荐
方舟 Agent Plan

超全模态模型 × Harness 升级,最新支持 Deepseek-V4.1-Flash、GLM-5.3 系列、Doubao-Seedream-5.0-pro、Kimi-K3 (部分), 限时 9.9 元起

最近更新时间:2026.07.09 10:55:35