unitarias unit test pruebas prueba integracion ejecutar casos automatizadas python unit-testing file

unit - tdd python



Cómo hacer pruebas unitarias de funciones escribiendo archivos usando python unittest (5)

Tengo una función de Python que escribe un archivo de salida en el disco.

Quiero escribir una prueba unitaria para ello usando el módulo Pytest Unittest.

¿Cómo debo afirmar la igualdad de archivos? Me gustaría obtener un error si el contenido del archivo difiere del esperado + una lista de diferencias. Como en la salida del comando unix diff.

¿Hay alguna manera oficial / recomendada de hacer eso?


De acuerdo con las sugerencias, hice lo siguiente.

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)

Creé una subclase MyTestCase ya que tengo muchas funciones que necesitan leer / escribir archivos, así que realmente necesito tener un método reutilizable de afirmación. Ahora en mis pruebas, clasifico MyTestCase en lugar de unittest.TestCase.

¿Qué piensa usted al respecto?


Lo más simple es escribir el archivo de salida, luego leer su contenido, leer los contenidos del archivo dorado (esperado) y compararlos con una cadena de igualdad simple. Si son iguales, elimine el archivo de salida. Si son diferentes, plantea una afirmación.

De esta manera, cuando se realicen las pruebas, cada prueba fallida se representará con un archivo de salida, y puede usar una herramienta de terceros para diferenciarlas de los archivos dorados (Beyond Compare es maravilloso para esto).

Si realmente desea proporcionar su propia salida de diff, recuerde que Python stdlib tiene el módulo difflib. El nuevo soporte de unittest en Python 3.1 incluye un método assertMultiLineEqual que lo usa para mostrar diffs, similar a esto:

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)


Prefiero que las funciones de salida acepten explícitamente un identificador de archivo (o un objeto similar a un archivo), en lugar de aceptar un nombre de archivo y abrir el archivo ellos mismos. De esta forma, puedo pasar un objeto StringIO a la función de salida en mi prueba unitaria, luego .read() el contenido de ese objeto StringIO (después de una .seek(0) ) y compararlo con mi salida esperada.

Por ejemplo, haríamos una transición de código como este

##File:lamb.py import sys def write_lamb(outfile_path): with open(outfile_path, ''w'') as outfile: outfile.write("Mary had a little lamb./n") 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): outfile_path = tempfile.mkstemp()[1] try: lamb.write_lamb(outfile_path) contents = open(tempfile_path).read() finally: # NOTE: To retain the tempfile if the test fails, remove # the try-finally clauses os.remove(outfile_path) self.assertEqual(result, "Mary had a little lamb./n")

codificar de esta manera

##File:lamb.py import sys def write_lamb(outfile): outfile.write("Mary had a little lamb./n") if __name__ == ''__main__'': with open(sys.argv[1], ''w'') as outfile: write_lamb(outfile) ##File test_lamb.py import unittest from io import StringIO import lamb class LambTests(unittest.TestCase): def test_lamb_output(self): outfile = StringIO() # NOTE: Alternatively, for Python 2.6+, you can use # tempfile.SpooledTemporaryFile, e.g., #outfile = tempfile.SpooledTemporaryFile(10 ** 9) lamb.write_lamb(outfile) outfile.seek(0) content = outfile.read() self.assertEqual(content, "Mary had a little lamb./n")

Este enfoque tiene el beneficio adicional de hacer que su función de salida sea más flexible si, por ejemplo, decide que no desea escribir en un archivo, sino en otro búfer, ya que aceptará todos los objetos similares a archivos.

Tenga en cuenta que el uso de StringIO supone que el contenido de la salida de prueba puede caber en la memoria principal. Para un resultado muy grande, puede utilizar un enfoque de archivo temporal (por ejemplo, tempfile.SpooledTemporaryFile ).


Puede separar la generación de contenido del manejo de archivos. De esta forma, puede probar que el contenido es correcto sin tener que meterse con archivos temporales y limpiarlos después.

Si escribe un método de generador que produce cada línea de contenido, puede tener un método de manejo de archivos que abre un archivo y llama a file.writelines() con la secuencia de líneas. Los dos métodos podrían incluso estar en la misma clase: el código de prueba llamaría al generador y el código de producción llamaría al manejador de archivos.

Aquí hay un ejemplo que muestra las tres formas de probar. Normalmente, solo elegiría uno, dependiendo de los métodos disponibles en la clase para evaluar.

import os from io import StringIO from unittest.case import TestCase class Foo(object): def save_content(self, filename): with open(filename, ''w'') as f: self.write_content(f) def write_content(self, f): f.writelines(self.generate_content()) def generate_content(self): for i in range(3): yield u"line {}/n".format(i) class FooTest(TestCase): def test_generate(self): expected_lines = [''line 0/n'', ''line 1/n'', ''line 2/n''] foo = Foo() lines = list(foo.generate_content()) self.assertEqual(expected_lines, lines) def test_write(self): expected_text = u"""/ line 0 line 1 line 2 """ f = StringIO() foo = Foo() foo.write_content(f) self.assertEqual(expected_text, f.getvalue()) def test_save(self): expected_text = u"""/ line 0 line 1 line 2 """ foo = Foo() filename = ''foo_test.txt'' try: foo.save_content(filename) with open(filename, ''rU'') as f: text = f.read() finally: os.remove(filename) self.assertEqual(expected_text, text)


import filecmp

Entonces

self.assertTrue(filecmp.cmp(path1, path2))