2012-09-05 10 views
10

Mam kilka testów jednostkowych opartych na klasach uruchamianych w systemie unittest2 Pythona. Używamy Selenium WebDriver, który ma wygodną metodę save_screenshot(). Chciałbym pobrać zrzut ekranu w tearDown() dla każdego niepowodzenia testu, aby skrócić czas debugowania, aby test zakończył się niepowodzeniem.Jak wykonać kod tylko przy niepowodzeniach testowych z python unittest2?

Jednak nie mogę znaleźć sposobu na uruchomienie kodu tylko w przypadku błędów testowych. tearDown() jest wywoływana niezależnie od tego, czy test się powiedzie, i nie chcę zaśmiecać naszego systemu plików setkami zrzutów ekranu przeglądarki dla testów, które się powiodły.

Jak podejdziesz do tego?

Odpowiedz

6

znalazł rozwiązanie - mogę zastąpić failureException:

@property 
def failureException(self): 
    class MyFailureException(AssertionError): 
     def __init__(self_, *args, **kwargs): 
      self.b.save_screenshot('%s.png' % self.id()) 
      return super(MyFailureException, self_).__init__(*args, **kwargs) 
    MyFailureException.__name__ = AssertionError.__name__ 
    return MyFailureException 

Wydaje niewiarygodnie hacky ale wydaje się działać tak daleko.

+0

Wow, co za hack. Klasa wyjątków, owinięta w funkcję, owinięta we własność, wszystko po to, aby uzyskać instancję 'TestCase' w metodzie' __init __() 'wyjątku. Brzydki, ale przynajmniej działa! – kindall

2

Przesłoń fail(), aby wygenerować zrzut ekranu, a następnie zadzwoń pod numer TestCase.fail(self)?

+1

Pomaga to tylko w przypadku, gdy testowa nie powiedzie się, ponieważ ktoś nazwał 'self.fail()'. Inne awarie, takie jak 'self.assertTrue()', ... itp. Omijają to. – craigds

+0

Naprawdę? Nie mogę uwierzyć, że nie mieli tego połączenia 'fail()'. Oczywiście można również obejść wszystkie inne metody (westchnienie). – kindall

+0

Niektóre z nich tak, ale inne nie. Wydaje się trochę szalony;) ​​ – craigds

1

Użyj dekoratora wokół każdego testu.

Najbezpieczniejszym sposobem zapamiętywania nowych testów lub uniknięcia cofania i dekorowania zestawu istniejących testów jest użycie metaklasu do zawijania wszystkich funkcji testowych. Odpowiedź How to wrap every method of a class? zapewnia podstawy tego, czego potrzebujesz.

Prawdopodobnie powinien filtrować funkcje, które są zapakowane w dół do zaledwie testów, np .:

class ScreenshotMetaClass(type): 
    """Wraps all tests with screenshot_on_error""" 
    def __new__(meta, classname, bases, classDict): 
     newClassDict = {} 
     for attributeName, attribute in classDict.items(): 
      if type(attribute) == FunctionType and 'test' in attributeName.lower(): 
       # replace the function with a wrapped version 
       attribute = screenshot_on_error(attribute) 
      newClassDict[attributeName] = attribute 
     return type.__new__(meta, classname, bases, newClassDict) 
2

sys.exc_info() powinno dać zamknąć informację o tym, czy próba nie powiodła się, czy też nie. Więc coś takiego:

def tearDown(self): 
    if sys.exc_info()[0]: 
     path = os.path.join(os.path.dirname(os.path.abspath(__file__)), '../failures', self.driver.browser) 
     if not os.path.exists(path): 
      try: 
       os.makedirs(path) 
      except Exception: 
       # Since this might not be thread safe 
       pass 
     filename = '%s.%s.png' % (self.__class__.__name__, self._testMethodName) 
     file_path = os.path.join(path, filename) 
     self.driver.get_screenshot_as_file(file_path) 
+0

'jeśli sys.exc_info() [0]' nie działa w Pythonie 3. – l0b0

2

Oto podobne podejście do @craigds answer, ale z obsługą katalogów i lepsza kompatybilność z Python 3:

@property 
def failureException(self): 
    class MyFailureException(AssertionError): 
     def __init__(self_, *args, **kwargs): 
      screenshot_dir = 'reports/screenshots' 
      if not os.path.exists(screenshot_dir): 
       os.makedirs(screenshot_dir) 
      self.driver.save_screenshot('{0}/{1}.png'.format(screenshot_dir, self.id())) 
      return super(MyFailureException, self_).__init__(*args, **kwargs) 
    MyFailureException.__name__ = AssertionError.__name__ 
    return MyFailureException 

To było rzeczywiście znaleźć w tym blog.

mam przedłużony go jeszcze bardziej z argparse:

parser.add_argument("-r", "--reports-dir", action="store", dest="dir",  help="Directory to save screenshots.", default="reports")  

więc reż można określić dynamicznie poprzez zmienną systemową lub przekazanie argumentu:

screenshot_dir = os.environ.get('REPORTS_DIR', self.args.dir) + '/screenshots' 

Jest to szczególnie przydatne, jeśli ciebie Dodatkowa owijka do uruchamiania wszystkich skryptów, na przykład klasy bazowej.

Powiązane problemy