2010-04-11 10 views
27

Come scrivere i test in cui sussistono condizioni come il seguente:Come fornire stdin, file e input di variabili d'ambiente ai test di unità Python?

  1. test input dell'utente.
  2. Test input letto da un file.
  3. Test input letto da una variabile di ambiente.

Sarebbe bello se qualcuno potesse mostrarmi come affrontare gli scenari di cui sopra; Sarebbe comunque fantastico se tu potessi indicarmi alcuni documenti/articoli/post di blog che potrei leggere su .

+0

controllare se ho capito la tua domanda correttamente: ciò che si vuole sapere è: come verificare se un eseguibile/script, dato alcuni input come ad esempio le variabili standard input/ambiente dà i risultati attesi , come lo stato stdout, stderr o exit? –

risposta

30

Tutte e tre le situazioni che hai descritto sono dove devi specificamente andare fuori strada per assicurarti di utilizzare un accoppiamento libero nel tuo progetto.

Sei davvero necessario testare l'unità il metodo raw_input di Python? Il metodo open? os.environ.get?

È necessario configurare il progetto in modo da poter sostituire altri modi di recuperare tale input. Quindi, durante i test unitari, inserirai uno stub di qualche tipo che in realtà non chiama raw_input o open.

Per esempio, il codice normale potrebbe essere qualcosa del tipo:

import os 
def say_hello(input_func): 
    name = input_func() 
    return "Hello " + name 

def prompt_for_name(): 
    return raw_input("What is your name? ") 

print say_hello(prompt_for_name) 
# Normally would pass in methods, but lambdas can be used for brevity 
print say_hello(lambda: open("a.txt").readline()) 
print say_hello(lambda: os.environ.get("USER")) 

La sessione si presenta come:

 
What is your name? somebody 
Hello somebody 
Hello [some text] 

Hello mark 

Poi il test sarà come:

def test_say_hello(): 
    output = say_hello(lambda: "test") 
    assert(output == "Hello test") 

Tenete non è necessario verificare le funzionalità di I/O di una lingua (a meno che tu non sia quello che progetta la lingua, w che è una situazione completamente diversa).

+7

+1: Ciò non solo rende il codice più verificabile, ma anche più riutilizzabile poiché non è legato a una particolare sorgente di input. –

+1

Tranne quando non si dispone di quanto sopra sotto il proprio controllo.Il bel percorso è spesso non disponibile. – famousgarkin

+1

Quindi, come funziona questo approccio con più ingressi assegnati a più variabili. Crei più input_funcs o c'è un modo più elegante? – TomKivy

5

Se è possibile allontanarsi senza utilizzare un processo esterno, farlo.

Tuttavia, ci sono situazioni in cui questo è complicato e si desidera veramente utilizzare il processo, ad esempio, si desidera testare l'interfaccia della riga di comando di un eseguibile C.

input dell'utente

Usa subprocess.Popen come in:

process = subprocess.Popen(
    command, 
    shell = False, 
    stdin = subprocess.PIPE, 
    stdout = subprocess.PIPE, 
    stderr = subprocess.PIPE, 
    universal_newlines = True 
) 
stdout, stderr = process.communicate("the user input\nline 2") 
exit_status = process.wait() 

Non c'è alcuna differenza tra prendere l'input da un utente e prendendolo da un tubo per l'ingresso fatto con metodi come raw_input o sys.stdin.read().

file

  • Creare una directory temporanea e creare i file che si desidera leggere da lì il tuo test setUp metodi:

    tdir = tempfile.mkdtemp(
        prefix = 'filetest_', 
    ) 
    fpath = os.path.join(tdir,'filename') 
    fp = open(fpath, 'w') 
    fp.write("contents") 
    fp.close() 
    
  • fare la lettura del file nelle prove .

  • Rimuovere la directory temp in seguito.

    shutil.rmtree(tdir) 
    
  • E 'la lettura piuttosto complicato da file, e la maggior parte dei programmi in grado di leggere sia da file o da STDIN (ad esempio con fileinput). Quindi, se quello che vuoi testare è ciò che accade quando viene inserito un determinato contenuto e il tuo programma accetta STDIN, usa semplicemente Popen per testare il programma.

variabili d'ambiente

  • Impostare le variabili d'ambiente con os.environ["THE_VAR"] = "the_val"
  • Unset loro con del os.environ["THE_VAR"]
  • os.environ = {'a':'b'} non funziona
  • Quindi chiamare subprocess.Popen. L'ambiente è ereditato dal processo chiamante.

Codice Template

Ho un modulo su my github che mette alla prova STDOUT, STDERR e lo stato di uscita data STDIN, argomenti della riga di comando e l'ambiente. Inoltre, controlla i test per quel modulo sotto la directory "tests". Ci devono essere moduli molto migliori là fuori per questo, quindi prendi il mio solo per scopi di apprendimento.

29

Se sei legato all'utilizzo di raw_input (o di qualsiasi altra sorgente di input specifica), sono un grande sostenitore dello mock library. Dato il codice che Marco Rushakoff utilizzato nel suo esempio:

def say_hello(): 
    name = raw_input("What is your name? ") 
    return "Hello " + name 

Il tuo codice di prova potrebbe usare finto:

import mock 

def test_say_hello(): 
    with mock.patch('__builtin__.raw_input', return_value='dbw'): 
     assert say_hello() == 'Hello dbw' 

    with mock.patch('__builtin__.raw_input', side_effect=['dbw', 'uki']): 
     assert say_hello() == 'Hello dbw' 
     assert say_hello() == 'Hello uki' 

Queste affermazioni sarebbero passati. Nota che side_effect restituisce gli elementi della lista in ordine. Può fare molto di più! Consiglierei di controllare la documentazione.

+13

Per Python 3, si noti che '__builtin__' è stato rinominato come' builtins' e 'raw_input()' è diventato 'input()', quindi: 'mock.patch ('builtins.input', return_value = 'dew')' – geertjanvdk

+3

Sono dbw, non dew :) – dbn

+0

Mock fa parte del core python in Python 3 come 'from unittest import mock'. – dbn

3

Utilizzando pytest:

import os 


def test_user_input(monkeypatch): 
    inputs = [10, 'y'] 
    input_generator = (i for i in inputs) 
    monkeypatch.setattr('__builtin__.raw_input', lambda prompt: next(input_generator)) 
    assert raw_input('how many?') == 10 
    assert raw_input('you sure?') == 'y' 


def test_file_input(tmpdir): 
    fixture = tmpdir.join('fixture.txt') 
    fixture.write(os.linesep.join(['1', '2', '3'])) 
    fixture_path = str(fixture.realpath()) 
    with open(fixture_path) as f: 
     assert f.readline() == '1' + os.linesep 


def test_environment_input(monkeypatch): 
    monkeypatch.setenv('STAGING', 1) 
    assert os.environ['STAGING'] == '1'