15

I have a class like the following:

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

As you can see, initializing the class needs to feed in arg1, arg2, and arg3. However, testing func1 and func2 does not directly require such inputs, but rather, it's simply an input/output logic.

In my test, I can of course instantiate and initialize a test object in the regular way, and then test func1 and func2 individually. But the initialization requires input of arg1 arg2, arg3, which is really not relevant to test func1 and func2.

Therefore, I want to test func1 and func2 individually, without first calling __init__. So I have the following 2 questions:

  1. What's the best way of designing such tests? (perferably, in py.test)
  2. I want to test func1 and func2 without invoking __init__. I read from here that A.__new__() can skip invoking __init__ but still having the class instantiated. Is there a better way to achieve what I need without doing this?

NOTE:

There have been 2 questions regarding my ask here:

  1. Is it necessary to test individual member functions?
  2. (for testing purpose) Is it necessary to instantiating a class without initializing the object with __init__?

For question 1, I did a quick google search and find some relevant study or discussion on this:

We initially test base classes having no parents by designing a test suite that tests each member function individually and also tests the interactions among member functions.

For question 2, I'm not sure. But I think it is necessary, as shown in the sample code, func1 and func2 are called in __init__. I feel more comfortable testing them on an class A object that hasn't been called with __init__ (and therefore no previous calls to func1 and func2).

Of course, one could just instantiate a class A object with regular means (testobj = A()) and then perform individual test on func1 and func2. But is it good:)? I'm just discussing here as what's the best way to test such scenario, what's the pros and cons.

On the other hand, one might also argue that from design perspective one should NOT put calls to func1 and func2 in __init__ in the first place. Is this a reasonable design option?

4

3 回答 3

11

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

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

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

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

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

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

这就涉及到最后一个问题,即从 inside 调用 func1 和 func2 是否合适__init__。正如我在评论中反复声明的那样,这取决于这些功能的作用。如果 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

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

于 2012-07-24T03:45:14.317 回答
3

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

如果您有不同的输入/输出值要断言,您可以使用 py.test 查看一些参数化示例

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

# 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,霍尔格

于 2012-07-25T08:05:12.147 回答
1

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

如果您有充分的理由保留对self.func1etc in的调用__init__,那么有一种方法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 运行部分中运行,则在检测中给出了更多上下文pytest

于 2020-06-15T16:33:25.430 回答