Najprościej jest napisać plik wyjściowy, a następnie przeczytać jego zawartość, odczytać zawartość złotego (oczekiwanego) pliku i porównać je z prostą równością ciągów. Jeśli są takie same, usuń plik wyjściowy. Jeśli są różne, zgłoś twierdzenie.
W ten sposób, po zakończeniu testów, każdy nieudany test zostanie przedstawiony w pliku wyjściowym i możesz użyć narzędzia innej firmy, aby porównać je ze złotymi plikami ( Beyond Compare jest do tego wspaniały).
Jeśli naprawdę chcesz udostępnić własne wyjście diff, pamiętaj, że stdlib Pythona ma moduł difflib. Nowa obsługa unittest w Pythonie 3.1 zawiera assertMultiLineEqual
metodę, która używa jej do wyświetlania różnic, podobnie do tej:
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)
open
zgodnie z opisem w innych odpowiedziach na tej stronie, używającunittest.mock
(patrz odpowiedź od Enrico M)Wolę, aby funkcje wyjściowe wyraźnie akceptowały uchwyt pliku (lub obiekt podobny do pliku ), zamiast akceptować nazwę pliku i same otwierać plik. W ten sposób mogę przekazać
StringIO
obiekt do funkcji wyjściowej w moim teście jednostkowym, a następnie.read()
zawartość z powrotem z tegoStringIO
obiektu (po.seek(0)
wywołaniu) i porównać z oczekiwanymi wynikami.Na przykład moglibyśmy zmienić kod w ten sposób
##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")
kodować w ten sposób
##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")
Takie podejście ma tę dodatkową zaletę, że sprawia, że funkcja wyjściowa jest bardziej elastyczna, jeśli na przykład zdecydujesz, że nie chcesz zapisywać do pliku, ale do innego bufora, ponieważ akceptuje on wszystkie obiekty podobne do plików.
Zauważ, że użycie
StringIO
zakłada, że zawartość wyjścia testowego zmieści się w pamięci głównej. W przypadku bardzo dużych danych wyjściowych można użyć metody pliku tymczasowego (np. Tempfile.SpooledTemporaryFile ).źródło
import filecmp
Następnie
źródło
shallow
porównanie który sprawdza tylko metadanych plików (mtime, rozmiar, itp). Dodajshallow=False
swój przykład.Zawsze staram się unikać zapisywania plików na dysku, nawet jeśli jest to folder tymczasowy przeznaczony na moje testy: faktyczne nie dotykanie dysku znacznie przyspiesza testy, zwłaszcza jeśli w kodzie występuje dużo interakcji z plikami.
Załóżmy, że masz to „niesamowite” oprogramowanie w pliku o nazwie
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.")
Aby przetestować
write_to_file
metodę, możesz napisać coś takiego w pliku w tym samym folderze o nazwietest_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")
źródło
Możesz oddzielić generowanie treści od obsługi plików. W ten sposób możesz sprawdzić, czy zawartość jest poprawna, bez konieczności majstrowania przy plikach tymczasowych i późniejszego ich czyszczenia.
Jeśli napiszesz metodę generatora, która zwraca każdy wiersz treści, możesz mieć metodę obsługi plików, która otwiera plik i wywołuje
file.writelines()
sekwencję wierszy. Te dwie metody mogą nawet należeć do tej samej klasy: kod testowy wywoływałby generator, a kod produkcyjny wywoływałby procedurę obsługi plików.Oto przykład, który pokazuje wszystkie trzy sposoby testowania. Zwykle wystarczy wybrać jedną, w zależności od tego, jakie metody są dostępne w klasie do przetestowania.
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)
źródło
Na podstawie sugestii wykonałem następujące czynności.
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)
Stworzyłem podklasę MyTestCase, ponieważ mam wiele funkcji, które muszą odczytywać / zapisywać pliki, więc naprawdę potrzebuję metody assert wielokrotnego użytku. Teraz w moich testach podklasowałbym MyTestCase zamiast unittest.TestCase.
Co o tym myślisz?
źródło