【问题标题】:Python: best way to test a single method of a classPython:测试类的单个方法的最佳方法
【发布时间】:2012-07-22 08:29:48
【问题描述】:

我有一个类似如下的课程:

class A:
    def __init__(self, arg1, arg2, arg3):
        self.a=arg1
        self.b=arg2
        self.c=arg3
        # ...
        self.x=do_something(arg1, arg2, arg3)
        self.y=do_something(arg1, arg2, arg3)

        self.m = self.func1(self.x)
        self.n = self.func2(self.y)
        # ...

    def func1(self, arg):
        # do something here

    def func2(self, arg):
        # do something here

如您所见,初始化类需要传入 arg1、arg2 和 arg3。但是,测试 func1 和 func2 并不直接需要这样的输入,而是简单的输入/输出逻辑。

在我的测试中,我当然可以用常规的方式实例化和初始化一个测试对象,然后分别测试func1和func2。但是初始化需要输入arg1 arg2, arg3,这和测试func1 和func2 真的没有关系。

因此,我想单独测试 func1 和 func2,而无需先调用 __init__。所以我有以下两个问题:

  1. 设计此类测试的最佳方法是什么? (最好在 py.test 中)
  2. 我想在不调用 __init__ 的情况下测试 func1 和 func2 。我从here 中读到A.__new__() 可以跳过调用__init__ 但仍然实例化了该类。有没有更好的方法可以在不这样做的情况下实现我的需要?

注意:

关于我在这里提出的问题有 2 个问题:

  1. 是否需要测试单个成员函数?
  2. (用于测试目的)是否有必要在不使用__init__初始化对象的情况下实例化一个类?

对于问题 1,我做了一个快速的谷歌搜索,并找到了一些相关的研究或讨论:

我们最初通过设计一个测试来测试没有父类的基类 单独测试每个成员函数并测试 成员函数之间的交互。

对于问题 2,我不确定。但我认为有必要,如示例代码所示,func1和func2在__init__中被调用。我觉得在尚未使用 __init__ 调用的 A 类对象上测试它们更舒服(因此以前没有调用 func1 和 func2)。

当然,可以只用常规方法 (testobj = A()) 实例化一个 A 类对象,然后对 func1 和 func2 执行单独的测试。但是这样好吗:)?我只是在这里讨论测试这种情况的最佳方法是什么,优缺点是什么。

另一方面,有人可能会争辩说,从设计的角度来看,首先不应该在__init__ 中调用func1 和func2。这是一个合理的设计方案吗?

【问题讨论】:

  • 没有运行__init__的测试方法有什么意义?这就像有人要求你去参加工作面试,而不是先穿裤子。
  • 如果 func1func2 不依赖于被初始化的实例,你确定它们应该是实例方法吗?

标签: python testing pytest


【解决方案1】:

在不实例化类(包括运行__init__)的情况下测试类的方法通常是没有用的,甚至是不可能的。通常,您的类方法将引用类的属性(例如,self.a)。如果您不运行__init__,则这些属性将不存在,因此您的方法将不起作用。 (如果您的方法不依赖于其实例的属性,那么为什么它们是方法而不仅仅是独立函数?)在您的示例中,看起来 func1func2 是初始化过程的一部分,所以它们应该作为其中的一部分进行测试。

理论上可以通过使用__new__ 来“准实例化”该类,然后只添加您需要的成员,例如:

obj = A.__new__(args)
obj.a = "test value"
obj.func1()

但是,这可能不是一个很好的测试方法。一方面,它会导致您复制可能已经存在于初始化代码中的代码,这意味着您的测试更有可能与真实代码不同步。另一方面,您可能必须以这种方式重复许多初始化调用,因为您必须手动重新执行从您的类调用的任何基类 __init__ 方法会执行的操作。

至于如何设计测试,你可以看看the unittest module和/或the nose module。这为您提供了如何设置测试的基础知识。实际放入测试的内容显然取决于您的代码应该做什么。

编辑:您的问题 1 的答案是“肯定是的,但不一定是每一个”。您的问题 2 的答案是“可能不是”。即使在您提供的第一个链接中,关于是否应该测试不属于该类的公共 API 的方法也存在争议。如果你的 func1 和 func2 是纯粹的内部方法,只是初始化的一部分,那么可能不需要在初始化之外单独测试它们。

这是您最后一个问题,即从__init__ 中调用 func1 和 func2 是否合适。正如我在我的 cmets 中反复声明的那样,这取决于 这些函数的作用。如果 func1 和 func2 执行部分初始化(即,为实例做一些“设置”工作),那么从__init__ 调用它们是完全合理的;但在这种情况下,它们应该作为初始化过程的一部分进行测试,并且不需要单独测试它们。如果 func1 和 func2 不是初始化的 部分,那么是的,您应该独立测试它们;但既然如此,为什么他们在__init__

