web-dev-qa-db-fra.com

Comment affirmer la sortie avec nosetest / unittest en python?

J'écris des tests pour une fonction comme la suivante:

def foo():
    print 'hello world!'

Donc, quand je veux tester cette fonction, le code sera comme ceci:

import sys
from foomodule import foo
def test_foo():
    foo()
    output = sys.stdout.getline().strip() # because stdout is an StringIO instance
    assert output == 'hello world!'

Mais si j'exécute nosetests avec le paramètre -s, le test se bloque. Comment puis-je attraper la sortie avec le module unittest ou nose?

98
Pedro Valencia

J'utilise ceci gestionnaire de contexte pour capturer la sortie. Il utilise finalement la même technique que certaines des autres réponses en remplaçant temporairement sys.stdout. Je préfère le gestionnaire de contexte car il encapsule toute la comptabilité dans une seule fonction, donc je n'ai pas à réécrire de code try-finally, et je n'ai pas à écrire de fonctions de configuration et de démontage juste pour cela.

import sys
from contextlib import contextmanager
from StringIO import StringIO

@contextmanager
def captured_output():
    new_out, new_err = StringIO(), StringIO()
    old_out, old_err = sys.stdout, sys.stderr
    try:
        sys.stdout, sys.stderr = new_out, new_err
        yield sys.stdout, sys.stderr
    finally:
        sys.stdout, sys.stderr = old_out, old_err

Utilisez-le comme ceci:

with captured_output() as (out, err):
    foo()
# This can go inside or outside the `with` block
output = out.getvalue().strip()
self.assertEqual(output, 'hello world!')

De plus, étant donné que l'état de sortie d'origine est restauré à la sortie du bloc with, nous pouvons configurer un deuxième bloc de capture dans la même fonction que le premier, ce qui n'est pas possible à l'aide des fonctions de configuration et de démontage, et obtient verbeux lors de l'écriture manuelle des blocs try-finally. Cette capacité s'est avérée utile lorsque l'objectif d'un test était de comparer les résultats de deux fonctions l'une par rapport à l'autre plutôt qu'à une valeur précalculée.

103
Rob Kennedy

Si vous voulez vraiment le faire, vous pouvez réaffecter sys.stdout pour la durée du test.

def test_foo():
    import sys
    from foomodule import foo
    from StringIO import StringIO

    saved_stdout = sys.stdout
    try:
        out = StringIO()
        sys.stdout = out
        foo()
        output = out.getvalue().strip()
        assert output == 'hello world!'
    finally:
        sys.stdout = saved_stdout

Si j'écrivais ce code, cependant, je préférerais passer un paramètre out facultatif à la fonction foo.

def foo(out=sys.stdout):
    out.write("hello, world!")

Ensuite, le test est beaucoup plus simple:

def test_foo():
    from foomodule import foo
    from StringIO import StringIO

    out = StringIO()
    foo(out=out)
    output = out.getvalue().strip()
    assert output == 'hello world!'
56
Shane Hathaway

Depuis la version 2.7, vous n'avez plus besoin de réaffecter sys.stdout, ceci est fourni par buffer flag . De plus, c'est le comportement par défaut de nosetest.

Voici un exemple d'échec dans un contexte non tamponné:

import sys
import unittest

def foo():
    print 'hello world!'

class Case(unittest.TestCase):
    def test_foo(self):
        foo()
        if not hasattr(sys.stdout, "getvalue"):
            self.fail("need to run in buffered mode")
        output = sys.stdout.getvalue().strip() # because stdout is an StringIO instance
        self.assertEquals(output,'hello world!')

Vous pouvez définir un tampon via unit2 indicateur de ligne de commande -b, --buffer ou dans unittest.main options. L'inverse est obtenu grâce à nosetest flag --nocapture.

