Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

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

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:

  • Unit Testing Non Public Member Functions
  • (PDF) Incremental Testing of Object-Oriented Class Structures.

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?

like image 268
KFL Avatar asked Jul 24 '12 03:07

KFL


People also ask

How do you create a test class in Python?

First you need to create a test file. Then import the unittest module, define the testing class that inherits from unittest. TestCase, and lastly, write a series of methods to test all the cases of your function's behavior. First, you need to import a unittest and the function you want to test, formatted_name() .


1 Answers

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.

like image 71
BrenBarn Avatar answered Nov 15 '22 13:11

BrenBarn