Python: best way to test a single method of a class
I agree with previous comments that it is generally better to avoid this problem by reducing the amount of work done at instantiation, e.g. by moving func1
etc calls into aconfigure(self)
method which should be called after instantiation.
If you have strong reasons for keeping calls to self.func1
etc in __init__
, there is an approach in pytest
which might help.
(1) Put this in the module:
_called_from_test = False
(2) Put the following in conftest.py
import your_module
def pytest_configure(config):
your_module._called_from_test = True
with the appropriate name for your_module
.
(3) Insert an if
statement to end the execution of __init__
early when you are running tests,
if _called_from_test:
pass
else:
self.func1( ....)
You can then step through the individual function calls, testing them as you go.
The same could be achieved by making _called_from_test
an optional argument of __init__
.
More context is given in the Detect if running from within a pytest run section of pytest
documentation.
If you have a choice, i'd go for declaring your initialization helper functions as staticmethods and just call them from tests.
If you have different input/output values to assert on, you could look into some parametrizing examples with py.test.
If your class instantiation is somewhat heavy you might want to look into dependency injection and cache the instance like this:
# 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
This would re-use the same "a" instance across each test class. Other possible scopes are "session", "function" or "module". You can also define a command line option to set the scope so that for quick development you use more caching and for Continous-Integration you use more isolated resource setup, without the need to change the test source code.
Personally, in the last 12 years i went from fine-grained unit-testing to more functional/integration types of testing because it eases refactoring and seemed to make better use of my time overall. It's of course crucial to have good support and reports when failures occur, like dropping to PDB, concise tracebacks etc. And for some intricate algorithms i still write very fine-grained unit-tests but then i usually separate the algorithm out into a very independently testable thing.
HTH, holger
It is not usually useful or even possible to test methods of a class without instantiating the class (including running __init__
). Typically your class methods will refer to attributes of the class (e.g., self.a
). If you don't run __init__
, those attributes won't exist, so your methods won't work. (If your methods don't rely on the attributes of their instance, then why are they methods and not just standalone functions?) In your example, it looks like func1
and func2
are part of the initialization process, so they should be tested as part of that.
In theory it is possible to "quasi-instantiate" the class by using __new__
and then adding just the members that you need, e.g.:
obj = A.__new__(args)
obj.a = "test value"
obj.func1()
However, this is probably not a very good way to do tests. For one thing, it results in you duplicating code that presumably already exists in the initialization code, which means your tests are more likely to get out of sync with the real code. For another, you may have to duplicate many initialization calls this way, since you'll have to manually re-do what would otherwise be done by any base-class __init__
methods called from your class.
As for how to design tests, you can take a look at the unittest module and/or the nose module. That gives you the basics of how to set up tests. What to actually put in the tests obviously depends on what your code is supposed to do.
Edit: The answer to your question 1 is "definitely yes, but not necessarily every single one". The answer to your question 2 is "probably not". Even at the first link you give, there is debate about whether methods that are not part of the class's public API should be tested at all. If your func1 and func2 are purely internal methods that are just part of the initialization, then there is probably no need to test them separately from the initialization.
This gets to your last question about whether it's appropriate to call func1 and func2 from within __init__
. As I've stated repeatedly in my comments, it depends on what these functions do. If func1 and func2 perform part of the initialization (i.e., do some "setting-up" work for the instance), then it's perfectly reasonable to call them from __init__
; but in that case they should be tested as part of the initialization process, and there is no need to test them independently. If func1 and func2 are not part of the initialization, then yes, you should test them independently; but in that case, why are they in __init__
?
Methods that form an integral part of instantiating your class should be tested as part of testing the instantiation of your class. Methods that do not form an integral part of instantiating your class should not be called from within __init__
.
If func1 and func2 are "simply an input/output logic" and do not require access to the instance, then they don't need to be methods of the class at all; they can just be standalone functions. If you want to keep them in the class you can mark them as staticmethods and then call them on the class directly without instantiating it. Here's an example:
>>> 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
It is just imaginable that you might have some monster class which requires a hugely complex initialization process. In such a case, you might find it necessary to test parts of that process individually. However, such a giant init sequence would itself be a warning of an unwieldy designm.