2015-01-12 12 views
6

Myślałem o przejściu z nosa, aby zachowywać się podczas testów (mocha/chai itd. Mnie zepsuło). Jak na razie dobrze, ale ja nie potrafię wymyślić żadnego sposobu testowania wyjątki oprócz:Obsługa wyjątków w środowisku testowym Python Behavior Testowanie

@then("It throws a KeyError exception") 
def step_impl(context): 
try: 
    konfigure.load_env_mapping("baz", context.configs) 
except KeyError, e: 
    assert (e.message == "No baz configuration found") 

Z nosa mogę opisywanie test z

@raises(KeyError) 

nie mogę znaleźć niczego tak jak w zachowaniu (nie w źródle, nie w przykładach, nie tutaj). Na pewno byłoby wspaniale, gdyby można było określić wyjątki, które mogą być rzucone w kontury scenariusza.

Ktoś był na tej ścieżce?

+0

Wydaje mi się, że zapewnienie, że kod zgłasza pewne wyjątki w pewnych scenariuszach, jest dość standardową sprawą do przetestowania. Dobrze nadaje się również do pokazywania kodu klienta zachowań, jakich można się spodziewać. Kiedy testuję to, nie testuję na porażkę! W każdym razie jest to dość standardowa cecha większości frameworków testowych. –

Odpowiedz

6

Sam jestem całkiem nowy dla BDD, ale ogólnie rzecz biorąc, pomysł polegałby na tym, że testy dokumentują to, co zachowuje się klient, a nie etapowe implementacje. Więc będę oczekiwać kanoniczny sposób przetestować to byłoby coś takiego:

When I try to load config baz 
Then it throws a KeyError with message "No baz configuration found" 

ze stopniami zdefiniowane następująco:

@when('...') 
def step(context): 
    try: 
     # do some loading here 
     context.exc = None 
    except Exception, e: 
     context.exc = e 

@then('it throws a {type} with message "{msg}"') 
def step(context, type, msg): 
    assert isinstance(context.exc, eval(type)), "Invalid exception - expected " + type 
    assert context.exc.message == msg, "Invalid message - expected " + msg 

Jeśli to wspólny wzór, można po prostu napisać własny dekorator:

def catch_all(func): 
    def wrapper(context, *args, **kwargs): 
     try: 
      func(context, *args, **kwargs) 
      context.exc = None 
     except Exception, e: 
      context.exc = e 

    return wrapper 

@when('... ...') 
@catch_all 
def step(context): 
    # do some loading here - same as before 
+0

Przetworzył dziękczynienie! Mój mózg był zardzewiały zeszłej nocy. –

+0

Wound up abandoning behave. Posiadanie oddzielnego pliku specyfikacji ogórka i konwencji, która zmusza prawie wszystko do wykonania testu wieloetapowego, to po prostu za dużo pracy. Odkryłem, że niszczy mój rozwój. Z powrotem do nosa! –

0

Zachowanie nie jest w biznesie asercji matcherów. Dlatego nie zapewnia na to rozwiązania. Istnieje już wystarczająco dużo pakietów Pythona, które rozwiązują ten problem.

ZOBACZ TAKŻE:behave.example: Select an assertion matcher library

+0

Hi @jenisys; dzięki za zachowanie BTW Myślę, że problem tutaj nie jest tylko matchers. Nie jest jasne, kiedy uruchomisz krok "Kiedy", na co powinieneś pasować. na przykład jeśli dopasujesz się do wyjątku, ale go nie testujesz, możliwe jest zamaskowanie innego wyjątku później. Jeśli otrzymasz wyjątek w kroku "kiedy", wówczas powinien on propagować jako błąd testowy. – Michael

2

Ta próba podejścia/catch przez Barry działa, ale widzę pewne problemy:

  • dodanie try/z wyjątkiem twoich kroków oznacza, że ​​błędy zostaną ukryte.
  • Dodawanie dodatkowego dekoratora jest nieeleganckie. Chciałbym moje dekorator być modyfikowany @where

Moja sugestia jest

  • oddać oczekują wyjątku przed upadającego rachunku
  • w try/catch, podnieść jeśli błąd nie spodziewano
  • w after_scenario, podnieś błąd, jeśli nie znaleziono oczekiwanego błędu.
  • użyciu zmodyfikowanego podane/kiedy/potem wszędzie

Kod:

