【问题标题】:Unittest strategy for a function performing an experiment that includes some randomness执行包含一些随机性的实验的函数的单元测试策略
【发布时间】:2023-01-30 01:48:55
【问题描述】:

我应该采用什么方法为此函数编写单元测试?

请注意:

  • 在每次执行时,此函数将为相同的输入参数生成不同的结果列表(概率非常高)。
  • 执行结束时列表可能为空(如果我们已经达到最大尝试次数但没有找到一个被视为有效的)
  • NUMBER_OF_RESULTSMAX_TRIES> 0MAX_TRIESNUMBER_OF_RESULTS大得多
def perform_experiment(some parameters) -> results[obj]:
  results = []
  for i in range(MAX_TRIES):
    result_to_validate = random_attempt()
    if valid(result_to_validate):
      results.append(result_to_validate)
    if len(results) >= NUMBER_OF_RESULTS:
      break
  return results

我正在考虑通过以下方式在单元测试中实施

  1. 当结果列表不为空时,我可以简单地遍历所有元素并且assert每个元素都是有效的。这不难写。
  2. 如果结果列表为空,我想确保perform_experiment 一直运行到i 达到MAX_TRIES,但是变量i 在函数外部不可访问。

    我不确定如何在单元测试中测试 2.point,我是否应该将其更改为确保要测试的函数至少运行了一定时间,而不是检查 i 是否已达到 @987654333 @ 临界点?使用 seed 是这里唯一的选择吗?如果我们不能使用一个怎么办?或者我们可以从单元测试中完全省略点 2. 吗?

【问题讨论】:

  • 如果你设置种子,你每次都应该得到相同的结果,不是吗?
  • 假设 NUMBER_OF_RESULTS 总是大于 0,如果 i 没有达到 MAX_TRIES 这将是一个巨大的 Python 错误,不确定检查它的意义何在。
  • @keithpjolley 感谢您的评论。确实使用种子是一种方法,但这不能应用于这种情况......
  • 测试应该确保函数在给定一组输入的情况下返回正确的输出,即界面.测试功能的实现有什么意义?

标签: python unit-testing


【解决方案1】:

首先,您需要了解您所处的环境。因此这是您应该测试的功能。但它不是一个真正的函数,其中输入进入输出并在内部进行一些处理:这个函数有一些外部依赖性,这总是令人不安并且使测试更加困难。所以,重构它:

def perform_experiment(maxTries, numberOfResults, some parameters) -> results[obj]:

现在,还有 1 个我们无法控制的依赖项:random_attempt()。这就是为什么你必须嘲笑它​​的原因。让我们将它移到单独的函数中以增加清晰度并使模拟更容易:

def perform_experiment(maxTries, numberOfResults, some parameters) -> results[obj]:
  results = []
  for i in range(maxTries):
    results.append(getValidRandomAttempt())
    if len(results) >= numberOfResults:
      break
  return results

def getValidRandomAttempt():
   result_to_validate = random_attempt()
      if valid(result_to_validate):
        return result_to_validate

但是等等,我们只想收集有效结果,所以我们必须更改列表名称,当没有进行有效尝试时 None 值也有问题:

def perform_experiment(maxTries, numberOfResults, some parameters) -> validResults[obj]:
  validResults = []
  for i in range(maxTries):
    result = getValidRandomAttempt()
    if result != None:
      validResults.append(result)
    if len(validResults) >= numberOfResults:
      break
  return validResults


def getValidRandomAttempt():
   result_to_validate = random_attempt()
      if valid(result_to_validate):
        return result_to_validate

在此阶段是最困难的部分,因为您必须了解在给定 maxTries、numberOfResults 和结果列表值组合的情况下对该函数的期望。 这是一个可能过于详细或可能缺少某些方面的示例。此输入模型需要根据您的需要进行调整。反正:

关于maxTries有2种情况:

A. maxTries == 0
B. maxTries > 0

然后,关于 maxTries 与 numberOfResults 的关系,存在 maxTries > 0 的情况:

A. maxTries > numberOfResults
B. maxTries == numberOfResults
C. maxTries < numberOfResults

最后,它们应该与 validResults 案例结合:

0. maxTries == 0
1. maxTries > numberOfResults, len(validResults) == numberOfResults
2. maxTries > numberOfResults, len(validResults) > numberOfResults
3. maxTries > numberOfResults, len(validResults) < numberOfResults
4. maxTries == numberOfResults, len(validResults) == numberOfResults
5. maxTries == numberOfResults, len(validResults) > numberOfResults
6. maxTries == numberOfResults, len(validResults) < numberOfResults
7. maxTries < numberOfResults, len(validResults) == numberOfResults
8. maxTries < numberOfResults, len(validResults) > numberOfResults
9. maxTries < numberOfResults, len(validResults) < numberOfResults

我们现在可以尝试添加期望并使用 mockito getValidRandomAttempt 适当地创建测试用例:

0. GIVEN
mock_getValidRandomAttempt.side_effect = [None]
WHEN
resultList = perform_experiment(0, 0, some parameters)
THEN
assert len(resultList)==0

1. GIVEN
mock_getValidRandomAttempt.side_effect = [None, 1, 1, 1]
WHEN
resultList = perform_experiment(5, 3, some parameters)
THEN
assert len(resultList)==3

2. GIVEN
mock_getValidRandomAttempt.side_effect = [1, 1, 1, 1]
WHEN
resultList = perform_experiment(5, 3, some parameters)
THEN
assert len(resultList)==3

3. GIVEN
mock_getValidRandomAttempt.side_effect = [1, None, None, None, None]
WHEN
resultList = perform_experiment(5, 3, some parameters)
THEN
assert len(resultList)==1

4. GIVEN
mock_getValidRandomAttempt.side_effect = [1, 1, 1, 1, 1]
WHEN
resultList = perform_experiment(5, 5, some parameters)
THEN
assert len(resultList)==1

5. GIVEN
mock_getValidRandomAttempt.side_effect = [1, 1, 1, 1, 1, 1]
WHEN
resultList = perform_experiment(5, 5, some parameters)
THEN
assert len(resultList)==5

最后但同样重要的是,我鼓励您尽可能不要使用 Python。它的流行程度令人惊讶,同时在测试时有非常严重的缺点:非常不明确的模拟语法和大量的装饰器使得测试几乎完全不可读。

【讨论】:

    猜你喜欢
    • 1970-01-01
    • 1970-01-01
    • 2019-10-17
    • 2018-03-19
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 2011-02-06
    • 1970-01-01
    相关资源
    最近更新 更多