if __name__=="__main__":   
    assert not hasattr(sys.stdout, "getvalue")
    unittest.main(module=__name__, buffer=True, exit=False)
    #.
    #----------------------------------------------------------------------
    #Ran 1 test in 0.000s
    #
    #OK
    assert not hasattr(sys.stdout, "getvalue")

    unittest.main(module=__name__, buffer=False)
    #hello world!
    #F
    #======================================================================
    #FAIL: test_foo (__main__.Case)
    #----------------------------------------------------------------------
    #Traceback (most recent call last):
    #  File "test_stdout.py", line 15, in test_foo
    #    self.fail("need to run in buffered mode")
    #AssertionError: need to run in buffered mode
    #
    #----------------------------------------------------------------------
    #Ran 1 test in 0.002s
    #
    #FAILED (failures=1)
47
FabienAndre

Beaucoup de ces réponses ont échoué pour moi parce que vous ne pouvez pas from StringIO import StringIO in Python 3. Voici un extrait de travail minimum basé sur le commentaire de @ naxa et le Python Cookbook.

from io import StringIO
from unittest.mock import patch

with patch('sys.stdout', new=StringIO()) as fakeOutput:
    print('hello world')
    self.assertEqual(fakeOutput.getvalue().strip(), 'hello world')
27
Noumenon

Dans python 3.5 vous pouvez utiliser contextlib.redirect_stdout() et StringIO(). Voici la modification de votre code

import contextlib
from io import StringIO
from foomodule import foo

def test_foo():
    temp_stdout = StringIO()
    with contextlib.redirect_stdout(temp_stdout):
        foo()
    output = temp_stdout.getvalue().strip()
    assert output == 'hello world!'
20
Mudit Jain

J'apprends juste Python et je me suis retrouvé aux prises avec un problème similaire à celui ci-dessus avec des tests unitaires pour les méthodes avec sortie. Mon test unitaire réussi pour le module foo ci-dessus a fini par ressembler à ceci :

import sys
import unittest
from foo import foo
from StringIO import StringIO

class FooTest (unittest.TestCase):
    def setUp(self):
        self.held, sys.stdout = sys.stdout, StringIO()

    def test_foo(self):
        foo()
        self.assertEqual(sys.stdout.getvalue(),'hello world!\n')
15
sean_robbins

L'écriture de tests nous montre souvent une meilleure façon d'écrire notre code. Semblable à la réponse de Shane, j'aimerais suggérer une autre façon de voir les choses. Voulez-vous vraiment affirmer que votre programme a sorti une certaine chaîne, ou simplement qu'il a construit ne certaine chaîne pour la sortie? Cela devient plus facile à tester, car nous pouvons probablement supposer que l'instruction Python print fait correctement son travail.

def foo_msg():
    return 'hello world'

def foo():
    print foo_msg()

Votre test est alors très simple:

def test_foo_msg():
    assert 'hello world' == foo_msg()

Bien sûr, si vous avez vraiment besoin de tester la sortie réelle de votre programme, n'hésitez pas à l'ignorer. :)

10
Alison R.

Sur la base de la réponse de Rob Kennedy, j'ai écrit une version basée sur les classes du gestionnaire de contexte pour tamponner la sortie.

L'utilisation est comme:

with OutputBuffer() as bf:
    print('hello world')
assert bf.out == 'hello world\n'

Voici l'implémentation:

from io import StringIO
import sys


class OutputBuffer(object):

    def __init__(self):
        self.stdout = StringIO()
        self.stderr = StringIO()

    def __enter__(self):
        self.original_stdout, self.original_stderr = sys.stdout, sys.stderr
        sys.stdout, sys.stderr = self.stdout, self.stderr
        return self

    def __exit__(self, exception_type, exception, traceback):
        sys.stdout, sys.stderr = self.original_stdout, self.original_stderr

    @property
    def out(self):
        return self.stdout.getvalue()

    @property
    def err(self):
        return self.stderr.getvalue()
5
Hugo Mota

Ou pensez à utiliser pytest, il a un support intégré pour affirmer stdout et stderr. Voir docs

def test_myoutput(capsys): # or use "capfd" for fd-level
    print("hello")
    captured = capsys.readouterr()
    assert captured.out == "hello\n"
    print("next")
    captured = capsys.readouterr()
    assert captured.out == "next\n"
2
Michel Samia