Unittesty i przeładowywanie modułów Pythona

Natknąłem się ostatnio na problem podczas pisania testów do kodu który się zmienia za każdym przypadkiem testowym. Przykład takiego przypadku testowego:

class TestCase1(unittest.TestSuite):
    def setUp(self):
        crate_test_env(a=10, c=6)
    
    def test_method1(self):
        import mymodule
       
        self.assertEqual(mymodule.a, 10)
        self.assertEqual(mymodule.c, 6)
       
    def test_method2(self):
        import mymodule
       
        self.assertEqual(mymodule.a, 10)
        self.assertEqual(mymodule.c, 6)
   
class TestCase2(unittest.TestSuite):
    def setUp(self):     
        crate_test_env(a=2, b=9)
   
    def testMet1(self):
        import mymodule
        self.assertEqual(mymodule.a, 2)
        self.assertEqual(mymodule.b, 9)

W metodzie setUp każdego przypadku testowego tworze plik mymodule.py do którego wpisuje zmienne a, b, lub c. Problem jest tutaj taki że przy pierwszym utworzeniu pliku mymodule.py i jego zaimportowaniu moduł jest zapisany w pamięci i w kolejnym utworzeniu modułu i jego zaimportowaniu jest widoczny nie moduł który został utworzony w aktualnym przypadku testowym, tylko ten który został zaimportowany jako pierwszy. Przeładowanie modułu za pomocą reload(modul) nic nie daje. Znalazłem w internecie kod który przekompilowuje dany modół jednak nie zdało to egzaminu, ciągle widziany był pierwszy załadowany moduł. Potem znalazłem klase RollbackImporter na stronie projektu pyunit która podczas inicjalizacji zapamiętuje wszystkie moduły zaimportowane w danym momencie i na końcu usuwa z pamięci wszystkie moduły które zostały zaimportowane po zrobieniu kopii sys.modules.

Jednak użycie tej klasy w metodzie setUp przypadku testowego nie chciało działać. Długo zastanawiałem się jak odizolować uruchomienie pojedynczego testu od zaimportowanych modułów w danym momencie. Wymyśliłem że musiałbym napisać własny testrunner, zacząłem przeglądać kod źródłowy modułu unittest. Gdy testrunner uruchamia nasze testy:

unittest.TextTestRunner(verbosity=2).run(nasze_testy)

Wywoływana jest klasa TestSuite która posiada metodę run która uruchamia po kolei wszystkie przekazane testy. Wystarczy więc nadpisać metodę __call__ klasy TestSuite i tam wstawić przed wykonaniem każdego testu kod z modułu pyunit. Przykład klasy która dziedziczy po unittest.TestSuite i nadpisuje metodę __call__:

class MyTestSuite(unittest.TestSuite):
    def __call__(self, result):
        rollbackImporter = None
        for test in self._tests:
            if rollbackImporter:
                rollbackImporter.rollbackImports()
            rollbackImporter = RollbackImporter()
          
            if result.shouldStop:
                break
            test.run(result)
        return result

Nadpisana metoda __call__ różni się od oryginału tylko dodaniem kodu:

rollbackImporter = None
for test in self._tests
    if rollbackImporter:
        rollbackImporter.rollbackImports()
    rollbackImporter = RollbackImporter()

 


Comments turned off