2013-06-13 8 views
7

Ho una classe che istanziamo dando un nome di file come parser = ParserClass('/path/to/file'), quindi chiamo il metodo parser.parse() che apre e legge il file.
Ora voglio unit test che, se qualcosa di brutto accade dentro:Come test unitario con un oggetto file fittato in Python?

with open(filename, 'rb') as fp: 
    // do something 

l'eccezione corretta verrà sollevata, quindi voglio prendere in giro il __builtin__.open in questo modo:

from mock import MagicMock, patch 
from StringIO import StringIO 

test_lines = StringIO("""some test lines, emulating a real file content""") 
mock_open = MagicMock(return_value=test_lines) 
with patch('__builtin__.open', mock_open): 
    self.mock.parse() 

ma questo mi dà uno AttributeError: StringIO instance has no attribute '__exit__'.
Ho pensato che StringIO si comporta esattamente come un oggetto file, ma a quanto pare non è questo il caso.

Come è possibile testare questo metodo con un determinato contenuto (test_lines) con oggetti fittizi? Cosa dovrei usare invece?

risposta

9

Si potrebbe sottoclasse StringIO per fornire un gestore di contesto:

class ContextualStringIO(StringIO): 
    def __enter__(self): 
     return self 
    def __exit__(self, *args): 
     self.close() # icecrime does it, so I guess I should, too 
     return False # Indicate that we haven't handled the exception, if received 


test_lines = ContextualStringIO(...) 

speculazione lordo: se StringIO oggetti sono drop-in sostituzioni per file oggetti tranne per la mancanza di un contesto manager, ho chiedo se questo potrebbe funzionare:

class ContextualStringIO(StringIO, file): 
    pass 

ContextualStringIO eredita cosa le operazioni possono da StringIO, ma tutto il resto viene ereditato da file. Sembra elegante, ma probabilmente richiede test approfonditi (o qualcuno che abbia familiarità con gli interni di Python per spiegare perché questo non avrebbe funzionato).

+0

'TypeError: __exit __() richiede esattamente 1 argomento (4 dato)' – kissgyorgy

+0

@Walkman: Grazie. L'ho aggiornato per prendere un numero arbitrario di argomenti, dal momento che non gestirò nessuna delle eccezioni che potrebbero essere ricevute da '__exit__'. Una corretta implementazione dovrebbe; Non ho familiarità con il gestore di contesto fornito da 'open' per sapere se gestisce eventuali eccezioni, o se semplicemente chiude il file e consente di rievocare eventuali eccezioni. – chepner

+1

[Dalla documentazione:] (http://docs.python.org/2/reference/compound_stmts.html#with) Se la suite è stata chiusa a causa di un'eccezione e il valore restituito dal metodo __exit __() era falso, l'eccezione è controrilanciata. Se il valore restituito era true, l'eccezione viene soppressa e l'esecuzione continua con l'istruzione che segue l'istruzione with. – kissgyorgy

6

Questo è un problema noto che StringIO non implementa il protocollo del gestore di contesto.

A common recipe è il seguente:

from contextlib import contextmanager 


@contextmanager 
def StringIO(): 
    """Add support for 'with' statement to StringIO - http://bugs.python.org/issue1286 
    """ 
    try: 
     from cStringIO import StringIO 
    except ImportError: 
     from StringIO import StringIO 

    sio = StringIO() 

    try: 
     yield sio 
    finally: 
     sio.close() 

Esso implementa il protocollo responsabile contesto per StringIO e gli permette di essere utilizzato in una dichiarazione with.


UPDATE Beh, ho appena scoperto l'esistenza di mock_open che può leggere direttamente da una stringa, quindi è probabilmente la strada da percorrere.

+0

Grazie! Funziona, ma dovresti aggiungere 'def StringIO (data)' e 'sio = StringIO (data)' per poterlo costruire con un parametro. – kissgyorgy

+0

Giusto per sottolineare ancora una volta, l'aggiornamento è importante, "mock_open" è pensato per questo scopo, e nonostante http://bugs.python.org/issue21258 dovrebbe essere la soluzione ideale. – 0xc0de

2

In alternativa, è possibile utilizzare il io.StringIO from the Standard Library:

The io module provides the Python interfaces to stream handling. Under Python 2.x, this is proposed as an alternative to the built-in file object, but in Python 3.x it is the default interface to access files and streams.

Tuttavia (come la nota dice) questo può essere utilizzato solo con unicode tipo.

0

C'è a provision, appositamente per questo scopo nel mock libreria:

Esso dispone di un issue a sostegno mancante per iteratore di default (cioè __iter__ metodo, quindi non è possibile fare for line in opened_mock_file subito.), Ma può essere lavorato come descritto in here.

Ho svalutato la risposta di @ icecrime, ma ritengo che la sua parte di aggiornamento non sia sufficientemente evidenziata.