Python: best way to test one class method

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, class initialization should be served in arg1, arg2 and arg3. However, testing func1 and func2 does not directly require such inputs, but rather just I / O logic.

In my test, I can, of course, instantiate and initialize the test object in the usual way, and then check func1 and func2 individually. But for initialization, you need to enter arg1 arg2, arg3, which really does not apply to test functions 1 and func2.

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

  • What is the best way to develop such tests? (possibly in py.test)
  • I want to test func1 and func2 without calling __init__ . I read from here that A.__new__() may miss the __init__ call, but still has an instance of the class. Is there a better way to achieve what I need without doing this?

Note:

In my request there were 2 questions:

  • Do I need to test individual member functions?
  • (for testing purposes) Do I need to instantiate a class without initializing the object using __init__ ?

For question 1, I quickly looked at Google and found a suitable study or discussion on this subject:

First, we tested the base classes without parents, by developing a test suite that checks each member function separately, and also checks the interactions between member functions.

To question 2, I'm not sure. But I think it is necessary, as shown in the code example, func1 and func2 are called in __init__ . I feel more comfortable checking them for a Class A object that was not called using __init__ (and therefore no previous calls to func1 and func2).

Of course, you can simply create an object of class A with regular tools (testobj = A ()), and then run an individual test for func1 and func2. But it's good:)? I am just discussing here as the best way to test such a scenario, what are the pros and cons.

On the other hand, it can also be argued that, from a design point of view, func1 and func2 calls should not be introduced in __init__ in the first place. Is this a reasonable design option?

+6
source share
2 answers

It is usually not useful or even impossible to test class methods without instantiating the class (including starting __init__ ). Typically, your class methods will refer to class attributes (e.g. self.a ). If you do not run __init__ , these attributes will not exist, so your methods will not work. (If your methods do not rely on the attributes of their instance, then why are they methods and not just stand-alone functions?) In your example, it looks like func1 and func2 are part of the initialization process, so they should be tested as part of this.

In theory, you can “quasi-instantiate” a class using __new__ , and then add only the elements you need, for example:

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

However, this is probably not a good way to test. Firstly, this leads to duplication of code that supposedly already exists in the initialization code, which means that your tests are more likely to not synchronize with the real code. For another, you may need to duplicate many of the initialization calls this way, since you will have to manually repeat what would otherwise be done using any __init__ methods of the base class called from your class.

Regarding the design of the tests, you can look at the unittest module and / or the nose module . This gives you the basics for setting up tests. What you actually enter in the tests obviously depends on what your code should do.

Edit: The answer to your question 1 is "definitely yes, but not necessarily everyone." The answer to your question 2 is "maybe not." Even with the first link you give, there is a discussion about whether methods that are not part of the public API class should be tested at all. If your func1 and func2 are purely internal methods that are only part of the initialization, then there is probably no need to test them separately from the initialization.

This comes to your last question about the advisability of calling func1 and func2 from __init__ . As I have repeatedly stated in my comments, it depends on what these functions do. If func1 and func2 do part of the initialization (that is, they do some “tuning” of the work for the instance), then it makes sense to call them from __init__ ; but in this case they should be tested as part of the initialization process, and there is no need to test them yourself. If func1 and func2 are not part of the initialization, then yes, you must test them yourself; but in this case, why are they in __init__ ?

The methods that are an integral part of instantiating your class must be tested as part of testing your class instance. Methods that are not an integral part of instantiating your class should not be called from __init__ .

If func1 and func2 are “just I / O logic” and do not require access to the instance, then they should not be class methods at all; they can simply be standalone functions. If you want to store them in a class, you can mark them as staticmethods and then call them directly in the class without creating it. Here is 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 

You can just imagine that you might have a class of monsters that requires an extremely complex initialization process. In this case, you may need to check individual parts of this process individually. However, such a giant init sequence would itself be a warning about cumbersome construction.

+4
source

If you have a choice, I would like to declare your helper initialization functions as staticmethods and just call them from the tests.

If you have different I / O values ​​for approval, you can examine some parameterization options with py.test .

If your class instance is somewhat heavy, you might want to look into the dependency injection and cache the instance as follows:

 # 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 will repeat the use of the same instance of "a" for each test class. Other possible areas are “session”, “function” or “module”. You can also define a command line parameter to set the scope so that you use more caching for faster development, and for Continuous-Integration you use a more isolated setting of resources without the need to change the source code of the test.

Personally, over the past 12 years, I have moved from small-scale unit testing to more functional / integration types of testing because it facilitates the reorganization and seems to make better use of my time in general. Of course, it is important to have good support and reports in case of failures, such as a crash in the PDB, compressed traces, etc. And for some complex algorithms, I still write very fine-grained unit tests, but then I usually separate the algorithm from a very independently verifiable thing.

Hh, holger

+1
source

Source: https://habr.com/ru/post/921171/


All Articles