def given(regexp): 
     return _wrapped_step(behave.given, regexp) #pylint: disable=no-member 

    def then(regexp): 
     return _wrapped_step(behave.then, regexp) #pylint: disable=no-member 

    def when(regexp): 
     return _wrapped_step(behave.when, regexp) #pylint: disable=no-member 


    def _wrapped_step(step_function, regexp): 
     def wrapper(func): 
      """ 
      This corresponds to, for step_function=given 

      @given(regexp) 
      @accept_expected_exception 
      def a_given_step_function(context, ... 
      """ 
      return step_function(regexp)(_accept_expected_exception(func)) 
     return wrapper 


    def _accept_expected_exception(func): 
     """ 
     If an error is expected, check if it matches the error. 
     Otherwise raise it again. 
     """ 
     def wrapper(context, *args, **kwargs): 
      try: 
       func(context, *args, **kwargs) 
      except Exception, e: #pylint: disable=W0703 
       expected_fail = context.expected_fail 
       # Reset expected fail, only try matching once. 
       context.expected_fail = None 
       if expected_fail: 
        expected_fail.assert_exception(e) 
       else: 
        raise 
     return wrapper 


    class ErrorExpected(object): 
     def __init__(self, message): 
      self.message = message 

     def get_message_from_exception(self, exception): 
      return str(exception) 

     def assert_exception(self, exception): 
      actual_msg = self.get_message_from_exception(exception) 
      assert self.message == actual_msg, self.failmessage(exception) 
     def failmessage(self, exception): 
      msg = "Not getting expected error: {0}\nInstead got{1}" 
      msg = msg.format(self.message, self.get_message_from_exception(exception)) 
      return msg 


    @given('the next step shall fail with') 
    def expect_fail(context): 
     if context.expected_fail: 
      msg = 'Already expecting failure:\n {0}'.format(context.expected_fail.message) 
      context.expected_fail = None 
      util.show_gherkin_error(msg) 
     context.expected_fail = ErrorExpected(context.text) 

mogę zaimportować zmodyfikowany podane/wtedy/gdy zamiast zachowywać i dodać do mojego environment.py inicjującego kontekst.Oczekuje nie wcześniej scenariusza i sprawdzenie go po:

def after_scenario(context, scenario): 
     if context.expected_fail: 
      msg = "Expected failure not found: %s" % (context.expected_fail.message) 
      util.show_gherkin_error(msg) 
1

Try/z wyjątkiem podejścia ty show jest rzeczywiście całkowicie poprawna, ponieważ pokazuje drogę, która faktycznie korzysta kod w prawdziwym życiu. Jest jednak powód, dla którego nie podoba ci się to całkowicie. Prowadzi to do problemów z brzydkich rzeczy, jak następuje:

Scenario: correct password accepted 
Given that I have a correct password 
When I attempt to log in 
Then I should get a prompt 

Scenario: correct password accepted 
Given that I have a correct password 
When I attempt to log in 
Then I should get an exception 

Jeśli piszę definicję etapie bez spróbuj/z wyjątkiem potem drugi scenariusz zawiedzie. Jeśli napiszę to przy użyciu funkcji try/except, wówczas pierwszy scenariusz może ukryć wyjątek, szczególnie jeśli wyjątek ma miejsce po tym, jak prompt został już wydrukowany.

Zamiast te scenariusze powinny IMHO być napisane jako coś jak

Scenario: correct password accepted 
Given that I have a correct password 
When I log in 
Then I should get a prompt 

Scenario: correct password accepted 
Given that I have a correct password 
When I try to log in 
Then I should get an exception 

„Ja zalogować” krok nie powinien używać spróbować; "Staram się zalogować" pasuje starannie, aby spróbować i zdradza, że ​​może nie być sukcesu.

Następnie pojawia się pytanie o ponowne użycie kodu między dwoma prawie, ale nie do końca identycznymi krokami. Prawdopodobnie nie chcemy mieć dwóch funkcji, które jednocześnie się logują. Oprócz zwykłej wspólnej funkcji, którą wywołujesz, możesz również zrobić coś podobnego pod koniec pliku kroków.

@when(u'{who} try to {what}') 
def step_impl(context): 
    try: 
     context.exception=None 
    except Exception as e: 
     context.exception=e 

To automatycznie konwertować wszystkie etapy zawierające słowo „próbują” w krokach o tej samej nazwie, ale z próbować usunięte, a następnie zabezpieczyć je próbie/z wyjątkiem.

Jest kilka pytań dotyczących tego, kiedy należy zajmować się wyjątkami w BDD, ponieważ nie są one widoczne dla użytkownika. To nie jest część odpowiedzi na to pytanie, więc umieściłem je w separate posting.