Python 如何确保被测试的函数是唯一被调用的函数?

Python 如何确保被测试的函数是唯一被调用的函数?,python,unit-testing,testing,Python,Unit Testing,Testing,如果相关的话,我正在使用Python 据我所知,单元测试是对与单个用例相关的最小部分代码的测试。通常这是一个单一的函数或方法 所以当我测试我的函数时,我不想测试它调用的任何函数。编写测试时,这相当容易:只需关注函数正在执行的逻辑即可 但当我遇到以下情况时: def is_prime(number): if number <= 1: return False for element in range(2, number): if number

如果相关的话,我正在使用Python

据我所知,单元测试是对与单个用例相关的最小部分代码的测试。通常这是一个单一的函数或方法

所以当我测试我的函数时,我不想测试它调用的任何函数。编写测试时,这相当容易:只需关注函数正在执行的逻辑即可

但当我遇到以下情况时:

def is_prime(number):
    if number <= 1:
        return False

    for element in range(2, number):
        if number % element == 0:
            return False

    return True

def extract_primes(lst):
    return [item for item in list if is_prime(item)]
但现在我只是隐式地测试is_prime是否正确工作。当is_prime有一个bug时,它也会导致extract_prime的测试失败,这超过了单元测试,在单元测试中,您希望快速发现单个失败点

因此,在我看来,我不应该打电话是首要的。所以我的结局是这样的:

@unittest.mock.patch("path.to.module.is_prime")
def test_extract_primes(mock_is_prime):
    mock_is_prime.return_value = True
    lst = list(range(10))
    result = extract_primes(lst)
    assert result == lst
    for i in lst:
        assert mock_is_prime.called_with(i)  # not sure if correct method name
但这充其量是令人厌倦和僵化的。我不能使mock_is_prime在同一测试中返回不同的值,除非我创建一个类似以下的构造:

bool_list = [True, False, True]
mock_is_prime.side_effect = lambda x: next(bool_list)
但这会变得更加冗长。随着被测函数中函数调用的数量增加,修补这些函数的大量样板代码也随之增加

当我试图在internet上寻找答案时,我通常会得到Java/C依赖项注入指令,它们告诉您传递正确的所需对象作为参数,然后为您正在测试的方法创建该对象的虚拟对象。或者我会遇到关于是否测试私有方法的争论。没关系,我理解这些事情。但我一辈子都搞不清楚如何处理依赖于其他函数调用的函数。我应该简单地注入这些函数吗

def extract_primes(lst, is_prime_function=is_prime):
    return [item for item in list if is_prime_function(item)]
并通过一个伪素数函数来提取测试中的素数?这看起来很愚蠢,并且在函数签名中添加了奇怪的参数。实际上,这与在所需的工作量上修补功能没有什么不同。它只是从测试中删除一个@patch语句

那么,我是否应该首先修补掉函数呢?如果不是,那么函数在什么时候值得修补呢?只有当它操纵系统的时候?或者当它来自一个完全独立的模块时


我有点迷路了。

一般来说,一个很好的经验法则是,当你达到最佳实践对你不起作用的时候,问题发生的时间比那一点早得多。在您的情况下,您不知道该怎么做,因为有两个公共方法相互调用。这通常表明它们不属于同一类。例如,基于谓词筛选列表的函数实际上与素数无关。这不应该是此类提供的功能。如果这是您经常做的事情,那么您可以创建一个具有筛选方法的list utils类。我认为测试这两种方法,is_prime和filter_list,应该很容易


一般来说,类的设计应该为用户提供一组功能。类不应该是它自己的用户。这会产生很多问题,其中最重要的是你遇到的问题。当然,这条规则也有例外,最好的设计应该根据具体情况来判断,但我发现这是一条很好的经验法则。

一般来说,当你达到最佳实践对你不起作用的程度时,问题就出现得比那一点早得多,这是一条很好的经验法则。在您的情况下,您不知道该怎么做,因为有两个公共方法相互调用。这通常表明它们不属于同一类。例如,基于谓词筛选列表的函数实际上与素数无关。这不应该是此类提供的功能。如果这是您经常做的事情,那么您可以创建一个具有筛选方法的list utils类。我认为测试这两种方法,is_prime和filter_list,应该很容易


一般来说,类的设计应该为用户提供一组功能。类不应该是它自己的用户。这会产生很多问题,其中最重要的是你遇到的问题。当然,这条规则也有例外,最好的设计应该根据具体情况来判断,但我发现这是一条很好的经验法则。

不幸的是,除了编写真正结构良好的代码之外,没有简单的答案,这当然很难。我将通过逐条检查你的问题来帮助阐明这个问题

