Python: best way to test a single method of a class

20,434

Solution 1

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.

Solution 2

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

Solution 3

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.

Share:
20,434
KFL
Author by

KFL

Updated on June 16, 2020

Comments

  • KFL
    KFL almost 4 years

    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?

    • wim
      wim almost 12 years
      What is the sense in testing methods without having run __init__? That's like someone asking you to go for a job interview without putting your pants on first.
    • John La Rooy
      John La Rooy almost 12 years
      If func1 and func2 don't depend on the instance being initilised, are you sure they should be instance methods?
  • KFL
    KFL almost 12 years
    I cannot agree. Firstly, instantiating a class doesn't mean you have to call __init__. Secondly, I think it's still useful to test each member method individually for spotting the bugs. Lastly, unittest and nose are only the test frameworks. Even if you want to suggest a guidance to general testing, I would appreciate a tutorial or general guidance, rather than merely a pointer to the test framework's documentation, which is so obvious to any capable person.
  • John La Rooy
    John La Rooy almost 12 years
    __init__ never instantiates. The instance is passed in as self
  • BrenBarn
    BrenBarn almost 12 years
    @Kay: If you want more specific help you need to provide ask a more specific question. The methods in your example don't do anything, so testing them is fairly vacuous. As for testing individual methods, again, can you explain what you want to happen? How do you expect the methods to ever work if they don't have access to necessary attributes?
  • KFL
    KFL almost 12 years
    @BrenBarn Question revised. One can of course call __init__ and then test the internal state of the object. But from a finer guanularity, I would want to test each method invoked during the initialization. Is this a reasonable ask? Is there a way to do this?
  • Joel Cornett
    Joel Cornett almost 12 years
    @Kay: I have to agree with BrenBarn. It's simply poor software design to put things together into a class if they aren't a cohesive unit. If you really want to instantiate the class without the __init__() code, just comment out the lines you don't want executed, or write a script that comments them out for you.
  • BrenBarn
    BrenBarn almost 12 years
    @Kay: If the methods have some purpose other than initialization, then test them normally after creating the instance. If they don't, test them as part of the initialization process. You'll still get tracebacks that show where the error occurred, so it's not like you won't know which method caused the error. Your edit is not that helpful because the methods still do nothing. You would need to provide some use case that explains why you would want to test them separately instead as part of the initialization. So given the information so far, no, it's not a reasonable thing to want to do.
  • KFL
    KFL almost 12 years
    @BrenBarn updated my question again. Hopefully this time it illustrates more clearly what I really meant