2010-04-11 2 views
27

Comment écrire des tests où les conditions telles que les suivantes se posent:Comment fournir les entrées variables stdin, fichiers et environnement aux tests unitaires Python?

  1. utilisateur test d'entrée.
  2. Tester l'entrée lue dans un fichier.
  3. Entrée de test lue à partir d'une variable d'environnement.

Ce serait génial si quelqu'un pouvait me montrer comment aborder les scénarios mentionnés ci-dessus; Ce serait encore génial si vous pouviez me diriger vers quelques docs/articles/billets de blog que je pourrais lire.

+0

vérifier si je comprends bien votre question: ce que vous voulez savoir est: comment tester si un fichier exécutable/script, compte tenu de certains facteurs de production tels que les variables stdin/environnement donne les résultats escomptés , comme stdout, stderr ou le statut de sortie? –

Répondre

30

Les trois situations que vous avez décrites sont celles où vous devez vous déplacer pour vous assurer que vous utilisez un couplage lâche dans votre conception.

Avez-vous vraiment besoin de tester la méthode Python raw_input? La méthode open? os.environ.get?

Vous devez configurer votre conception de sorte que vous puissiez substituer d'autres façons de récupérer cette entrée. Ensuite, lors de vos tests unitaires, vous jetez un talon d'un type quelconque qui n'appelle pas réellement raw_input ou open.

Par exemple, votre code normal pourrait être quelque chose comme:

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 séance ressemble à:

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

Hello mark 

Ensuite, votre test sera comme:

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

Gardez à Gardez à l'esprit que vous ne devriez pas avoir à tester les fonctionnalités d'E/S d'une langue (sauf si vous êtes celui qui conçoit la langue, qui est une situation complètement différente).

+7

+1: Cela rend non seulement le code plus testable, mais aussi plus réutilisable puisqu'il n'est pas lié à une source d'entrée particulière. –

+1

Sauf lorsque vous n'avez pas ce qui précède sous votre contrôle.La belle route est souvent indisponible. – famousgarkin

+1

Comment cette approche fonctionne-t-elle avec plusieurs entrées affectées à plusieurs variables? Est-ce que vous créez plusieurs entrées_funcs ou y a-t-il une manière plus élégante? – TomKivy

5

Si vous pouvez vous en sortir sans utiliser de processus externe, faites-le.

Cependant, il y a des situations où cela est compliqué, et vous voulez vraiment utiliser un processus, par exemple, vous voulez tester l'interface de ligne de commande d'un exécutable C.

Entrée utilisateur

Utilisation subprocess.Popen comme dans:

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() 

Il n'y a pas de différence entre la prise d'entrée d'un utilisateur et le prendre à partir d'un tuyau d'entrée fait avec des méthodes telles que raw_input ou sys.stdin.read().

Fichiers

  • Créez un répertoire temporaire et créer les fichiers que vous souhaitez lire là-bas sur votre test setUp méthodes:

    tdir = tempfile.mkdtemp(
        prefix = 'filetest_', 
    ) 
    fpath = os.path.join(tdir,'filename') 
    fp = open(fpath, 'w') 
    fp.write("contents") 
    fp.close() 
    
  • Est-ce que le fichier à lire dans les tests .

  • Supprimez ensuite le dossier temp.

    shutil.rmtree(tdir) 
    
  • Il est assez compliqué la lecture de fichiers, et la plupart des programmes peuvent lire soit à partir de fichiers ou de STDIN (par exemple avec fileinput). Donc, si ce que vous voulez tester est ce qui se passe quand un certain contenu est entré, et votre programme accepte STDIN, utilisez simplement Popen pour tester le programme.

Variables d'environnement

  • Définissez les variables d'environnement avec os.environ["THE_VAR"] = "the_val"
  • Désarmer les avec del os.environ["THE_VAR"]
  • os.environ = {'a':'b'} ne fonctionne pas
  • Ensuite, appelez subprocess.Popen. L'environnement est hérité du processus appelant.

Code modèle

J'ai un module sur my github qui teste STDOUT, STDERR et l'état de sortie donné STDIN, les arguments de ligne de commande et enviroment. Vérifiez également les tests de ce module dans le répertoire "tests". Il doit y avoir beaucoup de meilleurs modules pour cela, alors prenez le mien juste pour apprendre.

29

Si vous êtes lié à l'utilisation de raw_input (ou de toute autre source d'entrée spécifique), je suis un grand partisan du mock library. Vu le code que Mark Rushakoff utilisé dans son exemple:

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

Votre code de test pourrait utiliser la maquette:

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' 

Ces affirmations passeraient. Notez que side_effect renvoie les éléments de la liste dans l'ordre. Cela peut faire tellement plus! Je vous recommande de consulter la documentation.

+13

Pour Python 3, notez que '__builtin__' a été renommé comme' builtins' et 'raw_input()' est devenu 'input()', ainsi: 'mock.patch ('builtins.input', return_value = 'dew')' – geertjanvdk

+3

Je suis dbw, pas de rosée :) – dbn

+0

Mock fait partie de core python dans Python 3 comme 'from unittest import mock'. – dbn

3

En utilisant 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' 
Questions connexes