103 votes

Comment faire des tests unitaires de fonctions d'écriture de fichiers à l'aide de python unittest

J'ai une fonction Python qui écrit un fichier de sortie sur le disque.

Je veux écrire un test unitaire pour cela en utilisant le module Python unittest.

Comment dois-je affirmer l'égalité des fichiers? Je voudrais obtenir une erreur si le contenu du fichier diffère de la liste attendue + des différences. Comme dans la sortie de la commande diff unix.

Existe-t-il un moyen officiel / recommandé de le faire?

87voto

gotgenes Points 8667

Je préfère avoir des fonctions de sortie explicitement accepter un fichier handle (ou fichier-comme objet), plutôt que d'accepter un fichier nom et l'ouverture du fichier eux-mêmes. De cette façon, je peux passer un StringIO.StringIO (ou plus généralement un cStringIO.StringIO) de l'objet à la fonction de sortie dans mon unité de test, .read() le contenu retour, StringIO objet (après un .seek(0) appel) et de la comparer avec ma sortie attendue.

Par exemple:

##File:lamb.py
import sys

def write_lamb(filename):
    outfile = open(filename, 'w')
    outfile.write("Mary had a little lamb.\n")
    outfile.close()

if __name__ == '__main__':
    write_lamb(sys.argv[1])


##File test_lamb.py
import unittest
import tempfile
import lamb

class LambTests(unittest.TestCase):
    def test_lamb_output(self):
        tempfile_path = tempfile.mkstemp()[1]
        lamb.write_lamb(tempfile_path)
        expected = "Mary had a little lamb.\n"
        result = open(tempfile_path).read()
        try:
            # NOTE: You could replace this with a string-comparison
            # method like assertMultiLineEqual
            self.assertEqual(result, expected)
        finally:
            # NOTE: To retain the tempfile if the test fails, remove
            # the try-finally clause.
            os.remove(tempfile_path)

Va à ce

##File:lamb2.py
import sys

def write_lamb(outfileh):
    outfileh.write("Mary had a little lamb.\n")

if __name__ == '__main__':
    outfile = open(sys.argv[1])
    write_lamb(outfile)
    outfile.close()


##File test_lamb2.py
import unittest
#import tempfile
import cStringIO
import lamb2

class LambTests(unittest.TestCase):
    def test_lamb_output(self):
        tempfile = cStringIO.StringIO()
        # NOTE: Alternatively, for Python 2.6+, you can use
        # tempfile.SpooledTemporaryFile, e.g.,
        #tempfile = tempfile.SpooledTemporaryFile(10 ** 9)
        lamb.write_lamb(tempfile)
        expected = "Mary had a little lamb.\n"
        tempfile.seek(0)
        result = tempfile.read()
        self.assertEqual(result, expected)

Cette approche a l'avantage de faire de votre fonction de sortie plus souple si, par exemple, vous décidez que vous ne voulez pas écrire dans un fichier, mais certains autres tampon, puisqu'il accepte tous les fichiers comme des objets.

Notez que l'utilisation de StringIO suppose que le contenu de la sortie du test peut s'intégrer dans la mémoire principale. Pour les très grosses sorties, utiliser un fichier temporaire (par exemple, tempfile.SpooledTemporaryFile).

57voto

Ned Batchelder Points 128913

La chose la plus simple est d'écrire le fichier de sortie, puis de lire son contenu, lire le contenu de l'or (attendue) de fichiers, et de les comparer avec une simple chaîne de l'égalité. Si elles sont identiques, supprimer le fichier de sortie. Si elles sont différentes, élever une affirmation.

De cette façon, lorsque les tests sont effectués, chaque échec de test sera représenté avec un fichier de sortie, et vous pouvez utiliser un outil de la 3e partie du diff contre l'or des fichiers (au-Delà de comparaison est merveilleux pour cette).

Si vous voulez vraiment vous fournir votre propre diff sortie, n'oubliez pas que le Python stdlib a la difflib module. La nouvelle unittest soutien en Python 3.1 inclut un assertMultiLineEqual méthode qu'il utilise pour montrer les différences, similaire à ceci:

    def assertMultiLineEqual(self, first, second, msg=None):
        """Assert that two multi-line strings are equal.

        If they aren't, show a nice diff.

        """
        self.assertTrue(isinstance(first, str),
                'First argument is not a string')
        self.assertTrue(isinstance(second, str),
                'Second argument is not a string')

        if first != second:
            message = ''.join(difflib.ndiff(first.splitlines(True),
                                                second.splitlines(True)))
            if msg:
                message += " : " + msg
            self.fail("Multi-line strings are unequal:\n" + message)

3voto

Don Kirkby Points 12671

Vous pourriez séparer la génération du contenu de la manipulation de fichiers. De cette façon, vous pouvez vérifier que le contenu est correct sans avoir à déranger les fichiers temporaires et de les nettoyer par la suite.

Si vous écrivez un générateur de méthode que les rendements de chaque ligne de contenu, alors vous pouvez avoir un fichier de la méthode de gestion qui ouvre un fichier et les appels file.writelines() avec la séquence de lignes. Les deux méthodes pourrait même être sur la même catégorie: test de code d'appel du générateur, et la production de code d'appeler le gestionnaire de fichier.

-1voto

jan Points 355

Sur la base de suggestions, j'ai fait ce qui suit.

 class MyTestCase(unittest.TestCase):
    def assertFilesEqual(self, first, second, msg=None):
        first_f = open(first)
        first_str = first_f.read()
        second_f = open(second)
        second_str = second_f.read()
        first_f.close()
        second_f.close()

        if first_str != second_str:
            first_lines = first_str.splitlines(True)
            second_lines = second_str.splitlines(True)
            delta = difflib.unified_diff(first_lines, second_lines, fromfile=first, tofile=second)
            message = ''.join(delta)

            if msg:
                message += " : " + msg

            self.fail("Multi-line strings are unequal:\n" + message)
 

J'ai créé une sous-classe MyTestCase car j'ai beaucoup de fonctions qui ont besoin de lire / écrire des fichiers, donc j'ai vraiment besoin d'avoir une méthode d'assertion réutilisable. Maintenant, dans mes tests, je sous-classerais MyTestCase au lieu de unittest.TestCase.

Qu'est-ce que tu en penses?

Prograide.com

Prograide est une communauté de développeurs qui cherche à élargir la connaissance de la programmation au-delà de l'anglais.
Pour cela nous avons les plus grands doutes résolus en français et vous pouvez aussi poser vos propres questions ou résoudre celles des autres.

Powered by:

X