📜 ⬆️ ⬇️

Unittest and abstract tests

Instead of intro


Unittest is probably the most famous framework for writing tests in Python. It is very easy to learn and easy to start using in your project. But nothing is perfect. In this post I want to talk about one opportunity that I personally (I think, not just one) lack in unittest.

A bit about Unit tests


Before discussing (and condemning) the test framework, I consider it necessary to talk a bit about testing in general. When I first heard the phrase “unit testing,” I thought that these were the duties of some quality control service that checks software modules for compliance. What was my surprise when I learned that programmers should write these same tests. The first time I did not write tests ... in general. Nothing will replace those feelings when you wake up in the morning and read from the user “The program does not work. With this urgent need to do something. " At first it seemed to me that this is a completely normal process, until I wrote the first unit test, which found an error. Unit tests generally seem useless exactly until they detect a problem. Now I just can not commit a new feature without writing a test for it.
But tests serve not only to verify the correctness of writing code. Here is a list of functions that, in my opinion, perform tests:


In a sense, tests repeat the structure of the program. Are program design principles applicable to tests? I believe that yes - this is the same program, even if it checks another.

Description of the problem


At some point, I got the idea to write an abstract test. What do I mean by this? This is such a test, which itself is not executed, but declares methods that depend on the parameters defined in the heirs. And then I discovered that I can not do this humanly in unittest. Here is an example:
')
class SerializerChecker(TestCase): model = None serializer = None def test_fields_creation(self): props = TestObjectFactory.get_properties_for_model(self.model) obj = TestObjectFactory.create_test_object_for_model(self.model) serialized = self.serializer(obj) self.check_dict(serialized.data, props) 

I think, even without knowing the implementation of TestObjectFactory and the check_dict method, it is clear that props is a property dictionary for an object, obj is an object for which we are testing a serializer. check_dict checks recursively for matching dictionaries. I think many who are familiar with c unittest will immediately say that this test does not meet my definition of the abstract. Why? Because the test_fields_creation method will run from this class, which we absolutely do not need. After some searching for information, I came to the conclusion that the most adequate option is not to inherit SerializerChecker from TestCase, but to implement the inheritors something like this:

 class VehicleSerializerTest(SerializerChecker, RecursiveTestCase): model = Vehicle serializer = VehicleSerialize 

RecursiveTestCase is a descendant of TestCase that implements the check_dict method.

This decision is ugly at once from several positions:


image

The error that gives the development environment

It may seem that you can simply add a stub for check_dict and all problems are resolved:

 class SerializerChecker: model = None serializer = None def check_dict(self, data, props): raise NotImplementedError def test_fields_creation(self): props = TestObjectFactory.get_properties_for_model(self.model) obj = TestObjectFactory.create_test_object_for_model(self.model) serialized = self.serializer(obj) self.check_dict(serialized.data, props) 

But this is not a complete solution to the problem:


Summarizing


Unittest does not provide the implied opportunity to "disable" the class inherited from TestCase. I would be very happy if such a function were added to the framework. And how do you solve this problem?

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


All Articles