Prefiero que las funciones de salida acepten explícitamente un identificador de archivo (u objeto similar a un archivo), en lugar de aceptar un nombre de archivo y abrir el archivo ellos mismos. De esta manera, puedo pasar un StringIO
objeto a la función de salida en mi prueba unitaria, luego .read()
el contenido de ese StringIO
objeto (después de una .seek(0)
llamada) y compararlo con mi salida esperada.
Por ejemplo, haríamos una transición de código como este
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])
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:
os.remove(outfile_path)
self.assertEqual(result, "Mary had a little lamb.\n")
codificar como este
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)
import unittest
from io import StringIO
import lamb
class LambTests(unittest.TestCase):
def test_lamb_output(self):
outfile = StringIO()
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 algún otro búfer, ya que aceptará todos los objetos similares a archivos.
Tenga en cuenta que el uso StringIO
supone que el contenido de la salida de prueba puede caber en la memoria principal. Para una salida muy grande, puede utilizar un enfoque de archivo temporal (por ejemplo, tempfile.SpooledTemporaryFile ).
import filecmp
Entonces
fuente
shallow
comparación que verifica solo los metadatos de los archivos (mtime, size, etc.). Agregueshallow=False
su ejemplo.Siempre trato de evitar escribir archivos en el disco, incluso si es una carpeta temporal dedicada a mis pruebas: no tocar realmente el disco hace que tus pruebas sean mucho más rápidas, especialmente si interactúas mucho con archivos en tu código.
Suponga que tiene este software "asombroso" en un archivo llamado
main.py
:""" main.py """ def write_to_file(text): with open("output.txt", "w") as h: h.write(text) if __name__ == "__main__": write_to_file("Every great dream begins with a dreamer.")
Para probar el
write_to_file
método, puede escribir algo como esto en un archivo en la misma carpeta llamadotest_main.py
:""" test_main.py """ from unittest.mock import patch, mock_open import main def test_do_stuff_with_file(): open_mock = mock_open() with patch("main.open", open_mock, create=True): main.write_to_file("test-data") open_mock.assert_called_with("output.txt", "w") open_mock.return_value.write.assert_called_once_with("test-data")
fuente
Puede separar la generación de contenido del manejo de archivos. De esa manera, puede probar que el contenido es correcto sin tener que perder el tiempo con archivos temporales y limpiarlos después.
Si escribe un método generador que produce cada línea de contenido, entonces puede tener un método de manejo de archivos que abre un archivo y llama
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 controlador de archivos.A continuación, se muestra un ejemplo que muestra las tres formas de realizar la prueba. Por lo general, solo elegiría uno, dependiendo de los métodos disponibles en la clase para probar.
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)
fuente
Basado en 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, por lo que realmente necesito tener un método de aserción reutilizable. Ahora, en mis pruebas, subclase MyTestCase en lugar de unittest.TestCase.
¿Qué piensa usted al respecto?
fuente