2015-02-24 20 views
10

Ho un metodo in python (2.7) che fa pippo, e si arresta dopo 5 minuti se foo non ha funzionato.override funzione python locale-variabile in unittest

def keep_trying(self): 
    timeout = 300 #empirically derived, appropriate timeout 
    end_time = time.time() + timeout 
    while (time.time() < end_time): 
     result = self.foo() 
     if (result == 'success'): 
      break 
     time.sleep(2) 
    else: 
     raise MyException('useful msg here') 

So che alcuni possibili risultati di foo(), quindi sto usando finta di falsi quei valori di ritorno. Il problema è che non voglio che il test venga eseguito 5 minuti prima che venga visualizzata l'eccezione.

C'è un modo per ignorare quel valore locale di timeout? Mi piacerebbe che fossero solo pochi secondi in modo che io possa vedere il ciclo provare un paio di volte, poi rinunciare e rilanciare.

Quanto segue non funziona:

@patch.object(myClass.keep_trying, 'timeout') 
@patch.object(myClass, 'foo') 
def test_keep_trying(self, mock_foo, mock_timeout): 
    mock_foo.return_value = 'failed' 
    mock_timeout.return_value = 10 # raises AttributeError 
    mock_timeout = 10 # raises AttributeError 
    ... 

risposta

11

Piuttosto che cercare di deridere il valore se timeout, si vorrà prendere in giro il valore di ritorno di time.time().

ad es.

@patch.object(time, 'time') 
def test_keep_trying(self, mock_time): 
    mock_time.side_effect = iter([100, 200, 300, 400, 500, 600, 700, 800]) 
    ... 

Ora la prima volta time.time() si chiama, si otterrà il valore di 100, quindi dovrebbe timeout quando dopo un paio di giri del vostro ciclo while. Puoi anche prendere in giro time.sleep e contare quante volte viene chiamato per assicurarti che parte del codice funzioni correttamente.


Un altro approccio (che non è del tutto ortogonale a quella di cui sopra) è quello di permettere all'utente di passare una parola chiave timeout opzionale alla funzione:

def keep_trying(self, timeout=300): 
    ... 

Ciò consente di specificare qualunque timeout vuoi nei test (e nel futuro codice che non vuole aspettare 5 minuti ;-).

+0

mock_time lo è! – anregen

+0

In realtà, ho usato '@ patch.object (myClass.time, 'time')' per mantenere il mock.side_effect locale su quel modulo. – anregen

11

Non è possibile prendere in giro la variabile locale di una funzione. Per rendere il codice più facile da testare, cambiare a, per esempio:

def keep_trying(self, timeout=300): 
    end_time = time.time() + timeout 
    # etc, as above 

così diventa banale per i test da eseguire con un timeout più breve!

+0

E stavo proprio modificando questo nella mia risposta ... :-) – mgilson

+3

La funzione originale è parte di un'API pubblicata, quindi non voglio cambiare la firma, e non voglio che gli utenti abbiano accesso a regolare quel timeout. – anregen

+3

Python funziona come un gentleman's agreement e alcune convenzioni - se fai in modo che l'argomento abbia un singolo trattino basso - i tuoi utenti sapranno che l'argomento non dovrebbe essere usato - e qualsiasi sistema di documentazione decente ti consentirà di omettere argomenti, metodi, campi privati ecc. comunque, quindi solo un utente molto determinato troverà l'argomento in primo luogo. –