【问题标题】:How do I control a random seed with Pytest and Hypothesis?如何使用 Pytest 和 Hypothesis 控制随机种子?
【发布时间】:2020-08-12 19:05:35
【问题描述】:

我有一个测试,它执行一个使用随机事物的函数。我想使用hypothesis(或其他什么?)运行它几次,并知道,当它失败时,使用了哪个随机种子。

我该怎么做?

我的目标是多次测试我的代码,以确保它不会因为使用随机而失败。

【问题讨论】:

  • 这听起来不是一个整体的好测试策略,因为您可能永远不会发现那些使您的函数失败的随机值,那么测试的意义何在?
  • 该功能是一个游戏功能,可以在几个角色之间进行战斗。在代码的几个部分中使用了一些随机数(躲闪的机会,打好球的机会等)。目标是测试这些随机是否不会产生意外行为。 (实际上,玩家报告了一个崩溃,我不知道它是如何发生的。我希望通过这种方式找到它)
  • Hypothesis 听起来是个不错的选择——您尝试过使用它吗?什么不起作用?
  • 我没有通过阅读文档来理解如何做到这一点:p

标签: python python-hypothesis


【解决方案1】:

假设是您的用例的绝佳可能性 - 如果您正确使用它。首先,它为什么起作用:它不是随机的,而是伪随机的。当一个复杂示例的测试失败时,它会降低复杂性,直到找到失败的最小测试用例,并为您提供。然后它还存储失败测试用例的数据库,因此重放旧失败是它尝试的第一件事。

现在,缺点是构建测试用例通常需要很长时间,但好处是您可以真正确定您的代码是健壮的。

我不知道你的代码是什么样的,只是给你一个模型:

from hypothesis import strategies as st
from hypothesis import assume

# In this example, damage and miss chance are based
# on the length of the name of the attack
samus = Character(health=100, attacks=['punch', 'shoot'])
wario = Character(health=70, attacks=['growl', 'punch'])
bowser = Character(health=250, attacks=['growl', 'kidnap_princess'])

st_character = st.sampled_from([samus, wario, bowser])
st_n_rounds = st.integer(min=0, max=10)

@st.composite
def fight_sequence(draw):
  player_one = draw(st_character)
  player_two = draw(st_character)

  # don't test entire fights, just simulate one, record the steps,
  # and check that the end state is what you expect
  actions = [
    dict(type='choose', player_number=1, player=player_one),
    dict(type='choose', player_number=2, player=player_two)
  ]

  # this filters out all test cases where players have the same character
  assume(player_one != player_two)

  n_rounds = draw(st_n_rounds)
  both_alive = True

  def _attack(player, other):
    if not both_alive:
      return

    attack = draw(player.attacks)
    response = draw(st.integers(min=0, max=len(attack)))
    response_type = 'miss' if response == 0 else 'crit' if response == len(attack)) else 'hit'
    actions.push(dict(type='attack', player=player, attack=attack, response=response_type))

    if response_type == 'hit':
       other.health -= len(attack)
    elif response_type == 'crit':
       other.health -= len(attack) * 2

    if other.health <= 0:
      actions.push(dict(type='ko', player=other))

  for _ in range(n_rounds):
    _attack(player_one, player_two)
    _attack(player_two, player_one)
  return actions

然后在您的测试用例中,将播放脚本提供给您的代码并检查结果是否对齐。我希望你能从中获得灵感。

【讨论】:

  • 谢谢!对我来说很难理解,但我正在研究它!
【解决方案2】:

是的,Hypothesis 听起来是个不错的方法。例如:

from unittest import TestCase
from hypothesis import given
from hypothesis.strategies import integers
import hypothesis_random as undertest


class Test(TestCase):
    @given(seed=integers())
    def test_uses_random(self, seed):
        undertest.uses_random(seed)

如果您的函数引发错误,您将获得异常的回溯以及来自假设的伪造示例作为测试的输出触发它,例如

Falsifying example: test_uses_random(
    self=<test_hypothesis_random.Test testMethod=test_uses_random>, seed=-43,
)

Error
Traceback (most recent call last):
...

【讨论】:

    【解决方案3】:

    Hypothesis'st.random_module() strategy 正是为这个用例而设计的。

    【讨论】:

      猜你喜欢
      • 2021-07-28
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 2021-05-15
      • 1970-01-01
      • 2014-02-10
      • 2014-11-10
      • 2016-07-16
      相关资源
      最近更新 更多