据我所知,单元测试是对与单个用例相关的最小部分代码的测试。通常这是一个单一的函数或方法

这大体上是正确的,但主要目标是在 该准则的介面;也就是说,给定特定的输入,您是否获得了预期的输出。您不想关心这段代码是如何实现您想要的功能的。如果您这样做,您就开始编写测试

我不想测试[我的函数]正在调用的任何函数

不完全是,;您不想关心被测试的代码调用了什么函数。就您的测试而言,您不在乎它是否是输入到输出的硬编码映射,或者它是否出去进行谷歌搜索*并解析第一个结果,只要行为符合您的预期


请注意,您的is_prime函数也依赖于其他函数的行为-不幸的是,除了编写真正结构良好的代码之外,没有简单的答案,当然,这很难。我将通过逐条检查你的问题来帮助阐明这个问题

据我所知,单元测试是对与单个用例相关的最小部分代码的测试。通常这是一个单一的函数或方法

这大体上是正确的,但主要目标是测试代码的接口;也就是说,给定特定的输入,您是否获得了预期的输出。您不想关心这段代码是如何实现您想要的功能的。如果您这样做,您就开始编写测试

我不想测试[我的函数]正在调用的任何函数

不完全是,;您不想关心被测试的代码调用了什么函数。就您的测试而言,您不在乎它是否是输入到输出的硬编码映射,或者它是否出去进行谷歌搜索*并解析第一个结果,只要行为符合您的预期


请注意,您的is_prime函数也取决于其他函数的行为-一般来说:它完全取决于您的设计,但您最初的尝试似乎是合理的

假设is_prime和extract_prime都是类/模块公共接口的一部分,那么它们都应该进行测试。在这一点上,您希望将extract_primes作为一个黑盒进行测试,也就是说,只需确保它履行其契约:给它一个列表,它就会返回primes。它在内部使用is_prime的事实是您的单元测试代码不应该关注的实现细节

单元测试的要点不一定是只有最早的失败才会触发,当然,如果你能管理好它,那就太好了;如果你在上游破坏了一些足够高的东西,很多依赖的东西很可能会失败。如果is_素数被破坏,那么看到extract_素数的测试失败也是完全正确的,但是仅仅查看测试失败列表就足以立即确定根本原因

此外,通过这种方式,您可以在不需要更改测试代码的情况下进行以下操作:尝试其他基本测试函数、用生成器表达式替换列表理解或其他重构


模拟应该为外部依赖项保留;修补任何函数发出的每个调用都是荒谬的冗长,将测试代码与实现联系得过于紧密,并不能真正为您带来任何好处

一般来说:这完全取决于您的设计,但您最初的尝试似乎是合理的

假设is_prime和extract_prime都是类/模块公共接口的一部分,那么它们都应该进行测试。在这一点上,您希望将extract_primes作为一个黑盒进行测试,也就是说,只需确保它履行其契约:给它一个列表,它就会返回primes。它在内部使用is_prime的事实是您的单元测试代码不应该关注的实现细节

单元测试的要点不一定是只有最早的失败才会触发,当然,如果你能管理好它,那就太好了;如果你在上游破坏了一些足够高的东西,很多依赖的东西很可能会失败。如果is_素数被破坏,那么看到extract_素数的测试失败也是完全正确的,但是仅仅查看测试失败列表就足以立即确定根本原因

此外,通过这种方式,您可以在不需要更改测试代码的情况下进行以下操作:尝试其他基本测试函数、用生成器表达式替换列表理解或其他重构


模拟应该为外部依赖项保留;修补任何函数发出的每个调用都是荒谬的冗长,将测试代码与实现联系得过于紧密,并不能真正为您带来任何好处