构成实例化类的组成部分的方法应作为测试类实例化的一部分进行测试。不应从__init__ 中调用不构成实例化类的组成部分的方法。

如果 func1 和 func2 是“简单的输入/输出逻辑”并且不需要访问实例,那么它们根本不需要是类的方法;它们可以只是独立的功能。如果要将它们保留在类中,可以将它们标记为静态方法,然后直接在类上调用它们而不实例化它。这是一个例子:

>>> class Foo(object):
...     def __init__(self, num):
...         self.numSquared = self.square(num)
...     
...     @staticmethod
...     def square(num):
...         return num**2
>>> Foo.square(2) # you can test the square "method" this way without instantiating Foo
4
>>> Foo(8).numSquared
64

可以想象你可能有一些怪物类,它需要一个非常复杂的初始化过程。在这种情况下,您可能会发现有必要单独测试该过程的各个部分。然而,如此庞大的初始化序列本身就是一个笨拙设计的警告。

【讨论】:

  • 我不能同意。首先,实例化一个类并不意味着你必须调用__init__。其次,我认为单独测试每个成员方法以发现错误仍然很有用。最后,unittest 和nose 只是测试框架。即使您想建议一般测试的指导,我也会很感激教程或一般指导,而不仅仅是指向测试框架文档的指针,这对任何有能力的人来说都是如此明显。
  • __init__ 从不实例化。实例以self 的形式传入
  • @Kay:如果您需要更具体的帮助,您需要提供更具体的问题。您示例中的方法不执行任何操作,因此测试它们是相当空洞的。至于测试个别方法,你能解释一下你想要发生什么吗?如果这些方法无法访问必要的属性,您希望它们如何工作?
  • @BrenBarn 问题已修改。当然可以调用__init__,然后测试对象的内部状态。但从更精细的角度来看,我想测试初始化​​期间调用的每个方法。这是一个合理的要求吗?有没有办法做到这一点?
  • @Kay:我必须同意 BrenBarn。如果它们不是一个有凝聚力的单元,那么将它们放在一个类中只是糟糕的软件设计。如果您真的想在没有__init__() 代码的情况下实例化该类,只需注释掉您不想执行的行,或者编写一个脚本来为您提供它们。
【解决方案2】:

如果您有选择,我会将您的初始化辅助函数声明为静态方法,然后从测试中调用它们。

如果您有不同的输入/输出值要断言,您可以查看一些parametrizing examples with py.test

如果您的类实例化有些繁重,您可能需要查看dependency injection 并像这样缓存实例:

# content of test_module.py

def pytest_funcarg__a(request):
    return request.cached_setup(lambda: A(...), scope="class")

class TestA:
    def test_basic(self, a):
        assert .... # check properties/non-init functions

这将在每个测试类中重复使用相同的“a”实例。其他可能的范围是“会话”、“功能”或“模块”。您还可以定义一个命令行选项来设置范围,以便快速开发您使用更多的缓存和连续集成您使用更多隔离的资源设置,而无需更改测试源代码。

就个人而言,在过去的 12 年中,我从细粒度的单元测试转向更多功能/集成类型的测试,因为它简化了重构并且似乎更好地利用了我的时间。当然,在发生故障时获得良好的支持和报告至关重要,例如下降到 PDB、简洁的回溯等。对于一些复杂的算法,我仍然编写非常细粒度的单元测试,但是我通常将算法分离成一个非常独立的可测试的东西。

HTH,霍尔格

【讨论】:

    【解决方案3】:

    我同意以前的 cmets 的观点,通常最好通过减少实例化时完成的工作量来避免这个问题,例如通过将func1 等调用移动到应在实例化后调用的configure(self) 方法中。

    如果您有充分的理由在__init__ 中保留对self.func1 等的调用,pytest 中有一种方法可能会有所帮助。

    (1) 把这个放到模块里:

    _called_from_test = False
    

    (2) 将以下内容放入conftest.py中

    import your_module
    
    def pytest_configure(config):
        your_module._called_from_test = True
    

    your_module 使用适当的名称。

    (3) 在运行测试时插入if 语句,提前结束__init__ 的执行,

       if _called_from_test:
          pass
       else:
          self.func1( ....)
    

    然后,您可以单步执行各个函数调用,并在执行过程中对其进行测试。

    同样可以通过将_called_from_test 设为__init__ 的可选参数来实现。

    pytest 文档的Detect if running from within a pytest run 部分提供了更多上下文。

    【讨论】:

      猜你喜欢
      • 2012-05-03
      • 2016-06-10
      • 2018-08-12
      • 2015-10-01
      • 1970-01-01
      • 1970-01-01
      • 2018-07-04
      • 2021-08-17
      • 1970-01-01
      相关资源
      最近更新 更多