jeudi 11 avril 2019

How to properly test destructors in Python?

I want to test that the destructor of a Python object is called and works properly, i.e. it doesn't raise any error nor returns a bad exit code.

I am stuck in writing the unit test, because it seems that the unit test finishes before the object is deleted. I did not manage to force the object to be deleted before the test returns, and I can't afford to call __del__ directly in my tests for two reasons:

  • That's not exactly what I'm testing

  • This actually ends up in the object being destroyed twice, and in my case the destructor frees some memory so it creates a segmentation fault when called twice.

To illustrate this, I've created a toy example where I have two objects. The first one, MyWrongObject, contains a bug in the destructor and the second one MyCorrectObject is correct. I've written tests for each of these objects. One should succeed while the other should fail.

import unittest
import gc


class MyWrongObject(object):
    def __init__(self):
        self.del_calls = 0

    def __del__(self):
        if self.del_calls:
            # Something really bad can happen here like a segmentation fault
            # because we may free some memory twice
            raise AssertionError("Already destroyed")
        self.del_calls += 1

        # Simulate an error
        raise ValueError("Error while destroying the object")


class MyCorrectObject(object):
    def __init__(self):
        self.del_calls = 0

    def __del__(self):
        if self.del_calls:
            # Something really bad can happen here like a segmentation fault
            # because we may free some memory twice
            raise AssertionError("Already destroyed")
        self.del_calls += 1

        # No error here


class TestDestructor(unittest.TestCase):
    def test_my_correct_object_should_destroy_itself(self):
        # Given
        my_object = MyCorrectObject()

        del my_object
        gc.collect()

    # This test should fail
    def test_my_wrong_object_should_destroy_itself(self):
        # Given
        my_object = MyWrongObject()

        del my_object
        gc.collect()


In the current state, both tests succeed, with the second one prints the ValueError in stdout:

python -m unittest snips_nlu.tests.test_destructor.TestDestructor                                                                                                     1 ↵
.Exception ignored in: <bound method MyWrongObject.__del__ of <snips_nlu.tests.test_destructor.MyWrongObject object at 0x1118bbda0>>
Traceback (most recent call last):
  File "/Users/adrien/dev/snips-nlu/snips_nlu/tests/test_destructor.py", line 17, in __del__
    raise ValueError("Error while destroying the object")
ValueError: Error while destroying the object
.
----------------------------------------------------------------------
Ran 2 tests in 0.044s

OK

I can't manage to write these tests in a consistent way (using the same code in both), and have the first one pass while the second one fails (without any AssertionError in the stdout).

Thanks for your help!

Aucun commentaire:

Enregistrer un commentaire