这可能更适合。另外:这并不能回答您的问题,但是-extract\u primes=partialfilter是_prime@tzaman我很清楚。我只需要示例代码。在一个问题中快速解释实际代码要困难得多。每个人都知道什么是素数;所以我将用一个关于素数的问题。这可能更适合。另外:这并不能回答你的问题,但是-提取素数
=部分过滤器,是_prime@tzaman我很清楚。我只需要示例代码。在一个问题中快速解释实际代码要困难得多。每个人都知道什么是素数;所以我将使用一个关于素数的问题。当然,一个类可以是它自己的用户。Java总是通过getter和setter来实现这一点。并考虑一个类棋盘,它具有两个公有的方法,并且都是合法的。do_move方法调用是合法的,因为董事会不能执行非法移动。但在上面的例子中我甚至没有使用类;只有过程代码。同样,getter和setter几乎总是糟糕设计的标志。“这是一种几乎不那么冒犯的公开宣布会员的方式。@卡门比亚卡贝克虽然我同意能手和二传手几乎总是坏设计的标志,但听起来有点刺耳,没有必要称人们为疯子。广义地说,Benjy是对的,例如,可能没有必要向调用者公开像is_move_legal这样的方法。你的“尝试/捕捉”示例有些做作;打电话的人不需要撤销董事会,这是董事会的工作。在任何情况下,数据结构设计决策都与这个问题无关。当您遇到测试困难时,您可能希望重新检查早期的决策,这一点是正确的。@Carmenbianabaker如果您想就此发表问题,我很乐意与您讨论棋盘设计决策。但是,就这个问题而言,可以说您可以成功地实现ChessBoard类,而无需向调用方公开is_move_legal。Benjy和我的建议是,这样的设计可能更容易测试。既然你要来这里寻求关于如何测试东西的帮助,也许你应该认真对待这个建议。这里唯一疯狂的人就是你。请停止。当您需要测试私有方法时,此方法有95%的可能性应该是私有合作者的公共方法。也许is_move_legal是棋盘类的一个公共方法,用作棋盘的合作者。单一责任原则规则。。。当一个类做了太多的工作时,将它拆分,也许你的设计会得到改进,变得更易于测试。当然,一个类可以是它自己的用户。Java总是通过getter和setter来实现这一点。并考虑一个类棋盘,它具有两个公有的方法,并且都是合法的。do_move方法调用是合法的,因为董事会不能执行非法移动。但在上面的例子中我甚至没有使用类;只有过程代码。同样,getter和setter几乎总是糟糕设计的标志。“这是一种几乎不那么冒犯的公开宣布会员的方式。@卡门比亚卡贝克虽然我同意能手和二传手几乎总是坏设计的标志,但听起来有点刺耳,没有必要称人们为疯子。广义地说,Benjy是对的,例如,可能没有必要向调用者公开像is_move_legal这样的方法。你的“尝试/捕捉”示例有些做作;打电话的人不需要撤销董事会,这是董事会的工作。在任何情况下,数据结构设计决策都与这个问题无关。当您遇到测试困难时,您可能希望重新检查早期的决策,这一点是正确的。@Carmenbianabaker如果您想就此发表问题,我很乐意与您讨论棋盘设计决策。但是,就这个问题而言,可以说您可以成功地实现ChessBoard类,而无需向调用方公开is_move_legal。Benjy和我的建议是,这样的设计可能更容易测试。既然你要来这里寻求关于如何测试东西的帮助,也许你应该认真对待这个建议。这里唯一疯狂的人就是你。请停止。当您需要测试私有方法时,此方法有95%的可能性应该是私有合作者的公共方法。也许is_move_legal是棋盘类的一个公共方法,用作棋盘的合作者。单一责任原则规则。。。当一个类做了太多的工作时,它会被拆分,也许你的设计会得到改进,变得更易于测试。我有点理解,但我仍然有点困惑。这尤其令人困惑,因为很多单元测试指令都是面向对象的,Python代码很容易混合各种样式。相对清楚地描述了这个问题。如果有一个函数调用另外两个函数,那么测试第一个函数就变得非常困难。因为所有的函数都是纯函数,所以除了实时补丁之外,没有依赖注入可以完成。@CarmenbianCabaker我想说的核心点是,除非必须,否则不要担心你的函数是否调用其他函数。如果需要公开is_prime和extract_primes函数,那么应该对这两个函数都进行测试。如果您发现自己编写了大量冗余测试,
更改检测器,或片状测试,这是一个强烈的建议,您应该重新审视您的设计,这样您就可以提供这两种行为,而不会使它们相互依赖。对于您提供的示例,定义一个通用的过滤器方法并进行更干净的测试。我有点理解,但我仍然有点困惑。这尤其令人困惑,因为很多单元测试指令都是面向对象的,Python代码很容易混合各种样式。相对清楚地描述了这个问题。如果有一个函数调用另外两个函数,那么测试第一个函数就变得非常困难。因为所有的函数都是纯函数,所以除了实时补丁之外,没有依赖注入可以完成。@CarmenbianCabaker我想说的核心点是,除非必须,否则不要担心你的函数是否调用其他函数。如果需要公开is_prime和extract_primes函数,那么应该对这两个函数都进行测试。如果您发现自己编写了大量冗余测试、变更检测器或不稳定的测试,这是一个强烈的建议,您应该重新审视您的设计,这样您就可以提供这两种行为,而不会让它们相互依赖。对于您提供的示例,定义一个通用的过滤器方法并进行更干净的测试。
def extract_primes(lst, is_prime_function=is_prime):
    return [item for item in list if is_prime_function(item)]