2015-01-12 9 views
6

Ho pensato di passare dal naso al comportamento per i test (mocha/chai ecc mi hanno rovinato). Fin qui tutto bene, ma io non riesco a capire un modo di test per le eccezioni oltre:Gestione delle eccezioni in Python Behave Testing framework

@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") 

Con il naso posso annotare un test con

@raises(KeyError) 

non riesco a trovare nulla come questo nel comportamento (non nella fonte, non negli esempi, non qui). Sarebbe certamente grandioso poter specificare eccezioni che potrebbero essere gettate nei contorni dello scenario.

Qualcuno è stato su questa strada?

+0

Mi sembra che garantire che il codice generi certe eccezioni in determinati scenari è una cosa abbastanza standard da testare. Ottimo anche per mostrare al codice client i comportamenti che può aspettarsi. Quando provo per quello, non faccio i test per fallire! In ogni caso è una caratteristica piuttosto standard della maggior parte dei framework di test. –

risposta

6

Sono abbastanza nuovo per BDD, ma in generale, l'idea sarebbe che i test documentano ciò che si può aspettare dal client, non le implementazioni del passo. Quindi mi aspetto il modo canonico di testare questo sarebbe qualcosa di simile:

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

Con passi definiti come:

@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 

Se questo è un modello comune, si può solo scrivere il proprio decoratore:

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

Ha lavorato un piacere grazie! Il mio cervello era tutto arrugginito la scorsa notte. –

+0

Wound up che abbandona il comportamento. Avere il file spec cetriolo separato e la convenzione che forza quasi tutto per essere un test a più fasi è troppo lavoro. L'ho trovato distrutto dal mio flusso di sviluppo. Torna al naso! –

0

Behave non fa parte del business di assertion matcher. Pertanto, non fornisce una soluzione per questo. Ci sono già abbastanza pacchetti Python che risolvono questo problema.

Vedi anche:behave.example: Select an assertion matcher library

+0

Ciao @ jenisys; grazie per il comportamento BTW Penso che il problema qui non sia solo per gli abbinamenti. Non è chiaro quando esegui il tuo passaggio "Quando" su cosa dovresti abbinare. per esempio. se si esegue una corrispondenza su un'eccezione ma non si effettua il test, è possibile mascherare un'eccezione diversa in un secondo momento. Se ottieni un'eccezione in un passaggio "when", allora dovrebbe propagarsi come errore di test. – Michael

2

Questo try approccio/catch da Barry funziona, ma vedo alcune questioni:

  • Aggiunta di un try/tranne che per i vostri passi significa che gli errori verranno nascosti.
  • L'aggiunta di un decoratore in più è inelegante. Vorrei che la mia decoratrice di essere una versione modificata @where

Il mio suggerimento è quello di

  • hanno l'eccezione aspettarsi prima della dichiarazione in mancanza
  • nel try/catch, aumentare se l'errore non era previsto
  • nel file after_scenario, genera un errore se l'errore previsto non viene rilevato.
  • utilizzare il dato modificato/quando/poi ovunque

Codice:

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) 

ho importare il mio modificato data/then/quando invece di comportarsi, e aggiungere al mio environment.py iniziare contesto.atteso fallire prima di scenario e la verifica dopo:

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

Il try/tranne approccio si mostra in realtà è del tutto corretto perché dimostra il modo in cui si sarebbe effettivamente utilizzare il codice nella vita reale. Tuttavia, c'è una ragione per cui non ti piace completamente. Si porta a brutti problemi con cose come la seguente:

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 

Se scrivo la definizione passo senza provare/salvo poi il secondo scenario avrà esito negativo. Se scrivo con try/tranne il primo scenario rischia di nascondere un'eccezione, specialmente se l'eccezione si verifica dopo che il prompt è già stato stampato.

Invece questi scenari dovrebbero, secondo me, essere scritte come qualcosa di simile

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 

Il "accedo a" passo non dovrebbe usare provare; Il "Tento di accedere" si abbina perfettamente per provare e dà via il fatto che potrebbe non esserci successo.

Poi arriva la domanda sul riutilizzo del codice tra i due passaggi quasi, ma non del tutto identici. Probabilmente non vogliamo avere due funzioni che entrambi accedono. Oltre ad avere semplicemente un'altra funzione comune che chiami, potresti anche fare qualcosa di simile alla fine del tuo file passo.

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

Questo convertirà automaticamente tutti i passaggi che contengono la parola "cercare di" in passi con lo stesso nome ma con provare a cancellare e poi proteggerli con una try/except.

Ci sono alcune domande su quando effettivamente dovresti occuparti delle eccezioni in BDD dato che non sono visibili all'utente. Non fa parte della risposta a questa domanda, quindi li ho inseriti in uno separate posting.